From 4821f81ce765ed804cc9d471c27cd6256c4f0b79 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 31 Jul 2018 13:07:16 -0700 Subject: [PATCH] Refactor and improve caching in getTypeOfSymbol callees (#25842) * Refactor+improve caching in getTypeOfSymbol 1. Always cache calls to getTypeOfSymbol, even in the error case. 2. JS expando types are now cached on the original symbol as well as the cloned symbol. Previously they were only cached on the cloned symbol. 3. Large callees of getTypeOfSymbol (variable/param/property, func/class/enum/module, and accessors) now handle only caching and delegate to -Worker functions whose return values are cached unconditionally (unlike previously). * Fix circularity detection in getTypeOfFuncClassEnumModule Previously, successfully obtaining a type from a js special property declaration would forget to pop the circularity detection stack and check its value. --- src/compiler/checker.ts | 320 ++++++++++++++++++++-------------------- 1 file changed, 161 insertions(+), 159 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 42f553bd9d..5c46f3e7db 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4993,86 +4993,87 @@ namespace ts { function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { const links = getSymbolLinks(symbol); - if (!links.type) { - // Handle prototype property - if (symbol.flags & SymbolFlags.Prototype) { - return links.type = getTypeOfPrototypeProperty(symbol); - } - // CommonsJS require and module both have type any. - if (symbol === requireSymbol) { - return links.type = anyType; - } - if (symbol.flags & SymbolFlags.ModuleExports) { - const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); - const members = createSymbolTable(); - members.set("exports" as __String, fileSymbol); - return links.type = createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined); - } - // Handle catch clause variables - const declaration = symbol.valueDeclaration; - if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { - return links.type = anyType; - } - // Handle export default expressions - if (isSourceFile(declaration)) { - const jsonSourceFile = cast(declaration, isJsonSourceFile); - return links.type = jsonSourceFile.statements.length ? checkExpression(jsonSourceFile.statements[0].expression) : emptyObjectType; - } - if (declaration.kind === SyntaxKind.ExportAssignment) { - return links.type = checkExpression((declaration).expression); - } - // Handle variable, parameter or property - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } - - let type = getJSSpecialType(symbol, declaration); - if (!type) { - if (isJSDocPropertyLikeTag(declaration) - || isPropertyAccessExpression(declaration) - || isIdentifier(declaration) - || isClassDeclaration(declaration) - || isFunctionDeclaration(declaration) - || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) - || isMethodSignature(declaration)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType; - } - else if (isPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); - } - else if (isJsxAttribute(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); - } - else if (isShorthandPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); - } - else if (isObjectLiteralMethod(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); - } - else if (isParameter(declaration) - || isPropertyDeclaration(declaration) - || isPropertySignature(declaration) - || isVariableDeclaration(declaration) - || isBindingElement(declaration)) { - type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); - } - else { - return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol)); - } - } - - if (!popTypeResolution()) { - type = reportCircularityError(symbol); - } - links.type = type; - } - return links.type; + return links.type || (links.type = getTypeOfVariableOrParameterOrPropertyWorker(symbol)); } + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) { + // Handle prototype property + if (symbol.flags & SymbolFlags.Prototype) { + return getTypeOfPrototypeProperty(symbol); + } + // CommonsJS require and module both have type any. + if (symbol === requireSymbol) { + return anyType; + } + if (symbol.flags & SymbolFlags.ModuleExports) { + const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); + const members = createSymbolTable(); + members.set("exports" as __String, fileSymbol); + return createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined); + } + // Handle catch clause variables + const declaration = symbol.valueDeclaration; + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + return anyType; + } + // Handle export default expressions + if (isSourceFile(declaration)) { + const jsonSourceFile = cast(declaration, isJsonSourceFile); + return jsonSourceFile.statements.length ? checkExpression(jsonSourceFile.statements[0].expression) : emptyObjectType; + } + if (declaration.kind === SyntaxKind.ExportAssignment) { + return checkExpression((declaration).expression); + } + + // Handle variable, parameter or property + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + let type = getJSSpecialType(symbol, declaration); + if (!type) { + if (isJSDocPropertyLikeTag(declaration) + || isPropertyAccessExpression(declaration) + || isIdentifier(declaration) + || isClassDeclaration(declaration) + || isFunctionDeclaration(declaration) + || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) + || isMethodSignature(declaration)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); + } + type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType; + } + else if (isPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); + } + else if (isJsxAttribute(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); + } + else if (isShorthandPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); + } + else if (isObjectLiteralMethod(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); + } + else if (isParameter(declaration) + || isPropertyDeclaration(declaration) + || isPropertySignature(declaration) + || isVariableDeclaration(declaration) + || isBindingElement(declaration)) { + type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); + } + else { + return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol)); + } + } + + if (!popTypeResolution()) { + type = reportCircularityError(symbol); + } + return type; + } + function getJSSpecialType(symbol: Symbol, decl: Declaration): Type | undefined { if (!isInJavaScriptFile(decl)) { return undefined; @@ -5148,64 +5149,65 @@ namespace ts { function getTypeOfAccessors(symbol: Symbol): Type { const links = getSymbolLinks(symbol); - if (!links.type) { - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + return links.type || (links.type = getTypeOfAccessorsWorker(symbol)); + } - if (getter && isInJavaScriptFile(getter)) { - const jsDocType = getTypeForDeclarationFromJSDocComment(getter); - if (jsDocType) { - return links.type = jsDocType; - } + function getTypeOfAccessorsWorker(symbol: Symbol): Type { + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + + if (getter && isInJavaScriptFile(getter)) { + const jsDocType = getTypeForDeclarationFromJSDocComment(getter); + if (jsDocType) { + return jsDocType; } + } - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } - let type: Type; + let type: Type; - // First try to see if the user specified a return type on the get-accessor. - const getterReturnType = getAnnotatedAccessorType(getter); - if (getterReturnType) { - type = getterReturnType; + // First try to see if the user specified a return type on the get-accessor. + const getterReturnType = getAnnotatedAccessorType(getter); + if (getterReturnType) { + type = getterReturnType; + } + else { + // If the user didn't specify a return type, try to use the set-accessor's parameter type. + const setterParameterType = getAnnotatedAccessorType(setter); + if (setterParameterType) { + type = setterParameterType; } else { - // If the user didn't specify a return type, try to use the set-accessor's parameter type. - const setterParameterType = getAnnotatedAccessorType(setter); - if (setterParameterType) { - type = setterParameterType; + // If there are no specified types, try to infer it from the body of the get accessor if it exists. + if (getter && getter.body) { + type = getReturnTypeFromBody(getter); } + // Otherwise, fall back to 'any'. else { - // If there are no specified types, try to infer it from the body of the get accessor if it exists. - if (getter && getter.body) { - type = getReturnTypeFromBody(getter); - } - // Otherwise, fall back to 'any'. - else { - if (noImplicitAny) { - if (setter) { - error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); - } - else { - Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function"); - error(getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); - } + if (noImplicitAny) { + if (setter) { + error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); + } + else { + Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function"); + error(getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); } - type = anyType; } + type = anyType; } } - if (!popTypeResolution()) { - type = anyType; - if (noImplicitAny) { - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); - } - } - links.type = type; } - return links.type; + if (!popTypeResolution()) { + type = anyType; + if (noImplicitAny) { + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); + } + } + return type; } function getBaseTypeVariableOfClass(symbol: Symbol) { @@ -5215,6 +5217,7 @@ namespace ts { function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { let links = getSymbolLinks(symbol); + const originalLinks = links; if (!links.type) { const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration); if (jsDeclaration) { @@ -5233,48 +5236,47 @@ namespace ts { } } } - if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { - links.type = anyType; - } - else if (symbol.valueDeclaration.kind === SyntaxKind.BinaryExpression || - symbol.valueDeclaration.kind === SyntaxKind.PropertyAccessExpression && symbol.valueDeclaration.parent.kind === SyntaxKind.BinaryExpression) { - links.type = getWidenedTypeFromJSSpecialPropertyDeclarations(symbol); - } - else { - if (symbol.flags & SymbolFlags.ValueModule && symbol.valueDeclaration && isSourceFile(symbol.valueDeclaration) && symbol.valueDeclaration.commonJsModuleIndicator) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } - const resolvedModule = resolveExternalModuleSymbol(symbol); - if (resolvedModule !== symbol) { - const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); - links.type = getWidenedTypeFromJSSpecialPropertyDeclarations(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); - } - if (!popTypeResolution()) { - links.type = reportCircularityError(symbol); - } - } - if (!links.type) { - const type = createObjectType(ObjectFlags.Anonymous, symbol); - if (symbol.flags & SymbolFlags.Class) { - const baseTypeVariable = getBaseTypeVariableOfClass(symbol); - links.type = baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; - } - else { - links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; - } - } - } + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); } return links.type; } + function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { + const declaration = symbol.valueDeclaration; + if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { + return anyType; + } + else if (declaration.kind === SyntaxKind.BinaryExpression || + declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) { + return getWidenedTypeFromJSSpecialPropertyDeclarations(symbol); + } + else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { + const resolvedModule = resolveExternalModuleSymbol(symbol); + if (resolvedModule !== symbol) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); + const type = getWidenedTypeFromJSSpecialPropertyDeclarations(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); + if (!popTypeResolution()) { + return reportCircularityError(symbol); + } + return type; + } + } + const type = createObjectType(ObjectFlags.Anonymous, symbol); + if (symbol.flags & SymbolFlags.Class) { + const baseTypeVariable = getBaseTypeVariableOfClass(symbol); + return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; + } + else { + return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; + } + } + function getTypeOfEnumMember(symbol: Symbol): Type { const links = getSymbolLinks(symbol); - if (!links.type) { - links.type = getDeclaredTypeOfEnumMember(symbol); - } - return links.type; + return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); } function getTypeOfAlias(symbol: Symbol): Type { @@ -5303,7 +5305,7 @@ namespace ts { } else { if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; + return links.type = errorType; } symbolInstantiationDepth++; let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!);