diff --git a/src/ast.ts b/src/ast.ts index 87a9f09ff3..da2271ef3d 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -834,6 +834,9 @@ export abstract class TypeNode extends Node { super(kind, range); } + /** Whether this type node is currently in the process of being resolved. */ + currentlyResolving: bool = false; + /** Tests if this type has a generic component matching one of the given type parameters. */ hasGenericComponent(typeParameterNodes: TypeParameterNode[]): bool { if (this.kind == NodeKind.NamedType) { diff --git a/src/parser.ts b/src/parser.ts index a50351448e..3bcee57681 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3519,40 +3519,6 @@ export class Parser extends DiagnosticEmitter { return null; } - private getRecursiveDepthForTypeDeclaration( - identifierName: string, - type: TypeNode, - depth: i32 = 0 - ): i32 { - switch (type.kind) { - case NodeKind.NamedType: { - let typeArguments = (type).typeArguments; - if (typeArguments) { - for (let i = 0, k = typeArguments.length; i < k; i++) { - let res = this.getRecursiveDepthForTypeDeclaration(identifierName, typeArguments[i], depth + 1); - if (res != -1) return res; - } - } - if ((type).name.identifier.text == identifierName) { - return depth; - } - break; - } - case NodeKind.FunctionType: { - let fnType = type; - let res = this.getRecursiveDepthForTypeDeclaration(identifierName, fnType.returnType, depth + 1); - if (res != -1) return res; - let params = fnType.parameters; - for (let i = 0, k = params.length; i < k; i++) { - res = this.getRecursiveDepthForTypeDeclaration(identifierName, params[i].type, depth + 1); - if (res != -1) return res; - } - break; - } - } - return -1; - } - parseTypeDeclaration( tn: Tokenizer, flags: CommonFlags, @@ -3574,19 +3540,11 @@ export class Parser extends DiagnosticEmitter { tn.skip(Token.Bar); let type = this.parseType(tn); if (!type) return null; - let depth = this.getRecursiveDepthForTypeDeclaration(name.text, type); - if (depth >= 0) { - if (depth == 0) { - this.error( - DiagnosticCode.Type_alias_0_circularly_references_itself, - tn.range(), name.text - ); - } else { - this.error( - DiagnosticCode.Not_implemented_0, - tn.range(), "Recursion in type aliases" - ); - } + if (isCircularTypeAlias(name.text, type)) { + this.error( + DiagnosticCode.Type_alias_0_circularly_references_itself, + name.range, name.text + ); return null; } let ret = Node.createTypeDeclaration( @@ -4593,3 +4551,32 @@ function determinePrecedence(kind: Token): Precedence { } return Precedence.None; } + +/** Checks if the type alias of the given name and type is circular. */ +function isCircularTypeAlias(name: string, type: TypeNode): bool { + switch (type.kind) { + case NodeKind.NamedType: { + if ((type).name.identifier.text == name) { + return true; + } + let typeArguments = (type).typeArguments; + if (typeArguments) { + for (let i = 0, k = typeArguments.length; i < k; i++) { + if (isCircularTypeAlias(name, typeArguments[i])) return true; + } + } + break; + } + case NodeKind.FunctionType: { + let functionType = type; + if (isCircularTypeAlias(name, functionType.returnType)) return true; + let parameters = functionType.parameters; + for (let i = 0, k = parameters.length; i < k; i++) { + if (isCircularTypeAlias(name, parameters[i].type)) return true; + } + break; + } + default: assert(false); + } + return false; +} diff --git a/src/resolver.ts b/src/resolver.ts index 243041c75f..c54f555993 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -149,26 +149,38 @@ export class Resolver extends DiagnosticEmitter { /** How to proceed with eventual diagnostics. */ reportMode: ReportMode = ReportMode.Report ): Type | null { + if (node.currentlyResolving) { + this.error( + DiagnosticCode.Not_implemented_0, + node.range, "Recursive types" + ); + return null; + } + node.currentlyResolving = true; + let resolved: Type | null = null; switch (node.kind) { case NodeKind.NamedType: { - return this.resolveNamedType( + resolved = this.resolveNamedType( node, ctxElement, ctxTypes, reportMode ); + break; } case NodeKind.FunctionType: { - return this.resolveFunctionType( + resolved = this.resolveFunctionType( node, ctxElement, ctxTypes, reportMode ); + break; } default: assert(false); } - return null; + node.currentlyResolving = false; + return resolved; } /** Resolves a {@link NamedTypeNode} to a concrete {@link Type}. */ @@ -2721,7 +2733,7 @@ export class Resolver extends DiagnosticEmitter { const declaration = node.declaration; const signature = declaration.signature; const body = declaration.body; - let functionType = this.resolveFunctionType(signature, ctxFlow.sourceFunction, ctxFlow.contextualTypeArguments, reportMode); + let functionType = this.resolveType(signature, ctxFlow.sourceFunction, ctxFlow.contextualTypeArguments, reportMode); if ( functionType && declaration.arrowKind != ArrowKind.None && diff --git a/tests/compiler/typerecursion.json b/tests/compiler/typerecursion.json new file mode 100644 index 0000000000..947d847ebb --- /dev/null +++ b/tests/compiler/typerecursion.json @@ -0,0 +1,6 @@ +{ + "stderr": [ + "AS100: Not implemented: Recursive types", + "EOF" + ] +} \ No newline at end of file diff --git a/tests/compiler/typerecursion.ts b/tests/compiler/typerecursion.ts new file mode 100644 index 0000000000..d4ad3579ba --- /dev/null +++ b/tests/compiler/typerecursion.ts @@ -0,0 +1,6 @@ +type RecMethod = () => RecReturn; +type RecReturn = RecMethod | null; + +const test: RecMethod = () => null; + +ERROR("EOF"); diff --git a/tests/parser/type.ts.fixture.ts b/tests/parser/type.ts.fixture.ts index 01f7734e20..6ae9202e32 100644 --- a/tests/parser/type.ts.fixture.ts +++ b/tests/parser/type.ts.fixture.ts @@ -5,11 +5,11 @@ export type T1 = int32_t; export type T2 = int32_t; export type T11 = T1 | null; export type T12 = T1 | null; -// ERROR 2456: "Type alias 'T3' circularly references itself." in type.ts(11,23+4) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(12,29+3) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(13,24+2) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(14,31+1) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(15,26+1) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(16,39+1) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(17,32+1) -// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(18,25+1) +// ERROR 2456: "Type alias 'T3' circularly references itself." in type.ts(11,13+2) +// ERROR 2456: "Type alias 'T4' circularly references itself." in type.ts(12,13+2) +// ERROR 2456: "Type alias 'T5' circularly references itself." in type.ts(13,13+2) +// ERROR 2456: "Type alias 'T6' circularly references itself." in type.ts(14,13+2) +// ERROR 2456: "Type alias 'T7' circularly references itself." in type.ts(15,13+2) +// ERROR 2456: "Type alias 'T8' circularly references itself." in type.ts(16,13+2) +// ERROR 2456: "Type alias 'T9' circularly references itself." in type.ts(17,13+2) +// ERROR 2456: "Type alias 'T10' circularly references itself." in type.ts(18,13+3)