-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Error when variable is circularly referenced in type annotation #2991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05f51dc
a0f4478
a133684
8600fef
2792614
d0e5269
7efd93a
4186167
eeb23ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,12 +88,11 @@ module ts { | |
let undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); | ||
let nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null"); | ||
let unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); | ||
let resolvingType = createIntrinsicType(TypeFlags.Any, "__resolving__"); | ||
|
||
let emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); | ||
let anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); | ||
let noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); | ||
|
||
let anySignature = createSignature(undefined, undefined, emptyArray, anyType, 0, false, false); | ||
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, 0, false, false); | ||
|
||
|
@@ -118,14 +117,17 @@ module ts { | |
let getGlobalParameterDecoratorType: () => ObjectType; | ||
let getGlobalPropertyDecoratorType: () => ObjectType; | ||
let getGlobalMethodDecoratorType: () => ObjectType; | ||
|
||
let tupleTypes: Map<TupleType> = {}; | ||
let unionTypes: Map<UnionType> = {}; | ||
let stringLiteralTypes: Map<StringLiteralType> = {}; | ||
let emitExtends = false; | ||
let emitDecorate = false; | ||
let emitParam = false; | ||
|
||
let resolutionTargets: Object[] = []; | ||
let resolutionResults: boolean[] = []; | ||
|
||
let mergedSymbols: Symbol[] = []; | ||
let symbolLinks: SymbolLinks[] = []; | ||
let nodeLinks: NodeLinks[] = []; | ||
|
@@ -1980,7 +1982,7 @@ module ts { | |
} | ||
} | ||
|
||
function collectLinkedAliases(node: Identifier): Node[]{ | ||
function collectLinkedAliases(node: Identifier): Node[] { | ||
var exportSymbol: Symbol; | ||
if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { | ||
exportSymbol = resolveName(node.parent, node.text, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, Diagnostics.Cannot_find_name_0, node); | ||
|
@@ -2014,6 +2016,38 @@ module ts { | |
} | ||
} | ||
|
||
// Push an entry on the type resolution stack. If an entry with the given target is not already on the stack, | ||
// a new entry with that target and an associated result value of true is pushed on the stack, and the value | ||
// true is returned. Otherwise, a circularity has occurred and the result values of the existing entry and | ||
// all entries pushed after it are changed to false, and the value false is returned. The target object provides | ||
// a unique identity for a particular type resolution result: Symbol instances are used to track resolution of | ||
// SymbolLinks.type, SymbolLinks instances are used to track resolution of SymbolLinks.declaredType, and | ||
// Signature instances are used to track resolution of Signature.resolvedReturnType. | ||
function pushTypeResolution(target: Object): boolean { | ||
let i = 0; | ||
let count = resolutionTargets.length; | ||
while (i < count && resolutionTargets[i] !== target) { | ||
i++; | ||
} | ||
if (i < count) { | ||
do { | ||
resolutionResults[i++] = false; | ||
} | ||
while (i < count); | ||
return false; | ||
} | ||
resolutionTargets.push(target); | ||
resolutionResults.push(true); | ||
return true; | ||
} | ||
|
||
// Pop an entry from the type resolution stack and return its associated result value. The result value will | ||
// be true if no circularities were detected, or false if a circularity was found. | ||
function popTypeResolution(): boolean { | ||
resolutionTargets.pop(); | ||
return resolutionResults.pop(); | ||
} | ||
|
||
function getRootDeclaration(node: Node): Node { | ||
while (node.kind === SyntaxKind.BindingElement) { | ||
node = node.parent.parent; | ||
|
@@ -2271,20 +2305,27 @@ module ts { | |
return links.type = checkExpression((<ExportAssignment>declaration).expression); | ||
} | ||
// Handle variable, parameter or property | ||
links.type = resolvingType; | ||
let type = getWidenedTypeForVariableLikeDeclaration(<VariableLikeDeclaration>declaration, /*reportErrors*/ true); | ||
if (links.type === resolvingType) { | ||
links.type = type; | ||
if (!pushTypeResolution(symbol)) { | ||
return unknownType; | ||
} | ||
} | ||
else if (links.type === resolvingType) { | ||
links.type = anyType; | ||
if (compilerOptions.noImplicitAny) { | ||
let diagnostic = (<VariableLikeDeclaration>symbol.valueDeclaration).type ? | ||
Diagnostics._0_implicitly_has_type_any_because_it_is_referenced_directly_or_indirectly_in_its_own_type_annotation : | ||
Diagnostics._0_implicitly_has_type_any_because_it_is_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer; | ||
error(symbol.valueDeclaration, diagnostic, symbolToString(symbol)); | ||
let type = getWidenedTypeForVariableLikeDeclaration(<VariableLikeDeclaration>declaration, /*reportErrors*/ true); | ||
if (!popTypeResolution()) { | ||
if ((<VariableLikeDeclaration>symbol.valueDeclaration).type) { | ||
// Variable has type annotation that circularly references the variable itself | ||
type = unknownType; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for cleaning that up |
||
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, | ||
symbolToString(symbol)); | ||
} | ||
else { | ||
// Variable has initializer that circularly references the variable itself | ||
type = anyType; | ||
if (compilerOptions.noImplicitAny) { | ||
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_is_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, | ||
symbolToString(symbol)); | ||
} | ||
} | ||
} | ||
links.type = type; | ||
} | ||
return links.type; | ||
} | ||
|
@@ -2308,19 +2349,13 @@ module ts { | |
|
||
function getTypeOfAccessors(symbol: Symbol): Type { | ||
let links = getSymbolLinks(symbol); | ||
checkAndStoreTypeOfAccessors(symbol, links); | ||
return links.type; | ||
} | ||
|
||
function checkAndStoreTypeOfAccessors(symbol: Symbol, links?: SymbolLinks) { | ||
links = links || getSymbolLinks(symbol); | ||
if (!links.type) { | ||
links.type = resolvingType; | ||
if (!pushTypeResolution(symbol)) { | ||
return unknownType; | ||
} | ||
let getter = <AccessorDeclaration>getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); | ||
let setter = <AccessorDeclaration>getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); | ||
|
||
let type: Type; | ||
|
||
// First try to see if the user specified a return type on the get-accessor. | ||
let getterReturnType = getAnnotatedAccessorType(getter); | ||
if (getterReturnType) { | ||
|
@@ -2342,23 +2377,20 @@ module ts { | |
if (compilerOptions.noImplicitAny) { | ||
error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_type_annotation, symbolToString(symbol)); | ||
} | ||
|
||
type = anyType; | ||
} | ||
} | ||
} | ||
|
||
if (links.type === resolvingType) { | ||
links.type = type; | ||
} | ||
} | ||
else if (links.type === resolvingType) { | ||
links.type = anyType; | ||
if (compilerOptions.noImplicitAny) { | ||
let getter = <AccessorDeclaration>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)); | ||
if (!popTypeResolution()) { | ||
type = anyType; | ||
if (compilerOptions.noImplicitAny) { | ||
let getter = <AccessorDeclaration>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; | ||
} | ||
|
||
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { | ||
|
@@ -2451,7 +2483,7 @@ module ts { | |
return result; | ||
} | ||
|
||
function getBaseTypes(type: InterfaceType): ObjectType[]{ | ||
function getBaseTypes(type: InterfaceType): ObjectType[] { | ||
let typeWithBaseTypes = <InterfaceTypeWithBaseTypes>type; | ||
if (!typeWithBaseTypes.baseTypes) { | ||
if (type.symbol.flags & SymbolFlags.Class) { | ||
|
@@ -2536,17 +2568,18 @@ module ts { | |
function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { | ||
let links = getSymbolLinks(symbol); | ||
if (!links.declaredType) { | ||
links.declaredType = resolvingType; | ||
// Note that we use the links object as the target here because the symbol object is used as the unique | ||
// identity for resolution of the 'type' property in SymbolLinks. | ||
if (!pushTypeResolution(links)) { | ||
return unknownType; | ||
} | ||
let declaration = <TypeAliasDeclaration>getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); | ||
let type = getTypeFromTypeNode(declaration.type); | ||
if (links.declaredType === resolvingType) { | ||
links.declaredType = type; | ||
if (!popTypeResolution()) { | ||
type = unknownType; | ||
error(declaration.name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); | ||
} | ||
} | ||
else if (links.declaredType === resolvingType) { | ||
links.declaredType = unknownType; | ||
let declaration = <TypeAliasDeclaration>getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); | ||
error(declaration.name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); | ||
links.declaredType = type; | ||
} | ||
return links.declaredType; | ||
} | ||
|
@@ -3150,7 +3183,9 @@ module ts { | |
|
||
function getReturnTypeOfSignature(signature: Signature): Type { | ||
if (!signature.resolvedReturnType) { | ||
signature.resolvedReturnType = resolvingType; | ||
if (!pushTypeResolution(signature)) { | ||
return unknownType; | ||
} | ||
let type: Type; | ||
if (signature.target) { | ||
type = instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper); | ||
|
@@ -3161,21 +3196,19 @@ module ts { | |
else { | ||
type = getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration); | ||
} | ||
if (signature.resolvedReturnType === resolvingType) { | ||
signature.resolvedReturnType = type; | ||
} | ||
} | ||
else if (signature.resolvedReturnType === resolvingType) { | ||
signature.resolvedReturnType = anyType; | ||
if (compilerOptions.noImplicitAny) { | ||
let declaration = <Declaration>signature.declaration; | ||
if (declaration.name) { | ||
error(declaration.name, 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, declarationNameToString(declaration.name)); | ||
} | ||
else { | ||
error(declaration, Diagnostics.Function_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); | ||
if (!popTypeResolution()) { | ||
type = anyType; | ||
if (compilerOptions.noImplicitAny) { | ||
let declaration = <Declaration>signature.declaration; | ||
if (declaration.name) { | ||
error(declaration.name, 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, declarationNameToString(declaration.name)); | ||
} | ||
else { | ||
error(declaration, Diagnostics.Function_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); | ||
} | ||
} | ||
} | ||
signature.resolvedReturnType = type; | ||
} | ||
return signature.resolvedReturnType; | ||
} | ||
|
@@ -7342,10 +7375,9 @@ module ts { | |
if (isContextSensitive(node)) { | ||
assignContextualParameterTypes(signature, contextualSignature, contextualMapper || identityMapper); | ||
} | ||
if (!node.type) { | ||
signature.resolvedReturnType = resolvingType; | ||
if (!node.type && !signature.resolvedReturnType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why doesn't this one have to change to the new model? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is already guarded by the ContextChecked flag and will never be re-entered. |
||
let returnType = getReturnTypeFromBody(node, contextualMapper); | ||
if (signature.resolvedReturnType === resolvingType) { | ||
if (!signature.resolvedReturnType) { | ||
signature.resolvedReturnType = returnType; | ||
} | ||
} | ||
|
@@ -8359,8 +8391,7 @@ module ts { | |
} | ||
} | ||
} | ||
|
||
checkAndStoreTypeOfAccessors(getSymbolOfNode(node)); | ||
getTypeOfAccessors(getSymbolOfNode(node)); | ||
} | ||
|
||
checkFunctionLikeDeclaration(node); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize the target object only matters for identity of the result. But I'd rather have one type that gets pushed onto the stack. I noticed below, the things that get pushed can be of type Symbol, SymbolLinks or Signature. I would really prefer to converge on one type if possible, just to keep things organized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it seems very easy to pass in the same object from two different callers by mistake, even when tracking different properties. I wish we could pass a stronger proxy for the property, rather than passing a different owner object in a way that is unrelated to which property we want to track.