Skip to content

Commit 1f641be

Browse files
authored
fix: Disallow type recursion during type declaration (#2331)
1 parent 7d93509 commit 1f641be

File tree

4 files changed

+68
-0
lines changed

4 files changed

+68
-0
lines changed

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
"Variable '{0}' used before its declaration.": 2448,
159159
"Cannot redeclare block-scoped variable '{0}'" : 2451,
160160
"The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.": 2453,
161+
"Type alias '{0}' circularly references itself.": 2456,
161162
"Type '{0}' has no property '{1}'.": 2460,
162163
"The '{0}' operator cannot be applied to type '{1}'.": 2469,
163164
"In 'const' enum declarations member initializer must be constant expression.": 2474,

src/parser.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3478,6 +3478,40 @@ export class Parser extends DiagnosticEmitter {
34783478
return null;
34793479
}
34803480

3481+
private getRecursiveDepthForTypeDeclaration(
3482+
identifierName: string,
3483+
type: TypeNode,
3484+
depth: i32 = 0
3485+
): i32 {
3486+
switch (type.kind) {
3487+
case NodeKind.NAMEDTYPE: {
3488+
let typeArguments = (<NamedTypeNode>type).typeArguments;
3489+
if (typeArguments) {
3490+
for (let i = 0, k = typeArguments.length; i < k; i++) {
3491+
let res = this.getRecursiveDepthForTypeDeclaration(identifierName, typeArguments[i], depth + 1);
3492+
if (res != -1) return res;
3493+
}
3494+
}
3495+
if ((<NamedTypeNode>type).name.identifier.text == identifierName) {
3496+
return depth;
3497+
}
3498+
break;
3499+
}
3500+
case NodeKind.FUNCTIONTYPE: {
3501+
let fnType = <FunctionTypeNode>type;
3502+
let res = this.getRecursiveDepthForTypeDeclaration(identifierName, fnType.returnType, depth + 1);
3503+
if (res != -1) return res;
3504+
let params = fnType.parameters;
3505+
for (let i = 0, k = params.length; i < k; i++) {
3506+
res = this.getRecursiveDepthForTypeDeclaration(identifierName, params[i].type, depth + 1);
3507+
if (res != -1) return res;
3508+
}
3509+
break;
3510+
}
3511+
}
3512+
return -1;
3513+
}
3514+
34813515
parseTypeDeclaration(
34823516
tn: Tokenizer,
34833517
flags: CommonFlags,
@@ -3499,6 +3533,21 @@ export class Parser extends DiagnosticEmitter {
34993533
tn.skip(Token.BAR);
35003534
let type = this.parseType(tn);
35013535
if (!type) return null;
3536+
let depth = this.getRecursiveDepthForTypeDeclaration(name.text, type);
3537+
if (depth >= 0) {
3538+
if (depth == 0) {
3539+
this.error(
3540+
DiagnosticCode.Type_alias_0_circularly_references_itself,
3541+
tn.range(), name.text
3542+
);
3543+
} else {
3544+
this.error(
3545+
DiagnosticCode.Not_implemented_0,
3546+
tn.range(), "Recursion in type aliases"
3547+
);
3548+
}
3549+
return null;
3550+
}
35023551
let ret = Node.createTypeDeclaration(
35033552
name,
35043553
decorators,

tests/parser/type.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,13 @@ export type uint64_t = u64;
66
export type T1 = | int32_t;
77
export type T2 =
88
| int32_t;
9+
10+
// disallow type recursion
11+
export type T3 = T3 | null;
12+
export type T4 = (x: T4) => i32;
13+
export type T5 = () => T5;
14+
export type T6<T> = () => T6<T>;
15+
export type T7 = Array<T7>;
16+
export type T8 = Map<string, Array<T8>>;
17+
export type T9 = Array<() => T9>;
18+
export type T10 = T6<T10>;

tests/parser/type.ts.fixture.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@ type int32_t = i32;
33
export type uint64_t = u64;
44
export type T1 = int32_t;
55
export type T2 = int32_t;
6+
// ERROR 2456: "Type alias 'T3' circularly references itself." in type.ts(11,23+4)
7+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(12,29+3)
8+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(13,24+2)
9+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(14,31+1)
10+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(15,26+1)
11+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(16,39+1)
12+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(17,32+1)
13+
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(18,25+1)

0 commit comments

Comments
 (0)