Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bcfb5e8

Browse files
authored
fix: Detect recursive types early (#2634)
1 parent e06c7bc commit bcfb5e8

File tree

6 files changed

+73
-59
lines changed

6 files changed

+73
-59
lines changed

‎src/ast.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,9 @@ export abstract class TypeNode extends Node {
834834
super(kind, range);
835835
}
836836

837+
/** Whether this type node is currently in the process of being resolved. */
838+
currentlyResolving: bool = false;
839+
837840
/** Tests if this type has a generic component matching one of the given type parameters. */
838841
hasGenericComponent(typeParameterNodes: TypeParameterNode[]): bool {
839842
if (this.kind == NodeKind.NamedType) {

‎src/parser.ts‎

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3519,40 +3519,6 @@ export class Parser extends DiagnosticEmitter {
35193519
return null;
35203520
}
35213521

3522-
private getRecursiveDepthForTypeDeclaration(
3523-
identifierName: string,
3524-
type: TypeNode,
3525-
depth: i32 = 0
3526-
): i32 {
3527-
switch (type.kind) {
3528-
case NodeKind.NamedType: {
3529-
let typeArguments = (<NamedTypeNode>type).typeArguments;
3530-
if (typeArguments) {
3531-
for (let i = 0, k = typeArguments.length; i < k; i++) {
3532-
let res = this.getRecursiveDepthForTypeDeclaration(identifierName, typeArguments[i], depth + 1);
3533-
if (res != -1) return res;
3534-
}
3535-
}
3536-
if ((<NamedTypeNode>type).name.identifier.text == identifierName) {
3537-
return depth;
3538-
}
3539-
break;
3540-
}
3541-
case NodeKind.FunctionType: {
3542-
let fnType = <FunctionTypeNode>type;
3543-
let res = this.getRecursiveDepthForTypeDeclaration(identifierName, fnType.returnType, depth + 1);
3544-
if (res != -1) return res;
3545-
let params = fnType.parameters;
3546-
for (let i = 0, k = params.length; i < k; i++) {
3547-
res = this.getRecursiveDepthForTypeDeclaration(identifierName, params[i].type, depth + 1);
3548-
if (res != -1) return res;
3549-
}
3550-
break;
3551-
}
3552-
}
3553-
return -1;
3554-
}
3555-
35563522
parseTypeDeclaration(
35573523
tn: Tokenizer,
35583524
flags: CommonFlags,
@@ -3574,19 +3540,11 @@ export class Parser extends DiagnosticEmitter {
35743540
tn.skip(Token.Bar);
35753541
let type = this.parseType(tn);
35763542
if (!type) return null;
3577-
let depth = this.getRecursiveDepthForTypeDeclaration(name.text, type);
3578-
if (depth >= 0) {
3579-
if (depth == 0) {
3580-
this.error(
3581-
DiagnosticCode.Type_alias_0_circularly_references_itself,
3582-
tn.range(), name.text
3583-
);
3584-
} else {
3585-
this.error(
3586-
DiagnosticCode.Not_implemented_0,
3587-
tn.range(), "Recursion in type aliases"
3588-
);
3589-
}
3543+
if (isCircularTypeAlias(name.text, type)) {
3544+
this.error(
3545+
DiagnosticCode.Type_alias_0_circularly_references_itself,
3546+
name.range, name.text
3547+
);
35903548
return null;
35913549
}
35923550
let ret = Node.createTypeDeclaration(
@@ -4593,3 +4551,32 @@ function determinePrecedence(kind: Token): Precedence {
45934551
}
45944552
return Precedence.None;
45954553
}
4554+
4555+
/** Checks if the type alias of the given name and type is circular. */
4556+
function isCircularTypeAlias(name: string, type: TypeNode): bool {
4557+
switch (type.kind) {
4558+
case NodeKind.NamedType: {
4559+
if ((<NamedTypeNode>type).name.identifier.text == name) {
4560+
return true;
4561+
}
4562+
let typeArguments = (<NamedTypeNode>type).typeArguments;
4563+
if (typeArguments) {
4564+
for (let i = 0, k = typeArguments.length; i < k; i++) {
4565+
if (isCircularTypeAlias(name, typeArguments[i])) return true;
4566+
}
4567+
}
4568+
break;
4569+
}
4570+
case NodeKind.FunctionType: {
4571+
let functionType = <FunctionTypeNode>type;
4572+
if (isCircularTypeAlias(name, functionType.returnType)) return true;
4573+
let parameters = functionType.parameters;
4574+
for (let i = 0, k = parameters.length; i < k; i++) {
4575+
if (isCircularTypeAlias(name, parameters[i].type)) return true;
4576+
}
4577+
break;
4578+
}
4579+
default: assert(false);
4580+
}
4581+
return false;
4582+
}

‎src/resolver.ts‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,26 +149,38 @@ export class Resolver extends DiagnosticEmitter {
149149
/** How to proceed with eventual diagnostics. */
150150
reportMode: ReportMode = ReportMode.Report
151151
): Type | null {
152+
if (node.currentlyResolving) {
153+
this.error(
154+
DiagnosticCode.Not_implemented_0,
155+
node.range, "Recursive types"
156+
);
157+
return null;
158+
}
159+
node.currentlyResolving = true;
160+
let resolved: Type | null = null;
152161
switch (node.kind) {
153162
case NodeKind.NamedType: {
154-
return this.resolveNamedType(
163+
resolved= this.resolveNamedType(
155164
<NamedTypeNode>node,
156165
ctxElement,
157166
ctxTypes,
158167
reportMode
159168
);
169+
break;
160170
}
161171
case NodeKind.FunctionType: {
162-
return this.resolveFunctionType(
172+
resolved= this.resolveFunctionType(
163173
<FunctionTypeNode>node,
164174
ctxElement,
165175
ctxTypes,
166176
reportMode
167177
);
178+
break;
168179
}
169180
default: assert(false);
170181
}
171-
return null;
182+
node.currentlyResolving = false;
183+
return resolved;
172184
}
173185

174186
/** Resolves a {@link NamedTypeNode} to a concrete {@link Type}. */
@@ -2721,7 +2733,7 @@ export class Resolver extends DiagnosticEmitter {
27212733
const declaration = node.declaration;
27222734
const signature = declaration.signature;
27232735
const body = declaration.body;
2724-
let functionType = this.resolveFunctionType(signature, ctxFlow.sourceFunction, ctxFlow.contextualTypeArguments, reportMode);
2736+
let functionType = this.resolveType(signature, ctxFlow.sourceFunction, ctxFlow.contextualTypeArguments, reportMode);
27252737
if (
27262738
functionType &&
27272739
declaration.arrowKind != ArrowKind.None &&

‎tests/compiler/typerecursion.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"stderr": [
3+
"AS100: Not implemented: Recursive types",
4+
"EOF"
5+
]
6+
}

‎tests/compiler/typerecursion.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
type RecMethod = () => RecReturn;
2+
type RecReturn = RecMethod | null;
3+
4+
const test: RecMethod = () => null;
5+
6+
ERROR("EOF");

‎tests/parser/type.ts.fixture.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ export type T1 = int32_t;
55
export type T2 = int32_t;
66
export type T11 = T1 | null;
77
export type T12 = T1 | null;
8-
// ERROR 2456: "Type alias 'T3' circularly references itself." in type.ts(11,23+4)
9-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(12,29+3)
10-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(13,24+2)
11-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(14,31+1)
12-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(15,26+1)
13-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(16,39+1)
14-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(17,32+1)
15-
// ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(18,25+1)
8+
// ERROR 2456: "Type alias 'T3' circularly references itself." in type.ts(11,13+2)
9+
// ERROR 2456: "Type alias 'T4' circularly references itself." in type.ts(12,13+2)
10+
// ERROR 2456: "Type alias 'T5' circularly references itself." in type.ts(13,13+2)
11+
// ERROR 2456: "Type alias 'T6' circularly references itself." in type.ts(14,13+2)
12+
// ERROR 2456: "Type alias 'T7' circularly references itself." in type.ts(15,13+2)
13+
// ERROR 2456: "Type alias 'T8' circularly references itself." in type.ts(16,13+2)
14+
// ERROR 2456: "Type alias 'T9' circularly references itself." in type.ts(17,13+2)
15+
// ERROR 2456: "Type alias 'T10' circularly references itself." in type.ts(18,13+3)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /