mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-07 14:34:35 -06:00
Merge pull request #21206 from Microsoft/fix20744
Fix temp variable emit for names used in nested classes
This commit is contained in:
commit
cd525fb6de
@ -289,6 +289,9 @@ namespace ts {
|
||||
let generatedNames: Map<true>; // 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<true>[]; // Stack of TempFlags reserved in enclosing name generation scopes.
|
||||
let reservedNames: Map<true>; // 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<true>();
|
||||
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<true>();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ namespace ts {
|
||||
const node = <Identifier>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<TypeNode>);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -676,12 +676,18 @@ namespace ts {
|
||||
export type ModifiersArray = NodeArray<Modifier>;
|
||||
|
||||
/*@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<TypeNode | TypeParameterDeclaration>; // 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.<T>
|
||||
/*@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 {
|
||||
|
||||
@ -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
|
||||
|
||||
52
tests/baselines/reference/asyncAwaitNestedClasses_es5.js
Normal file
52
tests/baselines/reference/asyncAwaitNestedClasses_es5.js
Normal file
@ -0,0 +1,52 @@
|
||||
//// [asyncAwaitNestedClasses_es5.ts]
|
||||
// https://github.com/Microsoft/TypeScript/issues/20744
|
||||
class A {
|
||||
static B = class B {
|
||||
static func2(): Promise<void> {
|
||||
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();
|
||||
@ -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<void> {
|
||||
>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))
|
||||
|
||||
52
tests/baselines/reference/asyncAwaitNestedClasses_es5.types
Normal file
52
tests/baselines/reference/asyncAwaitNestedClasses_es5.types
Normal file
@ -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<void> { return new Promise((resolve) => { resolve(null); }); } static C = class C { static async func() { await B.func2(); } } } : typeof B
|
||||
>B : typeof B
|
||||
|
||||
static func2(): Promise<void> {
|
||||
>func2 : () => Promise<void>
|
||||
>Promise : Promise<T>
|
||||
|
||||
return new Promise((resolve) => { resolve(null); });
|
||||
>new Promise((resolve) => { resolve(null); }) : Promise<void>
|
||||
>Promise : PromiseConstructor
|
||||
>(resolve) => { resolve(null); } : (resolve: (value?: void | PromiseLike<void>) => void) => void
|
||||
>resolve : (value?: void | PromiseLike<void>) => void
|
||||
>resolve(null) : void
|
||||
>resolve : (value?: void | PromiseLike<void>) => 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<void>
|
||||
|
||||
await B.func2();
|
||||
>await B.func2() : void
|
||||
>B.func2() : Promise<void>
|
||||
>B.func2 : () => Promise<void>
|
||||
>B : typeof B
|
||||
>func2 : () => Promise<void>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
A.B.C.func();
|
||||
>A.B.C.func() : Promise<void>
|
||||
>A.B.C.func : () => Promise<void>
|
||||
>A.B.C : typeof C
|
||||
>A.B : typeof B
|
||||
>A : typeof A
|
||||
>B : typeof B
|
||||
>C : typeof C
|
||||
>func : () => Promise<void>
|
||||
|
||||
@ -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<void> {
|
||||
return new Promise((resolve) => { resolve(null); });
|
||||
}
|
||||
static C = class C {
|
||||
static async func() {
|
||||
await B.func2();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
A.B.C.func();
|
||||
Loading…
x
Reference in New Issue
Block a user