Skip to content

Commit cd525fb

Browse files
authored
Merge pull request #21206 from Microsoft/fix20744
Fix temp variable emit for names used in nested classes
2 parents 64b3086 + 1785d87 commit cd525fb

File tree

9 files changed

+232
-36
lines changed

9 files changed

+232
-36
lines changed

src/compiler/emitter.ts

+31-11
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ namespace ts {
289289
let generatedNames: Map<true>; // Set of names generated by the NameGenerator.
290290
let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes.
291291
let tempFlags: TempFlags; // TempFlags for the current name generation scope.
292+
let reservedNamesStack: Map<true>[]; // Stack of TempFlags reserved in enclosing name generation scopes.
293+
let reservedNames: Map<true>; // TempFlags to reserve in nested name generation scopes.
294+
292295
let writer: EmitTextWriter;
293296
let ownWriter: EmitTextWriter;
294297
let write = writeBase;
@@ -434,6 +437,7 @@ namespace ts {
434437
generatedNames = createMap<true>();
435438
tempFlagsStack = [];
436439
tempFlags = TempFlags.Auto;
440+
reservedNamesStack = [];
437441
comments.reset();
438442
setWriter(/*output*/ undefined);
439443
}
@@ -3083,6 +3087,7 @@ namespace ts {
30833087
}
30843088
tempFlagsStack.push(tempFlags);
30853089
tempFlags = 0;
3090+
reservedNamesStack.push(reservedNames);
30863091
}
30873092

30883093
/**
@@ -3093,16 +3098,24 @@ namespace ts {
30933098
return;
30943099
}
30953100
tempFlags = tempFlagsStack.pop();
3101+
reservedNames = reservedNamesStack.pop();
3102+
}
3103+
3104+
function reserveNameInNestedScopes(name: string) {
3105+
if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) {
3106+
reservedNames = createMap<true>();
3107+
}
3108+
reservedNames.set(name, true);
30963109
}
30973110

30983111
/**
30993112
* Generate the text for a generated identifier.
31003113
*/
31013114
function generateName(name: GeneratedIdentifier) {
3102-
if (name.autoGenerateKind === GeneratedIdentifierKind.Node) {
3115+
if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) {
31033116
// Node names generate unique names based on their original node
31043117
// and are cached based on that node's id.
3105-
if (name.skipNameGenerationScope) {
3118+
if (name.autoGenerateFlags & GeneratedIdentifierFlags.SkipNameGenerationScope) {
31063119
const savedTempFlags = tempFlags;
31073120
popNameGenerationScope(/*node*/ undefined);
31083121
const result = generateNameCached(getNodeForGeneratedName(name));
@@ -3134,7 +3147,8 @@ namespace ts {
31343147
function isUniqueName(name: string): boolean {
31353148
return !(hasGlobalName && hasGlobalName(name))
31363149
&& !currentSourceFile.identifiers.has(name)
3137-
&& !generatedNames.has(name);
3150+
&& !generatedNames.has(name)
3151+
&& !(reservedNames && reservedNames.has(name));
31383152
}
31393153

31403154
/**
@@ -3158,11 +3172,14 @@ namespace ts {
31583172
* TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name.
31593173
* Note that names generated by makeTempVariableName and makeUniqueName will never conflict.
31603174
*/
3161-
function makeTempVariableName(flags: TempFlags): string {
3175+
function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string {
31623176
if (flags && !(tempFlags & flags)) {
31633177
const name = flags === TempFlags._i ? "_i" : "_n";
31643178
if (isUniqueName(name)) {
31653179
tempFlags |= flags;
3180+
if (reservedInNestedScopes) {
3181+
reserveNameInNestedScopes(name);
3182+
}
31663183
return name;
31673184
}
31683185
}
@@ -3175,6 +3192,9 @@ namespace ts {
31753192
? "_" + String.fromCharCode(CharacterCodes.a + count)
31763193
: "_" + (count - 26);
31773194
if (isUniqueName(name)) {
3195+
if (reservedInNestedScopes) {
3196+
reserveNameInNestedScopes(name);
3197+
}
31783198
return name;
31793199
}
31803200
}
@@ -3275,12 +3295,12 @@ namespace ts {
32753295
* Generates a unique identifier for a node.
32763296
*/
32773297
function makeName(name: GeneratedIdentifier) {
3278-
switch (name.autoGenerateKind) {
3279-
case GeneratedIdentifierKind.Auto:
3280-
return makeTempVariableName(TempFlags.Auto);
3281-
case GeneratedIdentifierKind.Loop:
3282-
return makeTempVariableName(TempFlags._i);
3283-
case GeneratedIdentifierKind.Unique:
3298+
switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) {
3299+
case GeneratedIdentifierFlags.Auto:
3300+
return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes));
3301+
case GeneratedIdentifierFlags.Loop:
3302+
return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes));
3303+
case GeneratedIdentifierFlags.Unique:
32843304
return makeUniqueName(idText(name));
32853305
}
32863306

@@ -3300,7 +3320,7 @@ namespace ts {
33003320
// if "node" is a different generated name (having a different
33013321
// "autoGenerateId"), use it and stop traversing.
33023322
if (isIdentifier(node)
3303-
&& node.autoGenerateKind === GeneratedIdentifierKind.Node
3323+
&& node.autoGenerateFlags === GeneratedIdentifierFlags.Node
33043324
&& node.autoGenerateId !== autoGenerateId) {
33053325
break;
33063326
}

src/compiler/factory.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ namespace ts {
117117
const node = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
118118
node.escapedText = escapeLeadingUnderscores(text);
119119
node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown;
120-
node.autoGenerateKind = GeneratedIdentifierKind.None;
120+
node.autoGenerateFlags = GeneratedIdentifierFlags.None;
121121
node.autoGenerateId = 0;
122122
if (typeArguments) {
123123
node.typeArguments = createNodeArray(typeArguments as ReadonlyArray<TypeNode>);
@@ -137,21 +137,26 @@ namespace ts {
137137
let nextAutoGenerateId = 0;
138138

139139
/** Create a unique temporary variable. */
140-
export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier {
140+
export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier;
141+
/* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): Identifier; // tslint:disable-line unified-signatures
142+
export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): Identifier {
141143
const name = createIdentifier("");
142-
name.autoGenerateKind = GeneratedIdentifierKind.Auto;
144+
name.autoGenerateFlags = GeneratedIdentifierFlags.Auto;
143145
name.autoGenerateId = nextAutoGenerateId;
144146
nextAutoGenerateId++;
145147
if (recordTempVariable) {
146148
recordTempVariable(name);
147149
}
150+
if (reservedInNestedScopes) {
151+
name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes;
152+
}
148153
return name;
149154
}
150155

151156
/** Create a unique temporary variable for use in a loop. */
152157
export function createLoopVariable(): Identifier {
153158
const name = createIdentifier("");
154-
name.autoGenerateKind = GeneratedIdentifierKind.Loop;
159+
name.autoGenerateFlags = GeneratedIdentifierFlags.Loop;
155160
name.autoGenerateId = nextAutoGenerateId;
156161
nextAutoGenerateId++;
157162
return name;
@@ -160,22 +165,23 @@ namespace ts {
160165
/** Create a unique name based on the supplied text. */
161166
export function createUniqueName(text: string): Identifier {
162167
const name = createIdentifier(text);
163-
name.autoGenerateKind = GeneratedIdentifierKind.Unique;
168+
name.autoGenerateFlags = GeneratedIdentifierFlags.Unique;
164169
name.autoGenerateId = nextAutoGenerateId;
165170
nextAutoGenerateId++;
166171
return name;
167172
}
168173

169174
/** Create a unique name generated for a node. */
170175
export function getGeneratedNameForNode(node: Node): Identifier;
171-
// tslint:disable-next-line unified-signatures
172-
/*@internal*/ export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier;
176+
/* @internal */ export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier; // tslint:disable-line unified-signatures
173177
export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier {
174178
const name = createIdentifier("");
175-
name.autoGenerateKind = GeneratedIdentifierKind.Node;
179+
name.autoGenerateFlags = GeneratedIdentifierFlags.Node;
176180
name.autoGenerateId = nextAutoGenerateId;
177181
name.original = node;
178-
name.skipNameGenerationScope = !!shouldSkipNameGenerationScope;
182+
if (shouldSkipNameGenerationScope) {
183+
name.autoGenerateFlags |= GeneratedIdentifierFlags.SkipNameGenerationScope;
184+
}
179185
nextAutoGenerateId++;
180186
return name;
181187
}

src/compiler/transformers/ts.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -893,11 +893,14 @@ namespace ts {
893893

894894
if (some(staticProperties) || some(pendingExpressions)) {
895895
const expressions: Expression[] = [];
896-
const temp = createTempVariable(hoistVariableDeclaration);
897-
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference) {
896+
const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
897+
const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference);
898+
if (isClassWithConstructorReference) {
898899
// record an alias as the class name is not in scope for statics.
899900
enableSubstitutionForClassAliases();
900-
classAliases[getOriginalNodeId(node)] = getSynthesizedClone(temp);
901+
const alias = getSynthesizedClone(temp);
902+
alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes;
903+
classAliases[getOriginalNodeId(node)] = alias;
901904
}
902905

903906
// To preserve the behavior of the old emitter, we explicitly indent

src/compiler/types.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -676,12 +676,18 @@ namespace ts {
676676
export type ModifiersArray = NodeArray<Modifier>;
677677

678678
/*@internal*/
679-
export const enum GeneratedIdentifierKind {
680-
None, // Not automatically generated.
681-
Auto, // Automatically generated identifier.
682-
Loop, // Automatically generated identifier with a preference for '_i'.
683-
Unique, // Unique name based on the 'text' property.
684-
Node, // Unique name based on the node in the 'original' property.
679+
export const enum GeneratedIdentifierFlags {
680+
// Kinds
681+
None = 0, // Not automatically generated.
682+
Auto = 1, // Automatically generated identifier.
683+
Loop = 2, // Automatically generated identifier with a preference for '_i'.
684+
Unique = 3, // Unique name based on the 'text' property.
685+
Node = 4, // Unique name based on the node in the 'original' property.
686+
KindMask = 7, // Mask to extract the kind of identifier from its flags.
687+
688+
// Flags
689+
SkipNameGenerationScope = 1 << 3, // Should skip a name generation scope when generating the name for this identifier
690+
ReservedInNestedScopes = 1 << 4, // Reserve the generated name in nested scopes
685691
}
686692

687693
export interface Identifier extends PrimaryExpression, Declaration {
@@ -692,12 +698,11 @@ namespace ts {
692698
*/
693699
escapedText: __String;
694700
originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later
695-
/*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier.
701+
/*@internal*/ autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier.
696702
/*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name.
697703
isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace
698704
/*@internal*/ typeArguments?: NodeArray<TypeNode | TypeParameterDeclaration>; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help.
699705
/*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id.<T>
700-
/*@internal*/ skipNameGenerationScope?: boolean; // Should skip a name generation scope when generating the name for this identifier
701706
}
702707

703708
// Transient identifier node (marked by id === -1)
@@ -707,10 +712,7 @@ namespace ts {
707712

708713
/*@internal*/
709714
export interface GeneratedIdentifier extends Identifier {
710-
autoGenerateKind: GeneratedIdentifierKind.Auto
711-
| GeneratedIdentifierKind.Loop
712-
| GeneratedIdentifierKind.Unique
713-
| GeneratedIdentifierKind.Node;
715+
autoGenerateFlags: GeneratedIdentifierFlags;
714716
}
715717

716718
export interface QualifiedName extends Node {

src/compiler/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5134,7 +5134,7 @@ namespace ts {
51345134
/* @internal */
51355135
export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier {
51365136
// Using `>` here catches both `GeneratedIdentifierKind.None` and `undefined`.
5137-
return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.None;
5137+
return isIdentifier(node) && (node.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None;
51385138
}
51395139

51405140
// Keywords
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//// [asyncAwaitNestedClasses_es5.ts]
2+
// https://github.com/Microsoft/TypeScript/issues/20744
3+
class A {
4+
static B = class B {
5+
static func2(): Promise<void> {
6+
return new Promise((resolve) => { resolve(null); });
7+
}
8+
static C = class C {
9+
static async func() {
10+
await B.func2();
11+
}
12+
}
13+
}
14+
}
15+
16+
A.B.C.func();
17+
18+
//// [asyncAwaitNestedClasses_es5.js]
19+
// https://github.com/Microsoft/TypeScript/issues/20744
20+
var A = /** @class */ (function () {
21+
function A() {
22+
}
23+
A.B = (_a = /** @class */ (function () {
24+
function B() {
25+
}
26+
B.func2 = function () {
27+
return new Promise(function (resolve) { resolve(null); });
28+
};
29+
return B;
30+
}()),
31+
_a.C = /** @class */ (function () {
32+
function C() {
33+
}
34+
C.func = function () {
35+
return __awaiter(this, void 0, void 0, function () {
36+
return __generator(this, function (_b) {
37+
switch (_b.label) {
38+
case 0: return [4 /*yield*/, _a.func2()];
39+
case 1:
40+
_b.sent();
41+
return [2 /*return*/];
42+
}
43+
});
44+
});
45+
};
46+
return C;
47+
}()),
48+
_a);
49+
return A;
50+
var _a;
51+
}());
52+
A.B.C.func();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/20744
3+
class A {
4+
>A : Symbol(A, Decl(asyncAwaitNestedClasses_es5.ts, 0, 0))
5+
6+
static B = class B {
7+
>B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9))
8+
>B : Symbol(B, Decl(asyncAwaitNestedClasses_es5.ts, 2, 14))
9+
10+
static func2(): Promise<void> {
11+
>func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24))
12+
>Promise : Symbol(Promise, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
13+
14+
return new Promise((resolve) => { resolve(null); });
15+
>Promise : Symbol(Promise, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
16+
>resolve : Symbol(resolve, Decl(asyncAwaitNestedClasses_es5.ts, 4, 32))
17+
>resolve : Symbol(resolve, Decl(asyncAwaitNestedClasses_es5.ts, 4, 32))
18+
}
19+
static C = class C {
20+
>C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9))
21+
>C : Symbol(C, Decl(asyncAwaitNestedClasses_es5.ts, 6, 18))
22+
23+
static async func() {
24+
>func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28))
25+
26+
await B.func2();
27+
>B.func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24))
28+
>B : Symbol(B, Decl(asyncAwaitNestedClasses_es5.ts, 2, 14))
29+
>func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24))
30+
}
31+
}
32+
}
33+
}
34+
35+
A.B.C.func();
36+
>A.B.C.func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28))
37+
>A.B.C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9))
38+
>A.B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9))
39+
>A : Symbol(A, Decl(asyncAwaitNestedClasses_es5.ts, 0, 0))
40+
>B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9))
41+
>C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9))
42+
>func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28))
43+

0 commit comments

Comments
 (0)