From a556ca8f93b821e01f608d95b4bb0b3b0809b7ad Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 17 May 2018 13:57:54 -0700 Subject: [PATCH] More robust circularity detection in node builder --- src/compiler/checker.ts | 29 +++++++-------- ...lassExpressionInClassStaticDeclarations.js | 37 +++++++++++++++++++ ...xpressionInClassStaticDeclarations.symbols | 8 ++++ ...sExpressionInClassStaticDeclarations.types | 9 +++++ ...lassExpressionInClassStaticDeclarations.ts | 4 ++ 5 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 tests/baselines/reference/classExpressionInClassStaticDeclarations.js create mode 100644 tests/baselines/reference/classExpressionInClassStaticDeclarations.symbols create mode 100644 tests/baselines/reference/classExpressionInClassStaticDeclarations.types create mode 100644 tests/cases/compiler/classExpressionInClassStaticDeclarations.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index af308232eacb5..6edbaf44aef8c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3060,7 +3060,7 @@ namespace ts { flags, tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop }, encounteredError: false, - symbolStack: undefined, + visitedSymbols: undefined, inferTypeParameters: undefined }; } @@ -3242,7 +3242,10 @@ namespace ts { function createAnonymousTypeNode(type: ObjectType): TypeNode { const symbol = type.symbol; + let id: string; if (symbol) { + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + id = (isConstructorObject ? "+" : "") + getSymbolId(symbol); if (isJavaScriptConstructor(symbol.valueDeclaration)) { // Instance and static types share the same symbol; only add 'typeof' for the static side. const isInstanceType = type === getInferredClassType(symbol) ? SymbolFlags.Type : SymbolFlags.Value; @@ -3254,7 +3257,7 @@ namespace ts { shouldWriteTypeOfFunctionSymbol()) { return symbolToTypeNode(symbol, context, SymbolFlags.Value); } - else if (contains(context.symbolStack, symbol)) { + else if (context.visitedSymbols && context.visitedSymbols.has(id)) { // If type is an anonymous type literal in a type alias declaration, use type alias name const typeAlias = getTypeAliasForTypeLiteral(type); if (typeAlias) { @@ -3268,20 +3271,14 @@ namespace ts { else { // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead // of types allows us to catch circular references to instantiations of the same anonymous type - if (!context.symbolStack) { - context.symbolStack = []; + if (!context.visitedSymbols) { + context.visitedSymbols = createMap(); } - const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; - if (isConstructorObject) { - return createTypeNodeFromObjectType(type); - } - else { - context.symbolStack.push(symbol); - const result = createTypeNodeFromObjectType(type); - context.symbolStack.pop(); - return result; - } + context.visitedSymbols.set(id, true); + const result = createTypeNodeFromObjectType(type); + context.visitedSymbols.delete(id); + return result; } } else { @@ -3298,7 +3295,7 @@ namespace ts { declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { // typeof is allowed only for static/non local functions - return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || contains(context.symbolStack, symbol)) && // it is type of the symbol uses itself recursively + return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedSymbols && context.visitedSymbols.has(id))) && // it is type of the symbol uses itself recursively (!(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 } } @@ -3997,7 +3994,7 @@ namespace ts { // State encounteredError: boolean; - symbolStack: Symbol[] | undefined; + visitedSymbols: Map | undefined; inferTypeParameters: TypeParameter[] | undefined; } diff --git a/tests/baselines/reference/classExpressionInClassStaticDeclarations.js b/tests/baselines/reference/classExpressionInClassStaticDeclarations.js new file mode 100644 index 0000000000000..9fbaeef12b020 --- /dev/null +++ b/tests/baselines/reference/classExpressionInClassStaticDeclarations.js @@ -0,0 +1,37 @@ +//// [classExpressionInClassStaticDeclarations.ts] +class C { + static D = class extends C {}; +} + +//// [classExpressionInClassStaticDeclarations.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var C = /** @class */ (function () { + function C() { + } + C.D = /** @class */ (function (_super) { + __extends(class_1, _super); + function class_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + return class_1; + }(C)); + return C; +}()); + + +//// [classExpressionInClassStaticDeclarations.d.ts] +declare class C { + static D: { + new (): {}; + D: any; + }; +} diff --git a/tests/baselines/reference/classExpressionInClassStaticDeclarations.symbols b/tests/baselines/reference/classExpressionInClassStaticDeclarations.symbols new file mode 100644 index 0000000000000..99ca22158d6fb --- /dev/null +++ b/tests/baselines/reference/classExpressionInClassStaticDeclarations.symbols @@ -0,0 +1,8 @@ +=== tests/cases/compiler/classExpressionInClassStaticDeclarations.ts === +class C { +>C : Symbol(C, Decl(classExpressionInClassStaticDeclarations.ts, 0, 0)) + + static D = class extends C {}; +>D : Symbol(C.D, Decl(classExpressionInClassStaticDeclarations.ts, 0, 9)) +>C : Symbol(C, Decl(classExpressionInClassStaticDeclarations.ts, 0, 0)) +} diff --git a/tests/baselines/reference/classExpressionInClassStaticDeclarations.types b/tests/baselines/reference/classExpressionInClassStaticDeclarations.types new file mode 100644 index 0000000000000..da701149b1d74 --- /dev/null +++ b/tests/baselines/reference/classExpressionInClassStaticDeclarations.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/classExpressionInClassStaticDeclarations.ts === +class C { +>C : C + + static D = class extends C {}; +>D : typeof (Anonymous class) +>class extends C {} : typeof (Anonymous class) +>C : C +} diff --git a/tests/cases/compiler/classExpressionInClassStaticDeclarations.ts b/tests/cases/compiler/classExpressionInClassStaticDeclarations.ts new file mode 100644 index 0000000000000..2e442d0af0d56 --- /dev/null +++ b/tests/cases/compiler/classExpressionInClassStaticDeclarations.ts @@ -0,0 +1,4 @@ +// @declaration: true +class C { + static D = class extends C {}; +} \ No newline at end of file