Skip to content

Commit 3800d7b

Browse files
authored
More robust circularity detection in node builder (#24225)
1 parent 08c364d commit 3800d7b

5 files changed

+71
-16
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3060,7 +3060,7 @@ namespace ts {
30603060
flags,
30613061
tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop },
30623062
encounteredError: false,
3063-
symbolStack: undefined,
3063+
visitedSymbols: undefined,
30643064
inferTypeParameters: undefined
30653065
};
30663066
}
@@ -3242,7 +3242,10 @@ namespace ts {
32423242

32433243
function createAnonymousTypeNode(type: ObjectType): TypeNode {
32443244
const symbol = type.symbol;
3245+
let id: string;
32453246
if (symbol) {
3247+
const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class;
3248+
id = (isConstructorObject ? "+" : "") + getSymbolId(symbol);
32463249
if (isJavaScriptConstructor(symbol.valueDeclaration)) {
32473250
// Instance and static types share the same symbol; only add 'typeof' for the static side.
32483251
const isInstanceType = type === getInferredClassType(symbol) ? SymbolFlags.Type : SymbolFlags.Value;
@@ -3254,7 +3257,7 @@ namespace ts {
32543257
shouldWriteTypeOfFunctionSymbol()) {
32553258
return symbolToTypeNode(symbol, context, SymbolFlags.Value);
32563259
}
3257-
else if (contains(context.symbolStack, symbol)) {
3260+
else if (context.visitedSymbols && context.visitedSymbols.has(id)) {
32583261
// If type is an anonymous type literal in a type alias declaration, use type alias name
32593262
const typeAlias = getTypeAliasForTypeLiteral(type);
32603263
if (typeAlias) {
@@ -3268,20 +3271,14 @@ namespace ts {
32683271
else {
32693272
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
32703273
// of types allows us to catch circular references to instantiations of the same anonymous type
3271-
if (!context.symbolStack) {
3272-
context.symbolStack = [];
3274+
if (!context.visitedSymbols) {
3275+
context.visitedSymbols = createMap<true>();
32733276
}
32743277

3275-
const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class;
3276-
if (isConstructorObject) {
3277-
return createTypeNodeFromObjectType(type);
3278-
}
3279-
else {
3280-
context.symbolStack.push(symbol);
3281-
const result = createTypeNodeFromObjectType(type);
3282-
context.symbolStack.pop();
3283-
return result;
3284-
}
3278+
context.visitedSymbols.set(id, true);
3279+
const result = createTypeNodeFromObjectType(type);
3280+
context.visitedSymbols.delete(id);
3281+
return result;
32853282
}
32863283
}
32873284
else {
@@ -3298,7 +3295,7 @@ namespace ts {
32983295
declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock));
32993296
if (isStaticMethodSymbol || isNonLocalFunctionSymbol) {
33003297
// typeof is allowed only for static/non local functions
3301-
return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || contains(context.symbolStack, symbol)) && // it is type of the symbol uses itself recursively
3298+
return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedSymbols && context.visitedSymbols.has(id))) && // it is type of the symbol uses itself recursively
33023299
(!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed
33033300
}
33043301
}
@@ -3997,7 +3994,7 @@ namespace ts {
39973994

39983995
// State
39993996
encounteredError: boolean;
4000-
symbolStack: Symbol[] | undefined;
3997+
visitedSymbols: Map<true> | undefined;
40013998
inferTypeParameters: TypeParameter[] | undefined;
40023999
}
40034000

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [classExpressionInClassStaticDeclarations.ts]
2+
class C {
3+
static D = class extends C {};
4+
}
5+
6+
//// [classExpressionInClassStaticDeclarations.js]
7+
var __extends = (this && this.__extends) || (function () {
8+
var extendStatics = Object.setPrototypeOf ||
9+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
10+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
11+
return function (d, b) {
12+
extendStatics(d, b);
13+
function __() { this.constructor = d; }
14+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15+
};
16+
})();
17+
var C = /** @class */ (function () {
18+
function C() {
19+
}
20+
C.D = /** @class */ (function (_super) {
21+
__extends(class_1, _super);
22+
function class_1() {
23+
return _super !== null && _super.apply(this, arguments) || this;
24+
}
25+
return class_1;
26+
}(C));
27+
return C;
28+
}());
29+
30+
31+
//// [classExpressionInClassStaticDeclarations.d.ts]
32+
declare class C {
33+
static D: {
34+
new (): {};
35+
D: any;
36+
};
37+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
=== tests/cases/compiler/classExpressionInClassStaticDeclarations.ts ===
2+
class C {
3+
>C : Symbol(C, Decl(classExpressionInClassStaticDeclarations.ts, 0, 0))
4+
5+
static D = class extends C {};
6+
>D : Symbol(C.D, Decl(classExpressionInClassStaticDeclarations.ts, 0, 9))
7+
>C : Symbol(C, Decl(classExpressionInClassStaticDeclarations.ts, 0, 0))
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/compiler/classExpressionInClassStaticDeclarations.ts ===
2+
class C {
3+
>C : C
4+
5+
static D = class extends C {};
6+
>D : typeof (Anonymous class)
7+
>class extends C {} : typeof (Anonymous class)
8+
>C : C
9+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// @declaration: true
2+
class C {
3+
static D = class extends C {};
4+
}

0 commit comments

Comments
 (0)