From 1785d87fdae732aaa02652d6f20d4b94d0278a56 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 12:33:55 -0800 Subject: [PATCH] Fix temp variable emit for names used in nested classes --- src/compiler/emitter.ts | 42 +++++++++++---- src/compiler/factory.ts | 24 +++++---- src/compiler/transformers/ts.ts | 9 ++-- src/compiler/types.ts | 26 +++++----- src/compiler/utilities.ts | 2 +- .../reference/asyncAwaitNestedClasses_es5.js | 52 +++++++++++++++++++ .../asyncAwaitNestedClasses_es5.symbols | 43 +++++++++++++++ .../asyncAwaitNestedClasses_es5.types | 52 +++++++++++++++++++ .../async/es5/asyncAwaitNestedClasses_es5.ts | 18 +++++++ 9 files changed, 232 insertions(+), 36 deletions(-) create mode 100644 tests/baselines/reference/asyncAwaitNestedClasses_es5.js create mode 100644 tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols create mode 100644 tests/baselines/reference/asyncAwaitNestedClasses_es5.types create mode 100644 tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8a1de17826c..705c0811cdc 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -289,6 +289,9 @@ namespace ts { let generatedNames: Map; // Set of names generated by the NameGenerator. let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. let tempFlags: TempFlags; // TempFlags for the current name generation scope. + let reservedNamesStack: Map[]; // Stack of TempFlags reserved in enclosing name generation scopes. + let reservedNames: Map; // TempFlags to reserve in nested name generation scopes. + let writer: EmitTextWriter; let ownWriter: EmitTextWriter; let write = writeBase; @@ -434,6 +437,7 @@ namespace ts { generatedNames = createMap(); tempFlagsStack = []; tempFlags = TempFlags.Auto; + reservedNamesStack = []; comments.reset(); setWriter(/*output*/ undefined); } @@ -3083,6 +3087,7 @@ namespace ts { } tempFlagsStack.push(tempFlags); tempFlags = 0; + reservedNamesStack.push(reservedNames); } /** @@ -3093,16 +3098,24 @@ namespace ts { return; } tempFlags = tempFlagsStack.pop(); + reservedNames = reservedNamesStack.pop(); + } + + function reserveNameInNestedScopes(name: string) { + if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { + reservedNames = createMap(); + } + reservedNames.set(name, true); } /** * Generate the text for a generated identifier. */ function generateName(name: GeneratedIdentifier) { - if (name.autoGenerateKind === GeneratedIdentifierKind.Node) { + if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { // Node names generate unique names based on their original node // and are cached based on that node's id. - if (name.skipNameGenerationScope) { + if (name.autoGenerateFlags & GeneratedIdentifierFlags.SkipNameGenerationScope) { const savedTempFlags = tempFlags; popNameGenerationScope(/*node*/ undefined); const result = generateNameCached(getNodeForGeneratedName(name)); @@ -3134,7 +3147,8 @@ namespace ts { function isUniqueName(name: string): boolean { return !(hasGlobalName && hasGlobalName(name)) && !currentSourceFile.identifiers.has(name) - && !generatedNames.has(name); + && !generatedNames.has(name) + && !(reservedNames && reservedNames.has(name)); } /** @@ -3158,11 +3172,14 @@ namespace ts { * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. */ - function makeTempVariableName(flags: TempFlags): string { + function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { if (flags && !(tempFlags & flags)) { const name = flags === TempFlags._i ? "_i" : "_n"; if (isUniqueName(name)) { tempFlags |= flags; + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } return name; } } @@ -3175,6 +3192,9 @@ namespace ts { ? "_" + String.fromCharCode(CharacterCodes.a + count) : "_" + (count - 26); if (isUniqueName(name)) { + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } return name; } } @@ -3275,12 +3295,12 @@ namespace ts { * Generates a unique identifier for a node. */ function makeName(name: GeneratedIdentifier) { - switch (name.autoGenerateKind) { - case GeneratedIdentifierKind.Auto: - return makeTempVariableName(TempFlags.Auto); - case GeneratedIdentifierKind.Loop: - return makeTempVariableName(TempFlags._i); - case GeneratedIdentifierKind.Unique: + switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + case GeneratedIdentifierFlags.Auto: + return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Loop: + return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Unique: return makeUniqueName(idText(name)); } @@ -3300,7 +3320,7 @@ namespace ts { // if "node" is a different generated name (having a different // "autoGenerateId"), use it and stop traversing. if (isIdentifier(node) - && node.autoGenerateKind === GeneratedIdentifierKind.Node + && node.autoGenerateFlags === GeneratedIdentifierFlags.Node && node.autoGenerateId !== autoGenerateId) { break; } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 14c8ad91eeb..0a006e1c73a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -117,7 +117,7 @@ namespace ts { const node = createSynthesizedNode(SyntaxKind.Identifier); node.escapedText = escapeLeadingUnderscores(text); node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; - node.autoGenerateKind = GeneratedIdentifierKind.None; + node.autoGenerateFlags = GeneratedIdentifierFlags.None; node.autoGenerateId = 0; if (typeArguments) { node.typeArguments = createNodeArray(typeArguments as ReadonlyArray); @@ -137,21 +137,26 @@ namespace ts { let nextAutoGenerateId = 0; /** Create a unique temporary variable. */ - export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier { + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; + /* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): Identifier; // tslint:disable-line unified-signatures + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): Identifier { const name = createIdentifier(""); - name.autoGenerateKind = GeneratedIdentifierKind.Auto; + name.autoGenerateFlags = GeneratedIdentifierFlags.Auto; name.autoGenerateId = nextAutoGenerateId; nextAutoGenerateId++; if (recordTempVariable) { recordTempVariable(name); } + if (reservedInNestedScopes) { + name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + } return name; } /** Create a unique temporary variable for use in a loop. */ export function createLoopVariable(): Identifier { const name = createIdentifier(""); - name.autoGenerateKind = GeneratedIdentifierKind.Loop; + name.autoGenerateFlags = GeneratedIdentifierFlags.Loop; name.autoGenerateId = nextAutoGenerateId; nextAutoGenerateId++; return name; @@ -160,7 +165,7 @@ namespace ts { /** Create a unique name based on the supplied text. */ export function createUniqueName(text: string): Identifier { const name = createIdentifier(text); - name.autoGenerateKind = GeneratedIdentifierKind.Unique; + name.autoGenerateFlags = GeneratedIdentifierFlags.Unique; name.autoGenerateId = nextAutoGenerateId; nextAutoGenerateId++; return name; @@ -168,14 +173,15 @@ namespace ts { /** Create a unique name generated for a node. */ export function getGeneratedNameForNode(node: Node): Identifier; - // tslint:disable-next-line unified-signatures - /*@internal*/ export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier; + /* @internal */ export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier; // tslint:disable-line unified-signatures export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier { const name = createIdentifier(""); - name.autoGenerateKind = GeneratedIdentifierKind.Node; + name.autoGenerateFlags = GeneratedIdentifierFlags.Node; name.autoGenerateId = nextAutoGenerateId; name.original = node; - name.skipNameGenerationScope = !!shouldSkipNameGenerationScope; + if (shouldSkipNameGenerationScope) { + name.autoGenerateFlags |= GeneratedIdentifierFlags.SkipNameGenerationScope; + } nextAutoGenerateId++; return name; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index cdb0d0667ad..f6c5d145a90 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -893,11 +893,14 @@ namespace ts { if (some(staticProperties) || some(pendingExpressions)) { const expressions: Expression[] = []; - const temp = createTempVariable(hoistVariableDeclaration); - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference) { + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); + if (isClassWithConstructorReference) { // record an alias as the class name is not in scope for statics. enableSubstitutionForClassAliases(); - classAliases[getOriginalNodeId(node)] = getSynthesizedClone(temp); + const alias = getSynthesizedClone(temp); + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; } // To preserve the behavior of the old emitter, we explicitly indent diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6622348e101..478585adb01 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -676,12 +676,18 @@ namespace ts { export type ModifiersArray = NodeArray; /*@internal*/ - export const enum GeneratedIdentifierKind { - None, // Not automatically generated. - Auto, // Automatically generated identifier. - Loop, // Automatically generated identifier with a preference for '_i'. - Unique, // Unique name based on the 'text' property. - Node, // Unique name based on the node in the 'original' property. + export const enum GeneratedIdentifierFlags { + // Kinds + None = 0, // Not automatically generated. + Auto = 1, // Automatically generated identifier. + Loop = 2, // Automatically generated identifier with a preference for '_i'. + Unique = 3, // Unique name based on the 'text' property. + Node = 4, // Unique name based on the node in the 'original' property. + KindMask = 7, // Mask to extract the kind of identifier from its flags. + + // Flags + SkipNameGenerationScope = 1 << 3, // Should skip a name generation scope when generating the name for this identifier + ReservedInNestedScopes = 1 << 4, // Reserve the generated name in nested scopes } export interface Identifier extends PrimaryExpression, Declaration { @@ -692,12 +698,11 @@ namespace ts { */ escapedText: __String; originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later - /*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier. + /*@internal*/ autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier. /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace /*@internal*/ typeArguments?: NodeArray; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help. /*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id. - /*@internal*/ skipNameGenerationScope?: boolean; // Should skip a name generation scope when generating the name for this identifier } // Transient identifier node (marked by id === -1) @@ -707,10 +712,7 @@ namespace ts { /*@internal*/ export interface GeneratedIdentifier extends Identifier { - autoGenerateKind: GeneratedIdentifierKind.Auto - | GeneratedIdentifierKind.Loop - | GeneratedIdentifierKind.Unique - | GeneratedIdentifierKind.Node; + autoGenerateFlags: GeneratedIdentifierFlags; } export interface QualifiedName extends Node { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 796ed35a861..357e5179865 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5134,7 +5134,7 @@ namespace ts { /* @internal */ export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier { // Using `>` here catches both `GeneratedIdentifierKind.None` and `undefined`. - return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.None; + return isIdentifier(node) && (node.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; } // Keywords diff --git a/tests/baselines/reference/asyncAwaitNestedClasses_es5.js b/tests/baselines/reference/asyncAwaitNestedClasses_es5.js new file mode 100644 index 00000000000..ff806b76ff1 --- /dev/null +++ b/tests/baselines/reference/asyncAwaitNestedClasses_es5.js @@ -0,0 +1,52 @@ +//// [asyncAwaitNestedClasses_es5.ts] +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { + static B = class B { + static func2(): Promise { + return new Promise((resolve) => { resolve(null); }); + } + static C = class C { + static async func() { + await B.func2(); + } + } + } +} + +A.B.C.func(); + +//// [asyncAwaitNestedClasses_es5.js] +// https://github.com/Microsoft/TypeScript/issues/20744 +var A = /** @class */ (function () { + function A() { + } + A.B = (_a = /** @class */ (function () { + function B() { + } + B.func2 = function () { + return new Promise(function (resolve) { resolve(null); }); + }; + return B; + }()), + _a.C = /** @class */ (function () { + function C() { + } + C.func = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, _a.func2()]; + case 1: + _b.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return C; + }()), + _a); + return A; + var _a; +}()); +A.B.C.func(); diff --git a/tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols b/tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols new file mode 100644 index 00000000000..339b2617fbf --- /dev/null +++ b/tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols @@ -0,0 +1,43 @@ +=== tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts === +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { +>A : Symbol(A, Decl(asyncAwaitNestedClasses_es5.ts, 0, 0)) + + static B = class B { +>B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9)) +>B : Symbol(B, Decl(asyncAwaitNestedClasses_es5.ts, 2, 14)) + + static func2(): Promise { +>func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24)) +>Promise : Symbol(Promise, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + return new Promise((resolve) => { resolve(null); }); +>Promise : Symbol(Promise, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(asyncAwaitNestedClasses_es5.ts, 4, 32)) +>resolve : Symbol(resolve, Decl(asyncAwaitNestedClasses_es5.ts, 4, 32)) + } + static C = class C { +>C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9)) +>C : Symbol(C, Decl(asyncAwaitNestedClasses_es5.ts, 6, 18)) + + static async func() { +>func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28)) + + await B.func2(); +>B.func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24)) +>B : Symbol(B, Decl(asyncAwaitNestedClasses_es5.ts, 2, 14)) +>func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24)) + } + } + } +} + +A.B.C.func(); +>A.B.C.func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28)) +>A.B.C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9)) +>A.B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9)) +>A : Symbol(A, Decl(asyncAwaitNestedClasses_es5.ts, 0, 0)) +>B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9)) +>C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9)) +>func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28)) + diff --git a/tests/baselines/reference/asyncAwaitNestedClasses_es5.types b/tests/baselines/reference/asyncAwaitNestedClasses_es5.types new file mode 100644 index 00000000000..fd61951d5cc --- /dev/null +++ b/tests/baselines/reference/asyncAwaitNestedClasses_es5.types @@ -0,0 +1,52 @@ +=== tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts === +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { +>A : A + + static B = class B { +>B : typeof B +>class B { static func2(): Promise { return new Promise((resolve) => { resolve(null); }); } static C = class C { static async func() { await B.func2(); } } } : typeof B +>B : typeof B + + static func2(): Promise { +>func2 : () => Promise +>Promise : Promise + + return new Promise((resolve) => { resolve(null); }); +>new Promise((resolve) => { resolve(null); }) : Promise +>Promise : PromiseConstructor +>(resolve) => { resolve(null); } : (resolve: (value?: void | PromiseLike) => void) => void +>resolve : (value?: void | PromiseLike) => void +>resolve(null) : void +>resolve : (value?: void | PromiseLike) => void +>null : null + } + static C = class C { +>C : typeof C +>class C { static async func() { await B.func2(); } } : typeof C +>C : typeof C + + static async func() { +>func : () => Promise + + await B.func2(); +>await B.func2() : void +>B.func2() : Promise +>B.func2 : () => Promise +>B : typeof B +>func2 : () => Promise + } + } + } +} + +A.B.C.func(); +>A.B.C.func() : Promise +>A.B.C.func : () => Promise +>A.B.C : typeof C +>A.B : typeof B +>A : typeof A +>B : typeof B +>C : typeof C +>func : () => Promise + diff --git a/tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts b/tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts new file mode 100644 index 00000000000..222718122ef --- /dev/null +++ b/tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts @@ -0,0 +1,18 @@ +// @target: ES5 +// @lib: es5,es2015.promise +// @noEmitHelpers: true +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { + static B = class B { + static func2(): Promise { + return new Promise((resolve) => { resolve(null); }); + } + static C = class C { + static async func() { + await B.func2(); + } + } + } +} + +A.B.C.func(); \ No newline at end of file