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.
This commit is contained in:
Nathan Shively-Sanders 2018-07-31 13:07:16 -07:00 committed by GitHub
parent ab5f04721d
commit 4821f81ce7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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((<ExportAssignment>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((<ExportAssignment>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<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
const setter = getDeclarationOfKind<AccessorDeclaration>(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<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
const setter = getDeclarationOfKind<AccessorDeclaration>(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<AccessorDeclaration>(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<AccessorDeclaration>(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!);