diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 5c9a03e6f2..a69e5d608f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -158,6 +158,7 @@ "Variable '{0}' used before its declaration.": 2448, "Cannot redeclare block-scoped variable '{0}'" : 2451, "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.": 2453, + "Type alias '{0}' circularly references itself.": 2456, "Type '{0}' has no property '{1}'.": 2460, "The '{0}' operator cannot be applied to type '{1}'.": 2469, "In 'const' enum declarations member initializer must be constant expression.": 2474, diff --git a/src/parser.ts b/src/parser.ts index b9dc2fc7b3..664435260e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3478,6 +3478,40 @@ 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, @@ -3499,6 +3533,21 @@ 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" + ); + } + return null; + } let ret = Node.createTypeDeclaration( name, decorators, diff --git a/tests/parser/type.ts b/tests/parser/type.ts index dc6e5175db..7099da72d8 100644 --- a/tests/parser/type.ts +++ b/tests/parser/type.ts @@ -6,3 +6,13 @@ export type uint64_t = u64; export type T1 = | int32_t; export type T2 = | int32_t; + +// disallow type recursion +export type T3 = T3 | null; +export type T4 = (x: T4) => i32; +export type T5 = () => T5; +export type T6 = () => T6; +export type T7 = Array; +export type T8 = Map>; +export type T9 = Array<() => T9>; +export type T10 = T6; diff --git a/tests/parser/type.ts.fixture.ts b/tests/parser/type.ts.fixture.ts index d58bbc9ce4..c6814cac17 100644 --- a/tests/parser/type.ts.fixture.ts +++ b/tests/parser/type.ts.fixture.ts @@ -3,3 +3,11 @@ type int32_t = i32; export type uint64_t = u64; export type T1 = int32_t; export type T2 = int32_t; +// 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)