Merge pull request #21206 from Microsoft/fix20744

Fix temp variable emit for names used in nested classes
This commit is contained in:
Ron Buckton 2018-01-16 16:00:24 -08:00 committed by GitHub
commit cd525fb6de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 36 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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 {

View File

@ -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

View 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();

View File

@ -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))

View 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>

View File

@ -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();