From 003d816d5f5700821d103679294b333ad2f65406 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 29 Apr 2024 15:04:10 -0700 Subject: [PATCH 01/14] Move all js emit alias marking behavior behind a single entrypoint --- src/compiler/checker.ts | 534 +++++++++++++++++++++++----------------- 1 file changed, 311 insertions(+), 223 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 53e9c4387974c..89334bbcf9d7a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -692,6 +692,7 @@ import { isPrivateIdentifierPropertyAccessExpression, isPropertyAccessEntityNameExpression, isPropertyAccessExpression, + isPropertyAccessOrQualifiedName, isPropertyAccessOrQualifiedNameOrImportTypeNode, isPropertyAssignment, isPropertyDeclaration, @@ -4253,53 +4254,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } - function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { - if (!canCollectSymbolAliasAccessabilityData) { - return; - } - const symbol = getSymbolOfDeclaration(node); - const target = resolveAlias(symbol); - if (target) { - const markAlias = target === unknownSymbol || - ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); - - if (markAlias) { - markAliasSymbolAsReferenced(symbol); - } - } - } - - // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until - // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of - // the alias as an expression (which recursively takes us back here if the target references another alias). - function markAliasSymbolAsReferenced(symbol: Symbol) { - Debug.assert(canCollectSymbolAliasAccessabilityData); - const links = getSymbolLinks(symbol); - if (!links.referenced) { - links.referenced = true; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - // We defer checking of the reference of an `import =` until the import itself is referenced, - // This way a chain of imports can be elided if ultimately the final input is only used in a type - // position. - if (isInternalModuleImportEqualsDeclaration(node)) { - if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { - // import foo = - checkExpressionCached(node.moduleReference as Expression); - } - } - } - } - - // Aliases that resolve to const enums are not marked as referenced because they are not emitted, - // but their usage in value positions must be tracked to determine if the import can be type-only. - function markConstEnumAliasAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.constEnumReferenced) { - links.constEnumReferenced = true; - } - } - // This function is only for imports with entity names function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { // There are three things we might try to look for. In the following examples, @@ -29185,28 +29139,316 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); } - function markAliasReferenced(symbol: Symbol, location: Node) { + function markLinkedReferences(location: Node) { if (!canCollectSymbolAliasAccessabilityData) { return; } - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { + // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used + // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` + // This is the exception, rather than the rule - most non-expression identifiers are declaration names. + if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { + if (isPropertyAccessOrQualifiedName(location.parent)) { + const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; + if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) + } + markIdentifierAliasReferenced(location); + return; + } + if (isPropertyAccessOrQualifiedName(location)) { + let topProp: Node | undefined = location; + while (isPropertyAccessOrQualifiedName(topProp)) { + if (isPartOfTypeNode(topProp)) return; + topProp = topProp.parent; + } + const left = isPropertyAccessExpression(location) ? location.expression : location.left; + if (isThisIdentifier(left)) { + return; + } + const right = isPropertyAccessExpression(location) ? location.name : location.right; + const assignmentKind = getAssignmentTargetKind(location); + const leftType = checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); + const parentSymbol = getNodeLinks(left).resolvedSymbol; + if (!parentSymbol || parentSymbol === unknownSymbol) { + return; + } + if (isTypeAny(leftType) || leftType === silentNeverType) { + markAliasReferenced(parentSymbol, location); + return; + } + const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + const prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) : getPropertyOfType(apparentType, right.escapedText); + const node = location; + // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. + // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined + // here even if `Foo` is not a const enum. + // + // The exceptions are: + // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and + // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. + if ( + isIdentifier(left) && parentSymbol && ( + getIsolatedModules(compilerOptions) || + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node) + ) + ) { + markAliasReferenced(parentSymbol, node); + } + return; + } + if (isExportAssignment(location) && isIdentifier(location.expression)) { + const id = location.expression; + const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location)); + if (sym) { + markAliasReferenced(sym, id); + } + return; + } + if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { + const node = location; + if (!getJsxNamespaceContainerForImplicitImport(node)) { + // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. + // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. + const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; + const jsxFactoryNamespace = getJsxNamespace(node); + const jsxFactoryLocation = isJsxOpeningLikeElement(node) ? node.tagName : node; + + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); + } + + if (jsxFactorySym) { + // Mark local symbol as referenced here because it might not have been marked + // if jsx emit was not jsxFactory as there wont be error being emitted + jsxFactorySym.isReferenced = SymbolFlags.All; + + // If react/jsxFactory symbol is alias, mark it as refereced + if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); + } + } + + // For JsxFragment, mark jsx pragma as referenced via resolveName + if (isJsxOpeningFragment(node)) { + const file = getSourceFileOfNode(node); + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); + } + } + } + return; + } + if (languageVersion < ScriptTarget.ES2015 && (isFunctionLikeDeclaration(location) || isMethodSignature(location))) { + if (getFunctionFlags(location) & FunctionFlags.Async) { + const returnTypeNode = getEffectiveReturnTypeNode(location); + markTypeNodeAsReferenced(returnTypeNode); + } + // functions are maybe-decoratable and may need their argument type nodes marked for decorator metadata + } + if (isImportEqualsDeclaration(location) && hasSyntacticModifier(location, ModifierFlags.Export) && (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location))) { + markExportAsReferenced(location); + return; + } + if (isExportSpecifier(location) && !location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) { + const exportedName = location.propertyName || location.name; + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + // Do nothing, non-local symbol + } + else { + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || getSymbolFlags(target) & SymbolFlags.Value) { + markExportAsReferenced(location); // marks export as used + markIdentifierAliasReferenced(location.propertyName || location.name); // marks target of export as used + } + } + return; + } + if (compilerOptions.emitDecoratorMetadata) { + const node = location; + if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) { + return; + } + + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } + + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + + // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + const constructor = getFirstConstructorWithBody(node); + if (constructor) { + for (const parameter of constructor.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + } + break; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of node.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); + break; + + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); + break; + + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + const containingSignature = node.parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature)); + break; + } + } + return; + + function markIdentifierAliasReferenced(location: Identifier) { + const symbol = getResolvedSymbol(location); + if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { + markAliasReferenced(symbol, location); + } + } + + function markAliasReferenced(symbol: Symbol, location: Node) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { + const target = resolveAlias(symbol); + if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { + // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled + // (because the const enum value will not be inlined), or if (2) the alias is an export + // of a const enum declaration that will be preserved. + if ( + getIsolatedModules(compilerOptions) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + ) { + markAliasSymbolAsReferenced(symbol); + } + else { + markConstEnumAliasAsReferenced(symbol); + } + } + } + } + + // Aliases that resolve to const enums are not marked as referenced because they are not emitted, + // but their usage in value positions must be tracked to determine if the import can be type-only. + function markConstEnumAliasAsReferenced(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.constEnumReferenced) { + links.constEnumReferenced = true; + } + } + + // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until + // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of + // the alias as an expression (which recursively takes us back here if the target references another alias). + function markAliasSymbolAsReferenced(symbol: Symbol) { + Debug.assert(canCollectSymbolAliasAccessabilityData); + const links = getSymbolLinks(symbol); + if (!links.referenced) { + links.referenced = true; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + // We defer checking of the reference of an `import =` until the import itself is referenced, + // This way a chain of imports can be elided if ultimately the final input is only used in a type + // position. + if (isInternalModuleImportEqualsDeclaration(node)) { + if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { + // import foo = + checkExpressionCached(node.moduleReference as Expression); + } + } + } + } + + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfDeclaration(node); const target = resolveAlias(symbol); - if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { - // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled - // (because the const enum value will not be inlined), or if (2) the alias is an export - // of a const enum declaration that will be preserved. + if (target) { + const markAlias = target === unknownSymbol || + ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); + + if (markAlias) { + markAliasSymbolAsReferenced(symbol); + } + } + } + + function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { + if (!typeName) return; + + const rootName = getFirstIdentifier(typeName); + const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; + const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { if ( - getIsolatedModules(compilerOptions) || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || - !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + canCollectSymbolAliasAccessabilityData + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol) ) { - markAliasSymbolAsReferenced(symbol); + markAliasSymbolAsReferenced(rootSymbol); } - else { - markConstEnumAliasAsReferenced(symbol); + else if ( + forDecoratorMetadata + && getIsolatedModules(compilerOptions) + && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 + && !symbolIsValue(rootSymbol) + && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration) + ) { + const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); + const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); + if (aliasDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); + } } } } + + /** + * If a TypeNode can be resolved to a value symbol imported from an external module, it is + * marked as referenced to prevent import elision. + */ + function markTypeNodeAsReferenced(node: TypeNode | undefined) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); + } + + /** + * This function marks the type used for metadata decorator as referenced if it is import + * from external module. + * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in + * union and intersection type + * @param node + */ + function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { + const entityName = getEntityNameForDecoratorMetadata(node); + if (entityName && isEntityName(entityName)) { + markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); + } + } } function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier, checkMode?: CheckMode) { @@ -29342,9 +29584,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getTypeOfSymbol(symbol); } - if (shouldMarkIdentifierAliasReferenced(node)) { - markAliasReferenced(symbol, node); - } + markLinkedReferences(node); const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node); @@ -32473,39 +32713,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkJsxPreconditions(node); - if (!getJsxNamespaceContainerForImplicitImport(node)) { - // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. - // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. - const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; - const jsxFactoryNamespace = getJsxNamespace(node); - const jsxFactoryLocation = isNodeOpeningLikeElement ? node.tagName : node; - - // allow null as jsxFragmentFactory - let jsxFactorySym: Symbol | undefined; - if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { - jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); - } - - if (jsxFactorySym) { - // Mark local symbol as referenced here because it might not have been marked - // if jsx emit was not jsxFactory as there wont be error being emitted - jsxFactorySym.isReferenced = SymbolFlags.All; - - // If react/jsxFactory symbol is alias, mark it as refereced - if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { - markAliasSymbolAsReferenced(jsxFactorySym); - } - } - - // For JsxFragment, mark jsx pragma as referenced via resolveName - if (isJsxOpeningFragment(node)) { - const file = getSourceFileOfNode(node); - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); - } - } - } + markLinkedReferences(node); if (isNodeOpeningLikeElement) { const jsxOpeningLikeNode = node; @@ -33082,28 +33290,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else { if (isAnyLike) { if (isIdentifier(left) && parentSymbol) { - markAliasReferenced(parentSymbol, node); + markLinkedReferences(node); } return isErrorType(apparentType) ? errorType : apparentType; } prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); } - // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. - // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined - // here even if `Foo` is not a const enum. - // - // The exceptions are: - // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and - // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. - if ( - isIdentifier(left) && parentSymbol && ( - getIsolatedModules(compilerOptions) || - !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node) - ) - ) { - markAliasReferenced(parentSymbol, node); - } + markLinkedReferences(node); let propType: Type; if (!prop) { @@ -41632,6 +41825,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // then(...): Promise; // } // + // Always mark the type node as referenced if it points to a value + markLinkedReferences(node); const returnType = getTypeFromTypeNode(returnTypeNode); if (languageVersion >= ScriptTarget.ES2015) { if (isErrorType(returnType)) { @@ -41646,9 +41841,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { - // Always mark the type node as referenced if it points to a value - markTypeNodeAsReferenced(returnTypeNode); - if (isErrorType(returnType)) { return; } @@ -41867,59 +42059,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [valueParam], voidType); } - /** - * If a TypeNode can be resolved to a value symbol imported from an external module, it is - * marked as referenced to prevent import elision. - */ - function markTypeNodeAsReferenced(node: TypeNode) { - markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); - } - - function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { - if (!typeName) return; - - const rootName = getFirstIdentifier(typeName); - const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; - const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); - if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { - if ( - canCollectSymbolAliasAccessabilityData - && symbolIsValue(rootSymbol) - && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) - && !getTypeOnlyAliasDeclaration(rootSymbol) - ) { - markAliasSymbolAsReferenced(rootSymbol); - } - else if ( - forDecoratorMetadata - && getIsolatedModules(compilerOptions) - && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 - && !symbolIsValue(rootSymbol) - && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration) - ) { - const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); - const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); - if (aliasDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); - } - } - } - } - - /** - * This function marks the type used for metadata decorator as referenced if it is import - * from external module. - * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in - * union and intersection type - * @param node - */ - function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { - const entityName = getEntityNameForDecoratorMetadata(node); - if (entityName && isEntityName(entityName)) { - markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); - } - } - function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { if (node) { switch (node.kind) { @@ -42027,48 +42166,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - if (compilerOptions.emitDecoratorMetadata) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); - - // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - const constructor = getFirstConstructorWithBody(node); - if (constructor) { - for (const parameter of constructor.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - } - break; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); - markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); - break; - case SyntaxKind.MethodDeclaration: - for (const parameter of node.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); - break; - - case SyntaxKind.PropertyDeclaration: - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); - break; - - case SyntaxKind.Parameter: - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); - const containingSignature = node.parent; - for (const parameter of containingSignature.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature)); - break; - } - } + markLinkedReferences(node); for (const modifier of node.modifiers) { if (isDecorator(modifier)) { @@ -46400,9 +46498,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkGrammarModifiers(node); if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { checkImportBinding(node); - if (hasSyntacticModifier(node, ModifierFlags.Export)) { - markExportAsReferenced(node); - } + markLinkedReferences(node); if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { const target = resolveAlias(getSymbolOfDeclaration(node)); if (target !== unknownSymbol) { @@ -46503,6 +46599,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getEmitDeclarations(compilerOptions)) { collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); } + markLinkedReferences(node); if (!node.parent.parent.moduleSpecifier) { const exportedName = node.propertyName || node.name; // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) @@ -46510,15 +46607,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } - else { - if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) { - markExportAsReferenced(node); - } - const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); - if (!target || getSymbolFlags(target) & SymbolFlags.Value) { - checkExpressionCached(node.propertyName || node.name); - } - } } else { if ( @@ -46569,9 +46657,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (node.expression.kind === SyntaxKind.Identifier) { const id = node.expression as Identifier; const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node)); + markLinkedReferences(node); if (sym) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value); - markAliasReferenced(sym, id); // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) if (getSymbolFlags(sym) & SymbolFlags.Value) { // However if it is a value, we need to check it's being used correctly From 362ba1ba391bf6adbbe35022d90a2089bf1ff5d2 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 29 Apr 2024 18:38:21 -0700 Subject: [PATCH 02/14] Move sub functions into outer closure to avoid unnecessary copying --- src/compiler/checker.ts | 208 ++++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 89334bbcf9d7a..0b7ba79d83361 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29321,133 +29321,133 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } return; + } - function markIdentifierAliasReferenced(location: Identifier) { - const symbol = getResolvedSymbol(location); - if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { - markAliasReferenced(symbol, location); - } + function markIdentifierAliasReferenced(location: Identifier) { + const symbol = getResolvedSymbol(location); + if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { + markAliasReferenced(symbol, location); } + } - function markAliasReferenced(symbol: Symbol, location: Node) { - if (!canCollectSymbolAliasAccessabilityData) { - return; - } - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { - const target = resolveAlias(symbol); - if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { - // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled - // (because the const enum value will not be inlined), or if (2) the alias is an export - // of a const enum declaration that will be preserved. - if ( - getIsolatedModules(compilerOptions) || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || - !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) - ) { - markAliasSymbolAsReferenced(symbol); - } - else { - markConstEnumAliasAsReferenced(symbol); - } + function markAliasReferenced(symbol: Symbol, location: Node) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { + const target = resolveAlias(symbol); + if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { + // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled + // (because the const enum value will not be inlined), or if (2) the alias is an export + // of a const enum declaration that will be preserved. + if ( + getIsolatedModules(compilerOptions) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + ) { + markAliasSymbolAsReferenced(symbol); + } + else { + markConstEnumAliasAsReferenced(symbol); } } } + } - // Aliases that resolve to const enums are not marked as referenced because they are not emitted, - // but their usage in value positions must be tracked to determine if the import can be type-only. - function markConstEnumAliasAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.constEnumReferenced) { - links.constEnumReferenced = true; - } + // Aliases that resolve to const enums are not marked as referenced because they are not emitted, + // but their usage in value positions must be tracked to determine if the import can be type-only. + function markConstEnumAliasAsReferenced(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.constEnumReferenced) { + links.constEnumReferenced = true; } + } - // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until - // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of - // the alias as an expression (which recursively takes us back here if the target references another alias). - function markAliasSymbolAsReferenced(symbol: Symbol) { - Debug.assert(canCollectSymbolAliasAccessabilityData); - const links = getSymbolLinks(symbol); - if (!links.referenced) { - links.referenced = true; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - // We defer checking of the reference of an `import =` until the import itself is referenced, - // This way a chain of imports can be elided if ultimately the final input is only used in a type - // position. - if (isInternalModuleImportEqualsDeclaration(node)) { - if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { - // import foo = - checkExpressionCached(node.moduleReference as Expression); - } + // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until + // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of + // the alias as an expression (which recursively takes us back here if the target references another alias). + function markAliasSymbolAsReferenced(symbol: Symbol) { + Debug.assert(canCollectSymbolAliasAccessabilityData); + const links = getSymbolLinks(symbol); + if (!links.referenced) { + links.referenced = true; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + // We defer checking of the reference of an `import =` until the import itself is referenced, + // This way a chain of imports can be elided if ultimately the final input is only used in a type + // position. + if (isInternalModuleImportEqualsDeclaration(node)) { + if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { + // import foo = + checkExpressionCached(node.moduleReference as Expression); } } } + } - function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { - const symbol = getSymbolOfDeclaration(node); - const target = resolveAlias(symbol); - if (target) { - const markAlias = target === unknownSymbol || - ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfDeclaration(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); - if (markAlias) { - markAliasSymbolAsReferenced(symbol); - } + if (markAlias) { + markAliasSymbolAsReferenced(symbol); } } + } - function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { - if (!typeName) return; + function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { + if (!typeName) return; - const rootName = getFirstIdentifier(typeName); - const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; - const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); - if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { - if ( - canCollectSymbolAliasAccessabilityData - && symbolIsValue(rootSymbol) - && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) - && !getTypeOnlyAliasDeclaration(rootSymbol) - ) { - markAliasSymbolAsReferenced(rootSymbol); - } - else if ( - forDecoratorMetadata - && getIsolatedModules(compilerOptions) - && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 - && !symbolIsValue(rootSymbol) - && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration) - ) { - const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); - const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); - if (aliasDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); - } + const rootName = getFirstIdentifier(typeName); + const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; + const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { + if ( + canCollectSymbolAliasAccessabilityData + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol) + ) { + markAliasSymbolAsReferenced(rootSymbol); + } + else if ( + forDecoratorMetadata + && getIsolatedModules(compilerOptions) + && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 + && !symbolIsValue(rootSymbol) + && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration) + ) { + const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); + const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); + if (aliasDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); } } } + } - /** - * If a TypeNode can be resolved to a value symbol imported from an external module, it is - * marked as referenced to prevent import elision. - */ - function markTypeNodeAsReferenced(node: TypeNode | undefined) { - markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); - } + /** + * If a TypeNode can be resolved to a value symbol imported from an external module, it is + * marked as referenced to prevent import elision. + */ + function markTypeNodeAsReferenced(node: TypeNode | undefined) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); + } - /** - * This function marks the type used for metadata decorator as referenced if it is import - * from external module. - * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in - * union and intersection type - * @param node - */ - function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { - const entityName = getEntityNameForDecoratorMetadata(node); - if (entityName && isEntityName(entityName)) { - markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); - } + /** + * This function marks the type used for metadata decorator as referenced if it is import + * from external module. + * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in + * union and intersection type + * @param node + */ + function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { + const entityName = getEntityNameForDecoratorMetadata(node); + if (entityName && isEntityName(entityName)) { + markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); } } From 0cfb4431bb8b988cf50f6d24fc4856c7f874d82b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 10:33:43 -0700 Subject: [PATCH 03/14] Remove one checkExpression that we probably dont need --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0b7ba79d83361..54e8b55c35b16 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29379,7 +29379,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isInternalModuleImportEqualsDeclaration(node)) { if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { // import foo = - checkExpressionCached(node.moduleReference as Expression); + const left = getFirstIdentifier(node.moduleReference as EntityNameExpression); + markIdentifierAliasReferenced(left); } } } From e466071585fbf5d6b4d1fb1d9f660aa59b7bf23c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 10:59:24 -0700 Subject: [PATCH 04/14] Accept better baseline from reduced expression check, defer/reuse prop expression check more maximally --- src/compiler/checker.ts | 36 ++++++++++++------- .../reference/moduleImport.errors.txt | 5 +-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 54e8b55c35b16..c5ab9e690cbdb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29139,7 +29139,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); } - function markLinkedReferences(location: Node) { + /** + * @param location The location to mark js import refernces for + * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. + * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided. + */ + function markLinkedReferences(location: Node, propSymbol?: Symbol, parentType?: Type) { if (!canCollectSymbolAliasAccessabilityData) { return; } @@ -29161,12 +29166,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { topProp = topProp.parent; } const left = isPropertyAccessExpression(location) ? location.expression : location.left; - if (isThisIdentifier(left)) { + if (isThisIdentifier(left) || !isIdentifier(left)) { return; } const right = isPropertyAccessExpression(location) ? location.name : location.right; const assignmentKind = getAssignmentTargetKind(location); - const leftType = checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state + const leftType = parentType || checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); const parentSymbol = getNodeLinks(left).resolvedSymbol; if (!parentSymbol || parentSymbol === unknownSymbol) { @@ -29176,9 +29181,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { markAliasReferenced(parentSymbol, location); return; } - const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); - const prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) : getPropertyOfType(apparentType, right.escapedText); - const node = location; // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined // here even if `Foo` is not a const enum. @@ -29186,14 +29188,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // The exceptions are: // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. + // + // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking + // pulling on types/symbols it doesn't strictly need to. + if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { + markAliasReferenced(parentSymbol, location); + return; + } + let prop = propSymbol; + if (!prop) { + const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); + } if ( - isIdentifier(left) && parentSymbol && ( - getIsolatedModules(compilerOptions) || - !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node) - ) + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) ) { - markAliasReferenced(parentSymbol, node); + markAliasReferenced(parentSymbol, location); } return; } @@ -33297,7 +33307,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); } - markLinkedReferences(node); + markLinkedReferences(node, prop, leftType); let propType: Type; if (!prop) { diff --git a/tests/baselines/reference/moduleImport.errors.txt b/tests/baselines/reference/moduleImport.errors.txt index d45bb40278fa4..585eb6dfe19d6 100644 --- a/tests/baselines/reference/moduleImport.errors.txt +++ b/tests/baselines/reference/moduleImport.errors.txt @@ -1,13 +1,10 @@ -moduleImport.ts(2,17): error TS2339: Property 'Y' does not exist on type 'typeof X'. moduleImport.ts(2,17): error TS2694: Namespace 'X' has no exported member 'Y'. -==== moduleImport.ts (2 errors) ==== +==== moduleImport.ts (1 errors) ==== module A.B.C { import XYZ = X.Y.Z; ~ -!!! error TS2339: Property 'Y' does not exist on type 'typeof X'. - ~ !!! error TS2694: Namespace 'X' has no exported member 'Y'. export function ping(x: number) { if (x>0) XYZ.pong (x-1); From 2fc387517e38149164f2fe27b7df0d4f11f7d79e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 11:07:50 -0700 Subject: [PATCH 05/14] update comment --- src/compiler/checker.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c5ab9e690cbdb..a02b69bcc96bd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29140,9 +29140,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } /** + * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). + * (This corresponds to not getting elided in JS emit.) + * It can be called on *most* nodes in the AST and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, + * unless you *really* want to *definitely* mark those as referenced. + * These shouldn't be directly marked, and should only get marked transitively by the internals of this function. + * * @param location The location to mark js import refernces for * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. - * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided. + * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly). */ function markLinkedReferences(location: Node, propSymbol?: Symbol, parentType?: Type) { if (!canCollectSymbolAliasAccessabilityData) { From 143fabe85a885af22db77f25508bebc947faa51a Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 15:22:02 -0700 Subject: [PATCH 06/14] Hint the marking process so deep in the compiler context checks neednt be repeated --- src/compiler/checker.ts | 314 +++++++++++++++++++++++++--------------- 1 file changed, 199 insertions(+), 115 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7c9e93b922b60..024d4007e30ec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29139,137 +29139,227 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); } + enum ReferenceHint { + Unspecified, + Identifier, + Property, + ExportAssignment, + Jsx, + AsyncFunction, + ExportImportEquals, + ExportSpecifier, + Decorator, + } + /** * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). * (This corresponds to not getting elided in JS emit.) - * It can be called on *most* nodes in the AST and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, + * It can be called on *most* nodes in the AST with `ReferenceHint.Unspecified` and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, * unless you *really* want to *definitely* mark those as referenced. * These shouldn't be directly marked, and should only get marked transitively by the internals of this function. * * @param location The location to mark js import refernces for + * @param hint The kind of reference `location` has already been checked to be * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly). */ - function markLinkedReferences(location: Node, propSymbol?: Symbol, parentType?: Type) { + function markLinkedReferences(location: PropertyAccessExpression | QualifiedName, hint: ReferenceHint.Property, propSymbol: Symbol | undefined, parentType: Type): void; + function markLinkedReferences(location: Identifier, hint: ReferenceHint.Identifier): void; + function markLinkedReferences(location: ExportAssignment, hint: ReferenceHint.ExportAssignment): void; + function markLinkedReferences(location: JsxOpeningLikeElement | JsxOpeningFragment, hint: ReferenceHint.Jsx): void; + function markLinkedReferences(location: FunctionLikeDeclaration | MethodSignature, hint: ReferenceHint.AsyncFunction): void; + function markLinkedReferences(location: ImportEqualsDeclaration, hint: ReferenceHint.ExportImportEquals): void; + function markLinkedReferences(location: ExportSpecifier, hint: ReferenceHint.ExportSpecifier): void; + function markLinkedReferences(location: HasDecorators, hint: ReferenceHint.Decorator): void; + function markLinkedReferences(location: Node, hint: ReferenceHint.Unspecified, propSymbol?: Symbol, parentType?: Type): void; + function markLinkedReferences(location: Node, hint: ReferenceHint, propSymbol?: Symbol, parentType?: Type) { if (!canCollectSymbolAliasAccessabilityData) { return; } - // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used - // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` - // This is the exception, rather than the rule - most non-expression identifiers are declaration names. - if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { - if (isPropertyAccessOrQualifiedName(location.parent)) { - const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; - if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) + switch (hint) { + case ReferenceHint.Identifier: + return markIdentifierAliasReferenced(location as Identifier); + case ReferenceHint.Property: + return markPropertyAliasReferenced(location as PropertyAccessExpression | QualifiedName, propSymbol, parentType); + case ReferenceHint.ExportAssignment: + return markExportAssignmentAliasReferenced(location as ExportAssignment); + case ReferenceHint.Jsx: + return markJsxAliasReferenced(location as JsxOpeningLikeElement | JsxOpeningFragment); + case ReferenceHint.AsyncFunction: + return markAsyncFunctionAliasReferenced(location as FunctionLikeDeclaration | MethodSignature); + case ReferenceHint.ExportImportEquals: + return markImportEqualsAliasReferenced(location as ImportEqualsDeclaration); + case ReferenceHint.ExportSpecifier: + return markExportSpecifierAliasReferenced(location as ExportSpecifier); + case ReferenceHint.Decorator: + return markDecoratorAliasReferenced(location as HasDecorators); + case ReferenceHint.Unspecified: { + // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used + // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` + // This is the exception, rather than the rule - most non-expression identifiers are declaration names. + if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { + if (isPropertyAccessOrQualifiedName(location.parent)) { + const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; + if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) + } + markIdentifierAliasReferenced(location); + return; + } + if (isPropertyAccessOrQualifiedName(location)) { + let topProp: Node | undefined = location; + while (isPropertyAccessOrQualifiedName(topProp)) { + if (isPartOfTypeNode(topProp)) return; + topProp = topProp.parent; + } + return markPropertyAliasReferenced(location); + } + if (isExportAssignment(location)) { + return markExportAssignmentAliasReferenced(location); + } + if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { + return markJsxAliasReferenced(location); + } + if (isFunctionLikeDeclaration(location) || isMethodSignature(location)) { + return markAsyncFunctionAliasReferenced(location); + } + if (isImportEqualsDeclaration(location)) { + if (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location)) { + return markImportEqualsAliasReferenced(location); + } + return; + } + if (isExportSpecifier(location)) { + return markExportSpecifierAliasReferenced(location); + } + if (!compilerOptions.emitDecoratorMetadata) { + return; + } + if (!canHaveDecorators(location) || !hasDecorators(location) || !location.modifiers || !nodeCanBeDecorated(legacyDecorators, location, location.parent, location.parent.parent)) { + return; + } + + return markDecoratorAliasReferenced(location); } - markIdentifierAliasReferenced(location); + default: + Debug.assertNever(hint, `Unhandled reference hint: ${hint}`); + } + } + + function markIdentifierAliasReferenced(location: Identifier) { + const symbol = getResolvedSymbol(location); + if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { + markAliasReferenced(symbol, location); + } + } + + function markPropertyAliasReferenced(location: PropertyAccessExpression | QualifiedName, propSymbol?: Symbol, parentType?: Type) { + const left = isPropertyAccessExpression(location) ? location.expression : location.left; + if (isThisIdentifier(left) || !isIdentifier(left)) { return; } - if (isPropertyAccessOrQualifiedName(location)) { - let topProp: Node | undefined = location; - while (isPropertyAccessOrQualifiedName(topProp)) { - if (isPartOfTypeNode(topProp)) return; - topProp = topProp.parent; - } - const left = isPropertyAccessExpression(location) ? location.expression : location.left; - if (isThisIdentifier(left) || !isIdentifier(left)) { - return; - } - const right = isPropertyAccessExpression(location) ? location.name : location.right; - const assignmentKind = getAssignmentTargetKind(location); - const leftType = parentType || checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state - const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); - const parentSymbol = getNodeLinks(left).resolvedSymbol; - if (!parentSymbol || parentSymbol === unknownSymbol) { - return; - } - if (isTypeAny(leftType) || leftType === silentNeverType) { - markAliasReferenced(parentSymbol, location); - return; - } - // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. - // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined - // here even if `Foo` is not a const enum. - // - // The exceptions are: - // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and - // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. - // - // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking - // pulling on types/symbols it doesn't strictly need to. - if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { - markAliasReferenced(parentSymbol, location); - return; - } - let prop = propSymbol; - if (!prop) { - const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); - prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); - } - if ( - !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) - ) { - markAliasReferenced(parentSymbol, location); - } + const right = isPropertyAccessExpression(location) ? location.name : location.right; + const assignmentKind = getAssignmentTargetKind(location); + const leftType = parentType || checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); + const parentSymbol = getNodeLinks(left).resolvedSymbol; + if (!parentSymbol || parentSymbol === unknownSymbol) { + return; + } + if (isTypeAny(leftType) || leftType === silentNeverType) { + markAliasReferenced(parentSymbol, location); return; } - if (isExportAssignment(location) && isIdentifier(location.expression)) { + // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. + // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined + // here even if `Foo` is not a const enum. + // + // The exceptions are: + // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and + // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. + // + // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking + // pulling on types/symbols it doesn't strictly need to. + if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { + markAliasReferenced(parentSymbol, location); + return; + } + let prop = propSymbol; + if (!prop && !parentType) { + const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); + } + if ( + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) + ) { + markAliasReferenced(parentSymbol, location); + } + return; + } + + function markExportAssignmentAliasReferenced(location: ExportAssignment) { + if (isIdentifier(location.expression)) { const id = location.expression; const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location)); if (sym) { markAliasReferenced(sym, id); } - return; } - if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { - const node = location; - if (!getJsxNamespaceContainerForImplicitImport(node)) { - // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. - // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. - const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; - const jsxFactoryNamespace = getJsxNamespace(node); - const jsxFactoryLocation = isJsxOpeningLikeElement(node) ? node.tagName : node; + } - // allow null as jsxFragmentFactory - let jsxFactorySym: Symbol | undefined; - if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { - jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); - } + function markJsxAliasReferenced(node: JsxOpeningLikeElement | JsxOpeningFragment) { + if (!getJsxNamespaceContainerForImplicitImport(node)) { + // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. + // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. + const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; + const jsxFactoryNamespace = getJsxNamespace(node); + const jsxFactoryLocation = isJsxOpeningLikeElement(node) ? node.tagName : node; + + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); + } - if (jsxFactorySym) { - // Mark local symbol as referenced here because it might not have been marked - // if jsx emit was not jsxFactory as there wont be error being emitted - jsxFactorySym.isReferenced = SymbolFlags.All; + if (jsxFactorySym) { + // Mark local symbol as referenced here because it might not have been marked + // if jsx emit was not jsxFactory as there wont be error being emitted + jsxFactorySym.isReferenced = SymbolFlags.All; - // If react/jsxFactory symbol is alias, mark it as refereced - if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { - markAliasSymbolAsReferenced(jsxFactorySym); - } + // If react/jsxFactory symbol is alias, mark it as refereced + if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); } + } - // For JsxFragment, mark jsx pragma as referenced via resolveName - if (isJsxOpeningFragment(node)) { - const file = getSourceFileOfNode(node); - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); - } + // For JsxFragment, mark jsx pragma as referenced via resolveName + if (isJsxOpeningFragment(node)) { + const file = getSourceFileOfNode(node); + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); } } - return; } - if (languageVersion < ScriptTarget.ES2015 && (isFunctionLikeDeclaration(location) || isMethodSignature(location))) { + return; + } + + function markAsyncFunctionAliasReferenced(location: FunctionLikeDeclaration | MethodSignature) { + if (languageVersion < ScriptTarget.ES2015) { if (getFunctionFlags(location) & FunctionFlags.Async) { const returnTypeNode = getEffectiveReturnTypeNode(location); markTypeNodeAsReferenced(returnTypeNode); } - // functions are maybe-decoratable and may need their argument type nodes marked for decorator metadata } - if (isImportEqualsDeclaration(location) && hasSyntacticModifier(location, ModifierFlags.Export) && (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location))) { + } + + function markImportEqualsAliasReferenced(location: ImportEqualsDeclaration) { + if (hasSyntacticModifier(location, ModifierFlags.Export)) { markExportAsReferenced(location); - return; } - if (isExportSpecifier(location) && !location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) { + } + + function markExportSpecifierAliasReferenced(location: ExportSpecifier) { + if (!location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) { const exportedName = location.propertyName || location.name; const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { @@ -29284,12 +29374,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return; } - if (compilerOptions.emitDecoratorMetadata) { - const node = location; - if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) { - return; - } + } + function markDecoratorAliasReferenced(node: HasDecorators) { + if (compilerOptions.emitDecoratorMetadata) { const firstDecorator = find(node.modifiers, isDecorator); if (!firstDecorator) { return; @@ -29336,14 +29424,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { break; } } - return; - } - - function markIdentifierAliasReferenced(location: Identifier) { - const symbol = getResolvedSymbol(location); - if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { - markAliasReferenced(symbol, location); - } } function markAliasReferenced(symbol: Symbol, location: Node) { @@ -29589,7 +29669,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getTypeOfSymbol(symbol); } - markLinkedReferences(node); + if (shouldMarkIdentifierAliasReferenced(node)) { + markLinkedReferences(node, ReferenceHint.Identifier); + } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node); @@ -32718,7 +32800,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkJsxPreconditions(node); - markLinkedReferences(node); + markLinkedReferences(node, ReferenceHint.Jsx); if (isNodeOpeningLikeElement) { const jsxOpeningLikeNode = node; @@ -33295,13 +33377,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else { if (isAnyLike) { if (isIdentifier(left) && parentSymbol) { - markLinkedReferences(node); + markLinkedReferences(node, ReferenceHint.Property, /*propSymbol*/ undefined, leftType); } return isErrorType(apparentType) ? errorType : apparentType; } prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); } - markLinkedReferences(node, prop, leftType); + markLinkedReferences(node, ReferenceHint.Property, prop, leftType); let propType: Type; if (!prop) { @@ -41830,8 +41912,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // then(...): Promise; // } // - // Always mark the type node as referenced if it points to a value - markLinkedReferences(node); const returnType = getTypeFromTypeNode(returnTypeNode); if (languageVersion >= ScriptTarget.ES2015) { if (isErrorType(returnType)) { @@ -41846,6 +41926,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { + // Always mark the type node as referenced if it points to a value + markLinkedReferences(node, ReferenceHint.AsyncFunction); if (isErrorType(returnType)) { return; } @@ -42171,7 +42253,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - markLinkedReferences(node); + markLinkedReferences(node, ReferenceHint.Decorator); for (const modifier of node.modifiers) { if (isDecorator(modifier)) { @@ -46503,7 +46585,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkGrammarModifiers(node); if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { checkImportBinding(node); - markLinkedReferences(node); + markLinkedReferences(node, ReferenceHint.ExportImportEquals); if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { const target = resolveAlias(getSymbolOfDeclaration(node)); if (target !== unknownSymbol) { @@ -46604,7 +46686,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getEmitDeclarations(compilerOptions)) { collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); } - markLinkedReferences(node); if (!node.parent.parent.moduleSpecifier) { const exportedName = node.propertyName || node.name; // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) @@ -46612,6 +46693,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } + else { + markLinkedReferences(node, ReferenceHint.ExportSpecifier); + } } else { if ( @@ -46662,8 +46746,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (node.expression.kind === SyntaxKind.Identifier) { const id = node.expression as Identifier; const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node)); - markLinkedReferences(node); if (sym) { + markLinkedReferences(node, ReferenceHint.ExportAssignment); const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value); // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) if (getSymbolFlags(sym) & SymbolFlags.Value) { From 80aca4c303cd51536cfc0f67cd9966da45359273 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 15:22:40 -0700 Subject: [PATCH 07/14] With hinting and the optional arg, this is probably cachable --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 024d4007e30ec..81c5c6bdc082f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29259,7 +29259,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const right = isPropertyAccessExpression(location) ? location.name : location.right; const assignmentKind = getAssignmentTargetKind(location); - const leftType = parentType || checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state + const leftType = parentType || checkExpressionCached(left); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); const parentSymbol = getNodeLinks(left).resolvedSymbol; if (!parentSymbol || parentSymbol === unknownSymbol) { From e0b2467ec3c91a97b62b637fe80d99c1d23a6bab Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 15:25:00 -0700 Subject: [PATCH 08/14] Reorder a bit --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 81c5c6bdc082f..27f78d913ec13 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29258,9 +29258,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } const right = isPropertyAccessExpression(location) ? location.name : location.right; - const assignmentKind = getAssignmentTargetKind(location); const leftType = parentType || checkExpressionCached(left); - const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); const parentSymbol = getNodeLinks(left).resolvedSymbol; if (!parentSymbol || parentSymbol === unknownSymbol) { return; @@ -29286,6 +29284,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let prop = propSymbol; if (!prop && !parentType) { const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + const assignmentKind = getAssignmentTargetKind(location); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); } if ( From 87f565981dd4c55876a0ea22cda2da3a1f59e508 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 15:25:41 -0700 Subject: [PATCH 09/14] And this one --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 27f78d913ec13..efa3fdf6de56c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29257,7 +29257,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isThisIdentifier(left) || !isIdentifier(left)) { return; } - const right = isPropertyAccessExpression(location) ? location.name : location.right; const leftType = parentType || checkExpressionCached(left); const parentSymbol = getNodeLinks(left).resolvedSymbol; if (!parentSymbol || parentSymbol === unknownSymbol) { @@ -29283,6 +29282,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } let prop = propSymbol; if (!prop && !parentType) { + const right = isPropertyAccessExpression(location) ? location.name : location.right; const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); const assignmentKind = getAssignmentTargetKind(location); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); From 94317f43e5240c953820877536f7975a29743496 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 15:28:00 -0700 Subject: [PATCH 10/14] Use getResolvedSymbol since we know left is an Identifier --- src/compiler/checker.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index efa3fdf6de56c..e20d30bd4fa49 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29257,15 +29257,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isThisIdentifier(left) || !isIdentifier(left)) { return; } - const leftType = parentType || checkExpressionCached(left); - const parentSymbol = getNodeLinks(left).resolvedSymbol; + const parentSymbol = getResolvedSymbol(left); if (!parentSymbol || parentSymbol === unknownSymbol) { return; } - if (isTypeAny(leftType) || leftType === silentNeverType) { - markAliasReferenced(parentSymbol, location); - return; - } // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined // here even if `Foo` is not a const enum. @@ -29280,6 +29275,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { markAliasReferenced(parentSymbol, location); return; } + // Hereafter, this relies on type checking - but every check prior to this only used symbol information + const leftType = parentType || checkExpressionCached(left); + if (isTypeAny(leftType) || leftType === silentNeverType) { + markAliasReferenced(parentSymbol, location); + return; + } let prop = propSymbol; if (!prop && !parentType) { const right = isPropertyAccessExpression(location) ? location.name : location.right; From a0553b02f111e64100776350e70790be72027244 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 30 Apr 2024 15:41:21 -0700 Subject: [PATCH 11/14] const and hoist --- src/compiler/checker.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e20d30bd4fa49..ab446bf7d55c9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1112,6 +1112,18 @@ import * as performance from "./_namespaces/ts.performance"; const ambientModuleSymbolRegex = /^".+"$/; const anon = "(anonymous)" as __String & string; +const enum ReferenceHint { + Unspecified, + Identifier, + Property, + ExportAssignment, + Jsx, + AsyncFunction, + ExportImportEquals, + ExportSpecifier, + Decorator, +} + let nextSymbolId = 1; let nextNodeId = 1; let nextMergeId = 1; @@ -29139,18 +29151,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); } - enum ReferenceHint { - Unspecified, - Identifier, - Property, - ExportAssignment, - Jsx, - AsyncFunction, - ExportImportEquals, - ExportSpecifier, - Decorator, - } - /** * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). * (This corresponds to not getting elided in JS emit.) From 331d1f32fda414ae99dd7becd4989eab16335c7a Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 1 May 2024 12:57:51 -0700 Subject: [PATCH 12/14] Add early bail on Ambient node flag --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ab446bf7d55c9..21470e0377258 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29176,6 +29176,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!canCollectSymbolAliasAccessabilityData) { return; } + if (location.flags & NodeFlags.Ambient) { + return; // References within types and declaration files are never going to contribute to retaining a JS import + } switch (hint) { case ReferenceHint.Identifier: return markIdentifierAliasReferenced(location as Identifier); From 25bca5a03b22e411aba17ce9ee5af9393a0eb539 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 1 May 2024 13:18:16 -0700 Subject: [PATCH 13/14] Removal of another error from avoiding duplicate work we dont need --- tests/baselines/reference/reexportedMissingAlias.errors.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/baselines/reference/reexportedMissingAlias.errors.txt b/tests/baselines/reference/reexportedMissingAlias.errors.txt index 4f74ea345dc92..19a81f5a6d333 100644 --- a/tests/baselines/reference/reexportedMissingAlias.errors.txt +++ b/tests/baselines/reference/reexportedMissingAlias.errors.txt @@ -1,12 +1,9 @@ -second.d.ts(1,27): error TS2304: Cannot find name 'CompletelyMissing'. second.d.ts(1,27): error TS2503: Cannot find namespace 'CompletelyMissing'. -==== second.d.ts (2 errors) ==== +==== second.d.ts (1 errors) ==== export import Component = CompletelyMissing; ~~~~~~~~~~~~~~~~~ -!!! error TS2304: Cannot find name 'CompletelyMissing'. - ~~~~~~~~~~~~~~~~~ !!! error TS2503: Cannot find namespace 'CompletelyMissing'. ==== first.d.ts (0 errors) ==== import * as Second from './second'; From 380ddf6f7a731bce2de9a363c8cd5441384abe5e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 1 May 2024 13:57:27 -0700 Subject: [PATCH 14/14] Another one --- .../reference/importDeclWithDeclareModifier.errors.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/baselines/reference/importDeclWithDeclareModifier.errors.txt b/tests/baselines/reference/importDeclWithDeclareModifier.errors.txt index dd74b7c7ff305..eb9308554d6c8 100644 --- a/tests/baselines/reference/importDeclWithDeclareModifier.errors.txt +++ b/tests/baselines/reference/importDeclWithDeclareModifier.errors.txt @@ -1,9 +1,8 @@ importDeclWithDeclareModifier.ts(5,9): error TS1029: 'export' modifier must precede 'declare' modifier. -importDeclWithDeclareModifier.ts(5,27): error TS2708: Cannot use namespace 'x' as a value. importDeclWithDeclareModifier.ts(5,29): error TS2694: Namespace 'x' has no exported member 'c'. -==== importDeclWithDeclareModifier.ts (3 errors) ==== +==== importDeclWithDeclareModifier.ts (2 errors) ==== module x { interface c { } @@ -11,8 +10,6 @@ importDeclWithDeclareModifier.ts(5,29): error TS2694: Namespace 'x' has no expor declare export import a = x.c; ~~~~~~ !!! error TS1029: 'export' modifier must precede 'declare' modifier. - ~ -!!! error TS2708: Cannot use namespace 'x' as a value. ~ !!! error TS2694: Namespace 'x' has no exported member 'c'. var b: a;