From bc91292145e7e2cfeb8ba0c86e98f1965c2e7f93 Mon Sep 17 00:00:00 2001 From: Joey Watts Date: Tue, 27 Nov 2018 17:10:34 -0500 Subject: [PATCH 1/9] Fix Computed Property Name Bindings (#17) * Fix microsoft/TypeScript#27864 Signed-off-by: Joseph Watts * Tests for computed field scope fix Signed-off-by: Joseph Watts --- src/compiler/checker.ts | 17 +++++++ src/compiler/transformer.ts | 14 +++++- src/compiler/transformers/ts.ts | 18 ++++++- src/compiler/types.ts | 7 ++- .../reference/api/tsserverlibrary.d.ts | 6 ++- tests/baselines/reference/api/typescript.d.ts | 6 ++- .../baselines/reference/classBlockScoping.js | 2 +- .../classExpressionWithStaticProperties3.js | 2 +- ...classExpressionWithStaticPropertiesES63.js | 2 +- .../computedPropertyNames52_ES5.errors.txt | 17 +++++++ .../reference/computedPropertyNames52_ES5.js | 33 +++++++++++++ .../computedPropertyNames52_ES5.symbols | 36 ++++++++++++++ .../computedPropertyNames52_ES5.types | 47 +++++++++++++++++++ .../computedPropertyNames52_ES6.errors.txt | 17 +++++++ .../reference/computedPropertyNames52_ES6.js | 28 +++++++++++ .../computedPropertyNames52_ES6.symbols | 36 ++++++++++++++ .../computedPropertyNames52_ES6.types | 47 +++++++++++++++++++ .../computedPropertyNames52_ES5.ts | 12 +++++ .../computedPropertyNames52_ES6.ts | 12 +++++ 19 files changed, 350 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/computedPropertyNames52_ES5.errors.txt create mode 100644 tests/baselines/reference/computedPropertyNames52_ES5.js create mode 100644 tests/baselines/reference/computedPropertyNames52_ES5.symbols create mode 100644 tests/baselines/reference/computedPropertyNames52_ES5.types create mode 100644 tests/baselines/reference/computedPropertyNames52_ES6.errors.txt create mode 100644 tests/baselines/reference/computedPropertyNames52_ES6.js create mode 100644 tests/baselines/reference/computedPropertyNames52_ES6.symbols create mode 100644 tests/baselines/reference/computedPropertyNames52_ES6.types create mode 100644 tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts create mode 100644 tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7a1364c7a1f7b..4d73b7196ccff 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17834,6 +17834,23 @@ namespace ts { const links = getNodeLinks(node.expression); if (!links.resolvedType) { links.resolvedType = checkExpression(node.expression); + + if (isPropertyDeclaration(node.parent) && isClassLike(node.parent.parent)) { + const container = getEnclosingBlockScopeContainer(node); + let current = container; + let containedInIterationStatement = false; + while (current && !nodeStartsNewLexicalEnvironment(current)) { + if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { + containedInIterationStatement = true; + break; + } + current = current.parent; + } + if (containedInIterationStatement) { + getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + } + links.flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } // This will allow types number, string, symbol or any. It will also allow enums, the unknown // type, and any union of these types (like string | number). if (links.resolvedType.flags & TypeFlags.Nullable || diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index fdcaadb404011..ff9bb9c216498 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -92,6 +92,8 @@ namespace ts { let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + let lexicalEnvironmentScopingStack: LexicalEnvironmentScoping[] = []; + let lexicalEnvironmentScoping: LexicalEnvironmentScoping; let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; let emitHelpers: EmitHelper[] | undefined; @@ -266,7 +268,7 @@ namespace ts { * Starts a new lexical environment. Any existing hoisted variable or function declarations * are pushed onto a stack, and the related storage variables are reset. */ - function startLexicalEnvironment(): void { + function startLexicalEnvironment(scoping: LexicalEnvironmentScoping = LexicalEnvironmentScoping.Function): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); @@ -275,11 +277,13 @@ namespace ts { // stack size variable. This allows us to reuse existing array slots we've // already allocated between transformations to avoid allocation and GC overhead during // transformation. + lexicalEnvironmentScopingStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentScoping; lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; lexicalEnvironmentStackOffset++; lexicalEnvironmentVariableDeclarations = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; + lexicalEnvironmentScoping = scoping; } /** Suspends the current lexical environment, usually after visiting a parameter list. */ @@ -316,7 +320,10 @@ namespace ts { if (lexicalEnvironmentVariableDeclarations) { const statement = createVariableStatement( /*modifiers*/ undefined, - createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) + createVariableDeclarationList( + lexicalEnvironmentVariableDeclarations, + lexicalEnvironmentScoping === LexicalEnvironmentScoping.Block ? NodeFlags.Let : undefined + ) ); if (!statements) { @@ -332,9 +339,11 @@ namespace ts { lexicalEnvironmentStackOffset--; lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentScoping = lexicalEnvironmentScopingStack[lexicalEnvironmentStackOffset]; if (lexicalEnvironmentStackOffset === 0) { lexicalEnvironmentVariableDeclarationsStack = []; lexicalEnvironmentFunctionDeclarationsStack = []; + lexicalEnvironmentScopingStack = []; } return statements; } @@ -366,6 +375,7 @@ namespace ts { lexicalEnvironmentVariableDeclarationsStack = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; lexicalEnvironmentFunctionDeclarationsStack = undefined!; + lexicalEnvironmentScopingStack = undefined!; onSubstituteNode = undefined!; onEmitNode = undefined!; emitHelpers = undefined; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 700c0c8af2579..6789acb3b1376 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -208,7 +208,10 @@ namespace ts { * @param node The node to visit. */ function visitorWorker(node: Node): VisitResult { - if (node.transformFlags & TransformFlags.TypeScript) { + if (node.kind === SyntaxKind.Block && node.transformFlags & TransformFlags.AssertTypeScript) { + return visitBlock(node as Block); + } + else if (node.transformFlags & TransformFlags.TypeScript) { // This node is explicitly marked as TypeScript, so we should transform the node. return visitTypeScript(node); } @@ -560,6 +563,19 @@ namespace ts { } } + function visitBlock(node: Block): Block { + startLexicalEnvironment(LexicalEnvironmentScoping.Block); + node = visitEachChild(node, visitor, context); + const declarations = endLexicalEnvironment(); + if (some(declarations)) { + return updateBlock( + node, + mergeLexicalEnvironment(node.statements, declarations) + ); + } + return node; + } + function visitSourceFile(node: SourceFile) { const alwaysStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") && !(isExternalModule(node) && moduleKind >= ModuleKind.ES2015) && diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 495465f70326c..831fe05d6d1cf 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5241,6 +5241,11 @@ namespace ts { writeFile: WriteFileCallback; } + export const enum LexicalEnvironmentScoping { + Function, + Block + } + export interface TransformationContext { /*@internal*/ getEmitResolver(): EmitResolver; /*@internal*/ getEmitHost(): EmitHost; @@ -5249,7 +5254,7 @@ namespace ts { getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(): void; + startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2b78178587e56..e08971368b547 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2756,11 +2756,15 @@ declare namespace ts { Unspecified = 4, EmbeddedStatement = 5 } + enum LexicalEnvironmentScoping { + Function = 0, + Block = 1 + } interface TransformationContext { /** Gets the compiler options supplied to the transformer. */ getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(): void; + startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; /** Resumes a suspended lexical environment, usually before visiting a function body. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 0e693f698f253..9f132dbca0e2c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2756,11 +2756,15 @@ declare namespace ts { Unspecified = 4, EmbeddedStatement = 5 } + enum LexicalEnvironmentScoping { + Function = 0, + Block = 1 + } interface TransformationContext { /** Gets the compiler options supplied to the transformer. */ getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(): void; + startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; /** Resumes a suspended lexical environment, usually before visiting a function body. */ diff --git a/tests/baselines/reference/classBlockScoping.js b/tests/baselines/reference/classBlockScoping.js index ee3d9163b5e99..b2846a9cc1c89 100644 --- a/tests/baselines/reference/classBlockScoping.js +++ b/tests/baselines/reference/classBlockScoping.js @@ -35,9 +35,9 @@ function f(b: boolean) { //// [classBlockScoping.js] function f(b) { - var _a; var Foo; if (b) { + var _a = void 0; Foo = (_a = /** @class */ (function () { function Foo() { } diff --git a/tests/baselines/reference/classExpressionWithStaticProperties3.js b/tests/baselines/reference/classExpressionWithStaticProperties3.js index d71f02f22cb0b..120651d43c431 100644 --- a/tests/baselines/reference/classExpressionWithStaticProperties3.js +++ b/tests/baselines/reference/classExpressionWithStaticProperties3.js @@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) { arr.forEach(C => console.log(C.y())); //// [classExpressionWithStaticProperties3.js] -var _a; var arr = []; var _loop_1 = function (i) { + var _a = void 0; arr.push((_a = /** @class */ (function () { function C() { } diff --git a/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js b/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js index 8ff0f242f1951..b94e3aab7109e 100644 --- a/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js +++ b/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js @@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) { arr.forEach(C => console.log(C.y())); //// [classExpressionWithStaticPropertiesES63.js] -var _a; const arr = []; for (let i = 0; i < 3; i++) { + let _a; arr.push((_a = class C { }, _a.x = i, diff --git a/tests/baselines/reference/computedPropertyNames52_ES5.errors.txt b/tests/baselines/reference/computedPropertyNames52_ES5.errors.txt new file mode 100644 index 0000000000000..98ef7af30bfcc --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES5.errors.txt @@ -0,0 +1,17 @@ +tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts(5,13): error TS1166: A computed property name in a class property declaration must refer to an expression whose type is a literal type or a 'unique symbol' type. + + +==== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts (1 errors) ==== + const classes = []; + for (let i = 0; i <= 10; ++i) { + classes.push( + class A { + [i] = "my property"; + ~~~ +!!! error TS1166: A computed property name in a class property declaration must refer to an expression whose type is a literal type or a 'unique symbol' type. + } + ); + } + for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); + } \ No newline at end of file diff --git a/tests/baselines/reference/computedPropertyNames52_ES5.js b/tests/baselines/reference/computedPropertyNames52_ES5.js new file mode 100644 index 0000000000000..463c40ed1263d --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES5.js @@ -0,0 +1,33 @@ +//// [computedPropertyNames52_ES5.ts] +const classes = []; +for (let i = 0; i <= 10; ++i) { + classes.push( + class A { + [i] = "my property"; + } + ); +} +for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); +} + +//// [computedPropertyNames52_ES5.js] +var classes = []; +var _loop_1 = function (i) { + var _a = void 0, _b = void 0; + classes.push((_b = /** @class */ (function () { + function A() { + this[_a] = "my property"; + } + return A; + }()), + _a = i, + _b)); +}; +for (var i = 0; i <= 10; ++i) { + _loop_1(i); +} +for (var _i = 0, classes_1 = classes; _i < classes_1.length; _i++) { + var clazz = classes_1[_i]; + console.log(Object.getOwnPropertyNames(new clazz())); +} diff --git a/tests/baselines/reference/computedPropertyNames52_ES5.symbols b/tests/baselines/reference/computedPropertyNames52_ES5.symbols new file mode 100644 index 0000000000000..ede524071ec31 --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES5.symbols @@ -0,0 +1,36 @@ +=== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts === +const classes = []; +>classes : Symbol(classes, Decl(computedPropertyNames52_ES5.ts, 0, 5)) + +for (let i = 0; i <= 10; ++i) { +>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8)) +>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8)) +>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8)) + + classes.push( +>classes.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>classes : Symbol(classes, Decl(computedPropertyNames52_ES5.ts, 0, 5)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) + + class A { +>A : Symbol(A, Decl(computedPropertyNames52_ES5.ts, 2, 17)) + + [i] = "my property"; +>[i] : Symbol(A[i], Decl(computedPropertyNames52_ES5.ts, 3, 17)) +>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8)) + } + ); +} +for (const clazz of classes) { +>clazz : Symbol(clazz, Decl(computedPropertyNames52_ES5.ts, 8, 10)) +>classes : Symbol(classes, Decl(computedPropertyNames52_ES5.ts, 0, 5)) + + console.log(Object.getOwnPropertyNames(new clazz())); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>Object.getOwnPropertyNames : Symbol(ObjectConstructor.getOwnPropertyNames, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>getOwnPropertyNames : Symbol(ObjectConstructor.getOwnPropertyNames, Decl(lib.es5.d.ts, --, --)) +>clazz : Symbol(clazz, Decl(computedPropertyNames52_ES5.ts, 8, 10)) +} diff --git a/tests/baselines/reference/computedPropertyNames52_ES5.types b/tests/baselines/reference/computedPropertyNames52_ES5.types new file mode 100644 index 0000000000000..c0aa1ece400fe --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES5.types @@ -0,0 +1,47 @@ +=== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts === +const classes = []; +>classes : any[] +>[] : undefined[] + +for (let i = 0; i <= 10; ++i) { +>i : number +>0 : 0 +>i <= 10 : boolean +>i : number +>10 : 10 +>++i : number +>i : number + + classes.push( +>classes.push( class A { [i] = "my property"; } ) : number +>classes.push : (...items: any[]) => number +>classes : any[] +>push : (...items: any[]) => number + + class A { +>class A { [i] = "my property"; } : typeof A +>A : typeof A + + [i] = "my property"; +>[i] : string +>i : number +>"my property" : "my property" + } + ); +} +for (const clazz of classes) { +>clazz : any +>classes : any[] + + console.log(Object.getOwnPropertyNames(new clazz())); +>console.log(Object.getOwnPropertyNames(new clazz())) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>Object.getOwnPropertyNames(new clazz()) : string[] +>Object.getOwnPropertyNames : (o: any) => string[] +>Object : ObjectConstructor +>getOwnPropertyNames : (o: any) => string[] +>new clazz() : any +>clazz : any +} diff --git a/tests/baselines/reference/computedPropertyNames52_ES6.errors.txt b/tests/baselines/reference/computedPropertyNames52_ES6.errors.txt new file mode 100644 index 0000000000000..b48d170acda21 --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES6.errors.txt @@ -0,0 +1,17 @@ +tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts(5,13): error TS1166: A computed property name in a class property declaration must refer to an expression whose type is a literal type or a 'unique symbol' type. + + +==== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts (1 errors) ==== + const classes = []; + for (let i = 0; i <= 10; ++i) { + classes.push( + class A { + [i] = "my property"; + ~~~ +!!! error TS1166: A computed property name in a class property declaration must refer to an expression whose type is a literal type or a 'unique symbol' type. + } + ); + } + for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); + } \ No newline at end of file diff --git a/tests/baselines/reference/computedPropertyNames52_ES6.js b/tests/baselines/reference/computedPropertyNames52_ES6.js new file mode 100644 index 0000000000000..58a0d484486a8 --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES6.js @@ -0,0 +1,28 @@ +//// [computedPropertyNames52_ES6.ts] +const classes = []; +for (let i = 0; i <= 10; ++i) { + classes.push( + class A { + [i] = "my property"; + } + ); +} +for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); +} + +//// [computedPropertyNames52_ES6.js] +const classes = []; +for (let i = 0; i <= 10; ++i) { + let _a, _b; + classes.push((_b = class A { + constructor() { + this[_a] = "my property"; + } + }, + _a = i, + _b)); +} +for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); +} diff --git a/tests/baselines/reference/computedPropertyNames52_ES6.symbols b/tests/baselines/reference/computedPropertyNames52_ES6.symbols new file mode 100644 index 0000000000000..a7b8f869ce078 --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES6.symbols @@ -0,0 +1,36 @@ +=== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts === +const classes = []; +>classes : Symbol(classes, Decl(computedPropertyNames52_ES6.ts, 0, 5)) + +for (let i = 0; i <= 10; ++i) { +>i : Symbol(i, Decl(computedPropertyNames52_ES6.ts, 1, 8)) +>i : Symbol(i, Decl(computedPropertyNames52_ES6.ts, 1, 8)) +>i : Symbol(i, Decl(computedPropertyNames52_ES6.ts, 1, 8)) + + classes.push( +>classes.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>classes : Symbol(classes, Decl(computedPropertyNames52_ES6.ts, 0, 5)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) + + class A { +>A : Symbol(A, Decl(computedPropertyNames52_ES6.ts, 2, 17)) + + [i] = "my property"; +>[i] : Symbol(A[i], Decl(computedPropertyNames52_ES6.ts, 3, 17)) +>i : Symbol(i, Decl(computedPropertyNames52_ES6.ts, 1, 8)) + } + ); +} +for (const clazz of classes) { +>clazz : Symbol(clazz, Decl(computedPropertyNames52_ES6.ts, 8, 10)) +>classes : Symbol(classes, Decl(computedPropertyNames52_ES6.ts, 0, 5)) + + console.log(Object.getOwnPropertyNames(new clazz())); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>Object.getOwnPropertyNames : Symbol(ObjectConstructor.getOwnPropertyNames, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>getOwnPropertyNames : Symbol(ObjectConstructor.getOwnPropertyNames, Decl(lib.es5.d.ts, --, --)) +>clazz : Symbol(clazz, Decl(computedPropertyNames52_ES6.ts, 8, 10)) +} diff --git a/tests/baselines/reference/computedPropertyNames52_ES6.types b/tests/baselines/reference/computedPropertyNames52_ES6.types new file mode 100644 index 0000000000000..cbbaa7e71d7e6 --- /dev/null +++ b/tests/baselines/reference/computedPropertyNames52_ES6.types @@ -0,0 +1,47 @@ +=== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts === +const classes = []; +>classes : any[] +>[] : undefined[] + +for (let i = 0; i <= 10; ++i) { +>i : number +>0 : 0 +>i <= 10 : boolean +>i : number +>10 : 10 +>++i : number +>i : number + + classes.push( +>classes.push( class A { [i] = "my property"; } ) : number +>classes.push : (...items: any[]) => number +>classes : any[] +>push : (...items: any[]) => number + + class A { +>class A { [i] = "my property"; } : typeof A +>A : typeof A + + [i] = "my property"; +>[i] : string +>i : number +>"my property" : "my property" + } + ); +} +for (const clazz of classes) { +>clazz : any +>classes : any[] + + console.log(Object.getOwnPropertyNames(new clazz())); +>console.log(Object.getOwnPropertyNames(new clazz())) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>Object.getOwnPropertyNames(new clazz()) : string[] +>Object.getOwnPropertyNames : (o: any) => string[] +>Object : ObjectConstructor +>getOwnPropertyNames : (o: any) => string[] +>new clazz() : any +>clazz : any +} diff --git a/tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts b/tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts new file mode 100644 index 0000000000000..297a0e826dfc2 --- /dev/null +++ b/tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts @@ -0,0 +1,12 @@ +//@target: es5 +const classes = []; +for (let i = 0; i <= 10; ++i) { + classes.push( + class A { + [i] = "my property"; + } + ); +} +for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); +} \ No newline at end of file diff --git a/tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts b/tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts new file mode 100644 index 0000000000000..48ed1c391ce9b --- /dev/null +++ b/tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts @@ -0,0 +1,12 @@ +//@target: es6 +const classes = []; +for (let i = 0; i <= 10; ++i) { + classes.push( + class A { + [i] = "my property"; + } + ); +} +for (const clazz of classes) { + console.log(Object.getOwnPropertyNames(new clazz())); +} \ No newline at end of file From 7cc64563aec40dece50b34c53dc820a0fecb826e Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 08:55:11 -0500 Subject: [PATCH 2/9] Refactor duplicated code into getEnclosingIterationStatement Signed-off-by: Joseph Watts --- src/compiler/checker.ts | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4d73b7196ccff..df02788036654 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16305,6 +16305,10 @@ namespace ts { return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); } + function getEnclosingIterationStatement(node: Node): Node | undefined { + return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /* lookInLabeledStatements */ false)); + } + function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); } @@ -16323,18 +16327,8 @@ namespace ts { const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); const usedInFunction = isInsideFunction(node.parent, container); - let current = container; - - let containedInIterationStatement = false; - while (current && !nodeStartsNewLexicalEnvironment(current)) { - if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { - containedInIterationStatement = true; - break; - } - current = current.parent; - } - - if (containedInIterationStatement) { + const enclosingIterationStatement = getEnclosingIterationStatement(container); + if (enclosingIterationStatement) { if (usedInFunction) { // mark iteration statement as containing block-scoped binding captured in some function let capturesBlockScopeBindingInLoopBody = true; @@ -16354,7 +16348,7 @@ namespace ts { } } if (capturesBlockScopeBindingInLoopBody) { - getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; } } @@ -17837,17 +17831,9 @@ namespace ts { if (isPropertyDeclaration(node.parent) && isClassLike(node.parent.parent)) { const container = getEnclosingBlockScopeContainer(node); - let current = container; - let containedInIterationStatement = false; - while (current && !nodeStartsNewLexicalEnvironment(current)) { - if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { - containedInIterationStatement = true; - break; - } - current = current.parent; - } - if (containedInIterationStatement) { - getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + const enclosingIterationStatement = getEnclosingIterationStatement(container); + if (enclosingIterationStatement) { + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; } links.flags |= NodeCheckFlags.BlockScopedBindingInLoop; } From 4cc79ca4ff0d1bdc1e36896847b54ff07f8dee09 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 09:13:41 -0500 Subject: [PATCH 3/9] Rename LexicalEnvironmentScoping to LexicalEnvironmentKind Signed-off-by: Joseph Watts --- src/compiler/transformer.ts | 8 ++++---- src/compiler/transformers/ts.ts | 2 +- src/compiler/types.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index ff9bb9c216498..10b8174f0c3c0 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -92,8 +92,8 @@ namespace ts { let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; - let lexicalEnvironmentScopingStack: LexicalEnvironmentScoping[] = []; - let lexicalEnvironmentScoping: LexicalEnvironmentScoping; + let lexicalEnvironmentScopingStack: LexicalEnvironmentKind[] = []; + let lexicalEnvironmentScoping: LexicalEnvironmentKind; let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; let emitHelpers: EmitHelper[] | undefined; @@ -268,7 +268,7 @@ namespace ts { * Starts a new lexical environment. Any existing hoisted variable or function declarations * are pushed onto a stack, and the related storage variables are reset. */ - function startLexicalEnvironment(scoping: LexicalEnvironmentScoping = LexicalEnvironmentScoping.Function): void { + function startLexicalEnvironment(scoping: LexicalEnvironmentKind = LexicalEnvironmentKind.Function): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); @@ -322,7 +322,7 @@ namespace ts { /*modifiers*/ undefined, createVariableDeclarationList( lexicalEnvironmentVariableDeclarations, - lexicalEnvironmentScoping === LexicalEnvironmentScoping.Block ? NodeFlags.Let : undefined + lexicalEnvironmentScoping === LexicalEnvironmentKind.Block ? NodeFlags.Let : undefined ) ); diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 6789acb3b1376..5efac1d2c65b1 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -564,7 +564,7 @@ namespace ts { } function visitBlock(node: Block): Block { - startLexicalEnvironment(LexicalEnvironmentScoping.Block); + startLexicalEnvironment(LexicalEnvironmentKind.Block); node = visitEachChild(node, visitor, context); const declarations = endLexicalEnvironment(); if (some(declarations)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 831fe05d6d1cf..6b88c0b1cc699 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5241,7 +5241,7 @@ namespace ts { writeFile: WriteFileCallback; } - export const enum LexicalEnvironmentScoping { + export const enum LexicalEnvironmentKind { Function, Block } @@ -5254,7 +5254,7 @@ namespace ts { getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void; + startLexicalEnvironment(kind?: LexicalEnvironmentKind): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; From 0453abccb49eef0285cfd4ac4ce860bf0021e5a6 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 11:01:25 -0500 Subject: [PATCH 4/9] Internal start/end block APIs, use checker flags to hoist block vars Signed-off-by: Joseph Watts --- src/compiler/checker.ts | 9 +++-- src/compiler/factory.ts | 2 ++ src/compiler/transformer.ts | 59 ++++++++++++++++++++++++++------- src/compiler/transformers/ts.ts | 15 +++++++-- src/compiler/types.ts | 13 ++++---- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index df02788036654..bfee7e312418b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17829,13 +17829,18 @@ namespace ts { if (!links.resolvedType) { links.resolvedType = checkExpression(node.expression); - if (isPropertyDeclaration(node.parent) && isClassLike(node.parent.parent)) { + if (isPropertyDeclaration(node.parent) && isClassExpression(node.parent.parent)) { const container = getEnclosingBlockScopeContainer(node); const enclosingIterationStatement = getEnclosingIterationStatement(container); + // A computed property of a class expression inside a loop must be block scoped because + // the property name should be bound at class evaluation time. if (enclosingIterationStatement) { getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + // The hoisted variable which stores the evaluated property name should be block scoped. + getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + // The temporary name of the class expression should be block scoped. + getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } - links.flags |= NodeCheckFlags.BlockScopedBindingInLoop; } // This will allow types number, string, symbol or any. It will also allow enums, the unknown // type, and any union of these types (like string | number). diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ec19d2c906c12..403f79546ea17 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -3125,6 +3125,8 @@ namespace ts { enableEmitNotification: noop, enableSubstitution: noop, endLexicalEnvironment: () => undefined, + endBlockScope: () => undefined, + startBlockScope: noop, getCompilerOptions: notImplemented, getEmitHost: notImplemented, getEmitResolver: notImplemented, diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 10b8174f0c3c0..a2556cbb82888 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -92,10 +92,11 @@ namespace ts { let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; - let lexicalEnvironmentScopingStack: LexicalEnvironmentKind[] = []; - let lexicalEnvironmentScoping: LexicalEnvironmentKind; let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; + let blockScopedVariableDeclarationsStack: Identifier[][] = []; + let blockScopeStackOffset = 0; + let blockScopedVariableDeclarations: Identifier[]; let emitHelpers: EmitHelper[] | undefined; let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; @@ -114,6 +115,8 @@ namespace ts { endLexicalEnvironment, hoistVariableDeclaration, hoistFunctionDeclaration, + startBlockScope, + endBlockScope, requestEmitHelper, readEmitHelpers, enableSubstitution, @@ -241,6 +244,11 @@ namespace ts { function hoistVariableDeclaration(name: Identifier): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + // If the checker determined that this is a block scoped binding in a loop, we must emit a block-level variable declaration. + if (resolver && resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) { + (blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name); + return; + } const decl = setEmitFlags(createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); if (!lexicalEnvironmentVariableDeclarations) { lexicalEnvironmentVariableDeclarations = [decl]; @@ -264,11 +272,46 @@ namespace ts { } } + /** + * Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset. + */ + function startBlockScope() { + Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed."); + blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations; + blockScopeStackOffset++; + blockScopedVariableDeclarations = undefined!; + } + + /** + * Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned. + */ + function endBlockScope() { + Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed."); + const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ? + [ + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList( + blockScopedVariableDeclarations.map(identifier => createVariableDeclaration(identifier)), + NodeFlags.Let + ) + ) + ] : undefined; + blockScopeStackOffset--; + blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset]; + if (blockScopeStackOffset === 0) { + blockScopedVariableDeclarationsStack = []; + } + return statements; + } + /** * Starts a new lexical environment. Any existing hoisted variable or function declarations * are pushed onto a stack, and the related storage variables are reset. */ - function startLexicalEnvironment(scoping: LexicalEnvironmentKind = LexicalEnvironmentKind.Function): void { + function startLexicalEnvironment(): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); @@ -277,13 +320,11 @@ namespace ts { // stack size variable. This allows us to reuse existing array slots we've // already allocated between transformations to avoid allocation and GC overhead during // transformation. - lexicalEnvironmentScopingStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentScoping; lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; lexicalEnvironmentStackOffset++; lexicalEnvironmentVariableDeclarations = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; - lexicalEnvironmentScoping = scoping; } /** Suspends the current lexical environment, usually after visiting a parameter list. */ @@ -320,10 +361,7 @@ namespace ts { if (lexicalEnvironmentVariableDeclarations) { const statement = createVariableStatement( /*modifiers*/ undefined, - createVariableDeclarationList( - lexicalEnvironmentVariableDeclarations, - lexicalEnvironmentScoping === LexicalEnvironmentKind.Block ? NodeFlags.Let : undefined - ) + createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) ); if (!statements) { @@ -339,11 +377,9 @@ namespace ts { lexicalEnvironmentStackOffset--; lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentScoping = lexicalEnvironmentScopingStack[lexicalEnvironmentStackOffset]; if (lexicalEnvironmentStackOffset === 0) { lexicalEnvironmentVariableDeclarationsStack = []; lexicalEnvironmentFunctionDeclarationsStack = []; - lexicalEnvironmentScopingStack = []; } return statements; } @@ -375,7 +411,6 @@ namespace ts { lexicalEnvironmentVariableDeclarationsStack = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; lexicalEnvironmentFunctionDeclarationsStack = undefined!; - lexicalEnvironmentScopingStack = undefined!; onSubstituteNode = undefined!; onEmitNode = undefined!; emitHelpers = undefined; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 5efac1d2c65b1..9a9c77f096e2a 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -37,6 +37,8 @@ namespace ts { resumeLexicalEnvironment, endLexicalEnvironment, hoistVariableDeclaration, + startBlockScope, + endBlockScope } = context; const resolver = context.getEmitResolver(); @@ -564,9 +566,9 @@ namespace ts { } function visitBlock(node: Block): Block { - startLexicalEnvironment(LexicalEnvironmentKind.Block); + startBlockScope(); node = visitEachChild(node, visitor, context); - const declarations = endLexicalEnvironment(); + const declarations = endBlockScope(); if (some(declarations)) { return updateBlock( node, @@ -937,7 +939,13 @@ namespace ts { if (some(staticProperties) || some(pendingExpressions)) { const expressions: Expression[] = []; const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; - const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); + const temp = createTempVariable( + name => { + setOriginalNode(name, node); + hoistVariableDeclaration(name); + }, + !!isClassWithConstructorReference + ); if (isClassWithConstructorReference) { // record an alias as the class name is not in scope for statics. enableSubstitutionForClassAliases(); @@ -2207,6 +2215,7 @@ namespace ts { const inlinable = isSimpleInlineableExpression(innerExpression); if (!inlinable && shouldHoist) { const generatedName = getGeneratedNameForNode(name); + setOriginalNode(generatedName, name); hoistVariableDeclaration(generatedName); return createAssignment(generatedName, expression); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6b88c0b1cc699..bfcd5abf1a234 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5241,11 +5241,6 @@ namespace ts { writeFile: WriteFileCallback; } - export const enum LexicalEnvironmentKind { - Function, - Block - } - export interface TransformationContext { /*@internal*/ getEmitResolver(): EmitResolver; /*@internal*/ getEmitHost(): EmitHost; @@ -5254,7 +5249,7 @@ namespace ts { getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(kind?: LexicalEnvironmentKind): void; + startLexicalEnvironment(): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; @@ -5271,6 +5266,12 @@ namespace ts { /** Hoists a variable declaration to the containing scope. */ hoistVariableDeclaration(node: Identifier): void; + /* @internal */ + startBlockScope(): void; + + /* @internal */ + endBlockScope(): Statement[] | undefined; + /** Records a request for a non-scoped emit helper in the current context. */ requestEmitHelper(helper: EmitHelper): void; From 2dc12e5cb4e3d5e6b3ac12f6280a7d0ff4460ff6 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 11:01:52 -0500 Subject: [PATCH 5/9] Accept baseline changes Signed-off-by: Joseph Watts --- tests/baselines/reference/api/tsserverlibrary.d.ts | 6 +----- tests/baselines/reference/api/typescript.d.ts | 6 +----- tests/baselines/reference/classBlockScoping.js | 2 +- .../reference/classExpressionWithStaticProperties3.js | 2 +- .../reference/classExpressionWithStaticPropertiesES63.js | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e08971368b547..2b78178587e56 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2756,15 +2756,11 @@ declare namespace ts { Unspecified = 4, EmbeddedStatement = 5 } - enum LexicalEnvironmentScoping { - Function = 0, - Block = 1 - } interface TransformationContext { /** Gets the compiler options supplied to the transformer. */ getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void; + startLexicalEnvironment(): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; /** Resumes a suspended lexical environment, usually before visiting a function body. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 9f132dbca0e2c..0e693f698f253 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2756,15 +2756,11 @@ declare namespace ts { Unspecified = 4, EmbeddedStatement = 5 } - enum LexicalEnvironmentScoping { - Function = 0, - Block = 1 - } interface TransformationContext { /** Gets the compiler options supplied to the transformer. */ getCompilerOptions(): CompilerOptions; /** Starts a new lexical environment. */ - startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void; + startLexicalEnvironment(): void; /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; /** Resumes a suspended lexical environment, usually before visiting a function body. */ diff --git a/tests/baselines/reference/classBlockScoping.js b/tests/baselines/reference/classBlockScoping.js index b2846a9cc1c89..ee3d9163b5e99 100644 --- a/tests/baselines/reference/classBlockScoping.js +++ b/tests/baselines/reference/classBlockScoping.js @@ -35,9 +35,9 @@ function f(b: boolean) { //// [classBlockScoping.js] function f(b) { + var _a; var Foo; if (b) { - var _a = void 0; Foo = (_a = /** @class */ (function () { function Foo() { } diff --git a/tests/baselines/reference/classExpressionWithStaticProperties3.js b/tests/baselines/reference/classExpressionWithStaticProperties3.js index 120651d43c431..d71f02f22cb0b 100644 --- a/tests/baselines/reference/classExpressionWithStaticProperties3.js +++ b/tests/baselines/reference/classExpressionWithStaticProperties3.js @@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) { arr.forEach(C => console.log(C.y())); //// [classExpressionWithStaticProperties3.js] +var _a; var arr = []; var _loop_1 = function (i) { - var _a = void 0; arr.push((_a = /** @class */ (function () { function C() { } diff --git a/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js b/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js index b94e3aab7109e..8ff0f242f1951 100644 --- a/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js +++ b/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js @@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) { arr.forEach(C => console.log(C.y())); //// [classExpressionWithStaticPropertiesES63.js] +var _a; const arr = []; for (let i = 0; i < 3; i++) { - let _a; arr.push((_a = class C { }, _a.x = i, From a6376271123de150c481977ea295084ed4e06f18 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 11:24:56 -0500 Subject: [PATCH 6/9] Use block scoped temporary variable for storing class expression in loop Signed-off-by: Joseph Watts --- src/compiler/checker.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bfee7e312418b..0d3fc97c1117e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16200,6 +16200,11 @@ namespace ts { if (container.kind === SyntaxKind.PropertyDeclaration && hasModifier(container, ModifierFlags.Static)) { getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + // If the class expression is in a loop and the name of the class is used, + // the temporary variable which stores the evaluated class expression must be block scoped. + if (getEnclosingIterationStatement(declaration)) { + getNodeLinks(declaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } } break; } From d19e9470f40468f42505537287527983fa9ea3b7 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 11:30:45 -0500 Subject: [PATCH 7/9] Accept baselines Signed-off-by: Joseph Watts --- .../baselines/reference/classExpressionWithStaticProperties3.js | 2 +- .../reference/classExpressionWithStaticPropertiesES63.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/classExpressionWithStaticProperties3.js b/tests/baselines/reference/classExpressionWithStaticProperties3.js index d71f02f22cb0b..120651d43c431 100644 --- a/tests/baselines/reference/classExpressionWithStaticProperties3.js +++ b/tests/baselines/reference/classExpressionWithStaticProperties3.js @@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) { arr.forEach(C => console.log(C.y())); //// [classExpressionWithStaticProperties3.js] -var _a; var arr = []; var _loop_1 = function (i) { + var _a = void 0; arr.push((_a = /** @class */ (function () { function C() { } diff --git a/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js b/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js index 8ff0f242f1951..b94e3aab7109e 100644 --- a/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js +++ b/tests/baselines/reference/classExpressionWithStaticPropertiesES63.js @@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) { arr.forEach(C => console.log(C.y())); //// [classExpressionWithStaticPropertiesES63.js] -var _a; const arr = []; for (let i = 0; i < 3; i++) { + let _a; arr.push((_a = class C { }, _a.x = i, From 1d0c67760f44e303912903e40fdc4536051b0e56 Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 18:56:58 -0500 Subject: [PATCH 8/9] Fix block-scoped bindings inside class expression property initializers Signed-off-by: Joseph Watts --- src/compiler/checker.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d3fc97c1117e..afb08df2c5f4a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16306,8 +16306,16 @@ namespace ts { return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } - function isInsideFunction(node: Node, threshold: Node): boolean { - return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); + function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean { + return !!findAncestor(node, n => { + if (n === threshold) { + return "quit"; + } + if (isFunctionLike(n)) { + return true; + } + return (n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n); + }); } function getEnclosingIterationStatement(node: Node): Node | undefined { @@ -16331,10 +16339,10 @@ namespace ts { // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - const usedInFunction = isInsideFunction(node.parent, container); + const usedInFunctionOrInstanceProperty = isInsideFunctionOrInstancePropertyInitializer(node, container); const enclosingIterationStatement = getEnclosingIterationStatement(container); if (enclosingIterationStatement) { - if (usedInFunction) { + if (usedInFunctionOrInstanceProperty) { // mark iteration statement as containing block-scoped binding captured in some function let capturesBlockScopeBindingInLoopBody = true; if (isForStatement(container) && @@ -16369,7 +16377,7 @@ namespace ts { getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } - if (usedInFunction) { + if (usedInFunctionOrInstanceProperty) { getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; } } From 9e5d589beb8a5acd6ea6f4e6e0f791357f258a4b Mon Sep 17 00:00:00 2001 From: Joseph Watts Date: Thu, 29 Nov 2018 19:11:33 -0500 Subject: [PATCH 9/9] Add test for class property initializer referencing block scope binding Signed-off-by: Joseph Watts --- ...initializerReferencingBlockScopeBinding.js | 35 +++++++++++ ...alizerReferencingBlockScopeBinding.symbols | 43 ++++++++++++++ ...tializerReferencingBlockScopeBinding.types | 59 +++++++++++++++++++ ...initializerReferencingBlockScopeBinding.ts | 11 ++++ 4 files changed, 148 insertions(+) create mode 100644 tests/baselines/reference/initializerReferencingBlockScopeBinding.js create mode 100644 tests/baselines/reference/initializerReferencingBlockScopeBinding.symbols create mode 100644 tests/baselines/reference/initializerReferencingBlockScopeBinding.types create mode 100644 tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingBlockScopeBinding.ts diff --git a/tests/baselines/reference/initializerReferencingBlockScopeBinding.js b/tests/baselines/reference/initializerReferencingBlockScopeBinding.js new file mode 100644 index 0000000000000..63bc2fc8c97ca --- /dev/null +++ b/tests/baselines/reference/initializerReferencingBlockScopeBinding.js @@ -0,0 +1,35 @@ +//// [initializerReferencingBlockScopeBinding.ts] +let arr = []; +for (let i = 0; i < 5; ++i) { + arr.push(class C { + test = i; + }); + class A { + test = i * 2; + } + arr.push(A); +} +arr.forEach(clazz => console.log(new clazz().test)); + + +//// [initializerReferencingBlockScopeBinding.js] +var arr = []; +var _loop_1 = function (i) { + arr.push(/** @class */ (function () { + function C() { + this.test = i; + } + return C; + }())); + var A = /** @class */ (function () { + function A() { + this.test = i * 2; + } + return A; + }()); + arr.push(A); +}; +for (var i = 0; i < 5; ++i) { + _loop_1(i); +} +arr.forEach(function (clazz) { return console.log(new clazz().test); }); diff --git a/tests/baselines/reference/initializerReferencingBlockScopeBinding.symbols b/tests/baselines/reference/initializerReferencingBlockScopeBinding.symbols new file mode 100644 index 0000000000000..ac0564ad4c9cb --- /dev/null +++ b/tests/baselines/reference/initializerReferencingBlockScopeBinding.symbols @@ -0,0 +1,43 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingBlockScopeBinding.ts === +let arr = []; +>arr : Symbol(arr, Decl(initializerReferencingBlockScopeBinding.ts, 0, 3)) + +for (let i = 0; i < 5; ++i) { +>i : Symbol(i, Decl(initializerReferencingBlockScopeBinding.ts, 1, 8)) +>i : Symbol(i, Decl(initializerReferencingBlockScopeBinding.ts, 1, 8)) +>i : Symbol(i, Decl(initializerReferencingBlockScopeBinding.ts, 1, 8)) + + arr.push(class C { +>arr.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>arr : Symbol(arr, Decl(initializerReferencingBlockScopeBinding.ts, 0, 3)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>C : Symbol(C, Decl(initializerReferencingBlockScopeBinding.ts, 2, 13)) + + test = i; +>test : Symbol(C.test, Decl(initializerReferencingBlockScopeBinding.ts, 2, 22)) +>i : Symbol(i, Decl(initializerReferencingBlockScopeBinding.ts, 1, 8)) + + }); + class A { +>A : Symbol(A, Decl(initializerReferencingBlockScopeBinding.ts, 4, 7)) + + test = i * 2; +>test : Symbol(A.test, Decl(initializerReferencingBlockScopeBinding.ts, 5, 13)) +>i : Symbol(i, Decl(initializerReferencingBlockScopeBinding.ts, 1, 8)) + } + arr.push(A); +>arr.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>arr : Symbol(arr, Decl(initializerReferencingBlockScopeBinding.ts, 0, 3)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>A : Symbol(A, Decl(initializerReferencingBlockScopeBinding.ts, 4, 7)) +} +arr.forEach(clazz => console.log(new clazz().test)); +>arr.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>arr : Symbol(arr, Decl(initializerReferencingBlockScopeBinding.ts, 0, 3)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>clazz : Symbol(clazz, Decl(initializerReferencingBlockScopeBinding.ts, 10, 12)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>clazz : Symbol(clazz, Decl(initializerReferencingBlockScopeBinding.ts, 10, 12)) + diff --git a/tests/baselines/reference/initializerReferencingBlockScopeBinding.types b/tests/baselines/reference/initializerReferencingBlockScopeBinding.types new file mode 100644 index 0000000000000..72a8d4dacbefb --- /dev/null +++ b/tests/baselines/reference/initializerReferencingBlockScopeBinding.types @@ -0,0 +1,59 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingBlockScopeBinding.ts === +let arr = []; +>arr : any[] +>[] : undefined[] + +for (let i = 0; i < 5; ++i) { +>i : number +>0 : 0 +>i < 5 : boolean +>i : number +>5 : 5 +>++i : number +>i : number + + arr.push(class C { +>arr.push(class C { test = i; }) : number +>arr.push : (...items: any[]) => number +>arr : any[] +>push : (...items: any[]) => number +>class C { test = i; } : typeof C +>C : typeof C + + test = i; +>test : number +>i : number + + }); + class A { +>A : A + + test = i * 2; +>test : number +>i * 2 : number +>i : number +>2 : 2 + } + arr.push(A); +>arr.push(A) : number +>arr.push : (...items: any[]) => number +>arr : any[] +>push : (...items: any[]) => number +>A : typeof A +} +arr.forEach(clazz => console.log(new clazz().test)); +>arr.forEach(clazz => console.log(new clazz().test)) : void +>arr.forEach : (callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any) => void +>arr : any[] +>forEach : (callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any) => void +>clazz => console.log(new clazz().test) : (clazz: any) => void +>clazz : any +>console.log(new clazz().test) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>new clazz().test : any +>new clazz() : any +>clazz : any +>test : any + diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingBlockScopeBinding.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingBlockScopeBinding.ts new file mode 100644 index 0000000000000..06c023580df62 --- /dev/null +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingBlockScopeBinding.ts @@ -0,0 +1,11 @@ +let arr = []; +for (let i = 0; i < 5; ++i) { + arr.push(class C { + test = i; + }); + class A { + test = i * 2; + } + arr.push(A); +} +arr.forEach(clazz => console.log(new clazz().test));