Skip to content

Commit bc91292

Browse files
authored
Fix Computed Property Name Bindings (#17)
* Fix microsoft#27864 Signed-off-by: Joseph Watts <jwatts43@bloomberg.net> * Tests for computed field scope fix Signed-off-by: Joseph Watts <jwatts43@bloomberg.net>
1 parent aa3734c commit bc91292

19 files changed

+350
-9
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17834,6 +17834,23 @@ namespace ts {
1783417834
const links = getNodeLinks(node.expression);
1783517835
if (!links.resolvedType) {
1783617836
links.resolvedType = checkExpression(node.expression);
17837+
17838+
if (isPropertyDeclaration(node.parent) && isClassLike(node.parent.parent)) {
17839+
const container = getEnclosingBlockScopeContainer(node);
17840+
let current = container;
17841+
let containedInIterationStatement = false;
17842+
while (current && !nodeStartsNewLexicalEnvironment(current)) {
17843+
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
17844+
containedInIterationStatement = true;
17845+
break;
17846+
}
17847+
current = current.parent;
17848+
}
17849+
if (containedInIterationStatement) {
17850+
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
17851+
}
17852+
links.flags |= NodeCheckFlags.BlockScopedBindingInLoop;
17853+
}
1783717854
// This will allow types number, string, symbol or any. It will also allow enums, the unknown
1783817855
// type, and any union of these types (like string | number).
1783917856
if (links.resolvedType.flags & TypeFlags.Nullable ||

src/compiler/transformer.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ namespace ts {
9292
let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[];
9393
let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = [];
9494
let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = [];
95+
let lexicalEnvironmentScopingStack: LexicalEnvironmentScoping[] = [];
96+
let lexicalEnvironmentScoping: LexicalEnvironmentScoping;
9597
let lexicalEnvironmentStackOffset = 0;
9698
let lexicalEnvironmentSuspended = false;
9799
let emitHelpers: EmitHelper[] | undefined;
@@ -266,7 +268,7 @@ namespace ts {
266268
* Starts a new lexical environment. Any existing hoisted variable or function declarations
267269
* are pushed onto a stack, and the related storage variables are reset.
268270
*/
269-
function startLexicalEnvironment(): void {
271+
function startLexicalEnvironment(scoping: LexicalEnvironmentScoping = LexicalEnvironmentScoping.Function): void {
270272
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
271273
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
272274
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
@@ -275,11 +277,13 @@ namespace ts {
275277
// stack size variable. This allows us to reuse existing array slots we've
276278
// already allocated between transformations to avoid allocation and GC overhead during
277279
// transformation.
280+
lexicalEnvironmentScopingStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentScoping;
278281
lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations;
279282
lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations;
280283
lexicalEnvironmentStackOffset++;
281284
lexicalEnvironmentVariableDeclarations = undefined!;
282285
lexicalEnvironmentFunctionDeclarations = undefined!;
286+
lexicalEnvironmentScoping = scoping;
283287
}
284288

285289
/** Suspends the current lexical environment, usually after visiting a parameter list. */
@@ -316,7 +320,10 @@ namespace ts {
316320
if (lexicalEnvironmentVariableDeclarations) {
317321
const statement = createVariableStatement(
318322
/*modifiers*/ undefined,
319-
createVariableDeclarationList(lexicalEnvironmentVariableDeclarations)
323+
createVariableDeclarationList(
324+
lexicalEnvironmentVariableDeclarations,
325+
lexicalEnvironmentScoping === LexicalEnvironmentScoping.Block ? NodeFlags.Let : undefined
326+
)
320327
);
321328

322329
if (!statements) {
@@ -332,9 +339,11 @@ namespace ts {
332339
lexicalEnvironmentStackOffset--;
333340
lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset];
334341
lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset];
342+
lexicalEnvironmentScoping = lexicalEnvironmentScopingStack[lexicalEnvironmentStackOffset];
335343
if (lexicalEnvironmentStackOffset === 0) {
336344
lexicalEnvironmentVariableDeclarationsStack = [];
337345
lexicalEnvironmentFunctionDeclarationsStack = [];
346+
lexicalEnvironmentScopingStack = [];
338347
}
339348
return statements;
340349
}
@@ -366,6 +375,7 @@ namespace ts {
366375
lexicalEnvironmentVariableDeclarationsStack = undefined!;
367376
lexicalEnvironmentFunctionDeclarations = undefined!;
368377
lexicalEnvironmentFunctionDeclarationsStack = undefined!;
378+
lexicalEnvironmentScopingStack = undefined!;
369379
onSubstituteNode = undefined!;
370380
onEmitNode = undefined!;
371381
emitHelpers = undefined;

src/compiler/transformers/ts.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,10 @@ namespace ts {
208208
* @param node The node to visit.
209209
*/
210210
function visitorWorker(node: Node): VisitResult<Node> {
211-
if (node.transformFlags & TransformFlags.TypeScript) {
211+
if (node.kind === SyntaxKind.Block && node.transformFlags & TransformFlags.AssertTypeScript) {
212+
return visitBlock(node as Block);
213+
}
214+
else if (node.transformFlags & TransformFlags.TypeScript) {
212215
// This node is explicitly marked as TypeScript, so we should transform the node.
213216
return visitTypeScript(node);
214217
}
@@ -560,6 +563,19 @@ namespace ts {
560563
}
561564
}
562565

566+
function visitBlock(node: Block): Block {
567+
startLexicalEnvironment(LexicalEnvironmentScoping.Block);
568+
node = visitEachChild(node, visitor, context);
569+
const declarations = endLexicalEnvironment();
570+
if (some(declarations)) {
571+
return updateBlock(
572+
node,
573+
mergeLexicalEnvironment(node.statements, declarations)
574+
);
575+
}
576+
return node;
577+
}
578+
563579
function visitSourceFile(node: SourceFile) {
564580
const alwaysStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") &&
565581
!(isExternalModule(node) && moduleKind >= ModuleKind.ES2015) &&

src/compiler/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5241,6 +5241,11 @@ namespace ts {
52415241
writeFile: WriteFileCallback;
52425242
}
52435243

5244+
export const enum LexicalEnvironmentScoping {
5245+
Function,
5246+
Block
5247+
}
5248+
52445249
export interface TransformationContext {
52455250
/*@internal*/ getEmitResolver(): EmitResolver;
52465251
/*@internal*/ getEmitHost(): EmitHost;
@@ -5249,7 +5254,7 @@ namespace ts {
52495254
getCompilerOptions(): CompilerOptions;
52505255

52515256
/** Starts a new lexical environment. */
5252-
startLexicalEnvironment(): void;
5257+
startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void;
52535258

52545259
/** Suspends the current lexical environment, usually after visiting a parameter list. */
52555260
suspendLexicalEnvironment(): void;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2756,11 +2756,15 @@ declare namespace ts {
27562756
Unspecified = 4,
27572757
EmbeddedStatement = 5
27582758
}
2759+
enum LexicalEnvironmentScoping {
2760+
Function = 0,
2761+
Block = 1
2762+
}
27592763
interface TransformationContext {
27602764
/** Gets the compiler options supplied to the transformer. */
27612765
getCompilerOptions(): CompilerOptions;
27622766
/** Starts a new lexical environment. */
2763-
startLexicalEnvironment(): void;
2767+
startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void;
27642768
/** Suspends the current lexical environment, usually after visiting a parameter list. */
27652769
suspendLexicalEnvironment(): void;
27662770
/** Resumes a suspended lexical environment, usually before visiting a function body. */

tests/baselines/reference/api/typescript.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2756,11 +2756,15 @@ declare namespace ts {
27562756
Unspecified = 4,
27572757
EmbeddedStatement = 5
27582758
}
2759+
enum LexicalEnvironmentScoping {
2760+
Function = 0,
2761+
Block = 1
2762+
}
27592763
interface TransformationContext {
27602764
/** Gets the compiler options supplied to the transformer. */
27612765
getCompilerOptions(): CompilerOptions;
27622766
/** Starts a new lexical environment. */
2763-
startLexicalEnvironment(): void;
2767+
startLexicalEnvironment(scoping?: LexicalEnvironmentScoping): void;
27642768
/** Suspends the current lexical environment, usually after visiting a parameter list. */
27652769
suspendLexicalEnvironment(): void;
27662770
/** Resumes a suspended lexical environment, usually before visiting a function body. */

tests/baselines/reference/classBlockScoping.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ function f(b: boolean) {
3535

3636
//// [classBlockScoping.js]
3737
function f(b) {
38-
var _a;
3938
var Foo;
4039
if (b) {
40+
var _a = void 0;
4141
Foo = (_a = /** @class */ (function () {
4242
function Foo() {
4343
}

tests/baselines/reference/classExpressionWithStaticProperties3.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) {
1010
arr.forEach(C => console.log(C.y()));
1111

1212
//// [classExpressionWithStaticProperties3.js]
13-
var _a;
1413
var arr = [];
1514
var _loop_1 = function (i) {
15+
var _a = void 0;
1616
arr.push((_a = /** @class */ (function () {
1717
function C() {
1818
}

tests/baselines/reference/classExpressionWithStaticPropertiesES63.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ for (let i = 0; i < 3; i++) {
1010
arr.forEach(C => console.log(C.y()));
1111

1212
//// [classExpressionWithStaticPropertiesES63.js]
13-
var _a;
1413
const arr = [];
1514
for (let i = 0; i < 3; i++) {
15+
let _a;
1616
arr.push((_a = class C {
1717
},
1818
_a.x = i,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
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.
2+
3+
4+
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts (1 errors) ====
5+
const classes = [];
6+
for (let i = 0; i <= 10; ++i) {
7+
classes.push(
8+
class A {
9+
[i] = "my property";
10+
~~~
11+
!!! 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.
12+
}
13+
);
14+
}
15+
for (const clazz of classes) {
16+
console.log(Object.getOwnPropertyNames(new clazz()));
17+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [computedPropertyNames52_ES5.ts]
2+
const classes = [];
3+
for (let i = 0; i <= 10; ++i) {
4+
classes.push(
5+
class A {
6+
[i] = "my property";
7+
}
8+
);
9+
}
10+
for (const clazz of classes) {
11+
console.log(Object.getOwnPropertyNames(new clazz()));
12+
}
13+
14+
//// [computedPropertyNames52_ES5.js]
15+
var classes = [];
16+
var _loop_1 = function (i) {
17+
var _a = void 0, _b = void 0;
18+
classes.push((_b = /** @class */ (function () {
19+
function A() {
20+
this[_a] = "my property";
21+
}
22+
return A;
23+
}()),
24+
_a = i,
25+
_b));
26+
};
27+
for (var i = 0; i <= 10; ++i) {
28+
_loop_1(i);
29+
}
30+
for (var _i = 0, classes_1 = classes; _i < classes_1.length; _i++) {
31+
var clazz = classes_1[_i];
32+
console.log(Object.getOwnPropertyNames(new clazz()));
33+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts ===
2+
const classes = [];
3+
>classes : Symbol(classes, Decl(computedPropertyNames52_ES5.ts, 0, 5))
4+
5+
for (let i = 0; i <= 10; ++i) {
6+
>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8))
7+
>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8))
8+
>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8))
9+
10+
classes.push(
11+
>classes.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
12+
>classes : Symbol(classes, Decl(computedPropertyNames52_ES5.ts, 0, 5))
13+
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
14+
15+
class A {
16+
>A : Symbol(A, Decl(computedPropertyNames52_ES5.ts, 2, 17))
17+
18+
[i] = "my property";
19+
>[i] : Symbol(A[i], Decl(computedPropertyNames52_ES5.ts, 3, 17))
20+
>i : Symbol(i, Decl(computedPropertyNames52_ES5.ts, 1, 8))
21+
}
22+
);
23+
}
24+
for (const clazz of classes) {
25+
>clazz : Symbol(clazz, Decl(computedPropertyNames52_ES5.ts, 8, 10))
26+
>classes : Symbol(classes, Decl(computedPropertyNames52_ES5.ts, 0, 5))
27+
28+
console.log(Object.getOwnPropertyNames(new clazz()));
29+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
30+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
31+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
32+
>Object.getOwnPropertyNames : Symbol(ObjectConstructor.getOwnPropertyNames, Decl(lib.es5.d.ts, --, --))
33+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
34+
>getOwnPropertyNames : Symbol(ObjectConstructor.getOwnPropertyNames, Decl(lib.es5.d.ts, --, --))
35+
>clazz : Symbol(clazz, Decl(computedPropertyNames52_ES5.ts, 8, 10))
36+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES5.ts ===
2+
const classes = [];
3+
>classes : any[]
4+
>[] : undefined[]
5+
6+
for (let i = 0; i <= 10; ++i) {
7+
>i : number
8+
>0 : 0
9+
>i <= 10 : boolean
10+
>i : number
11+
>10 : 10
12+
>++i : number
13+
>i : number
14+
15+
classes.push(
16+
>classes.push( class A { [i] = "my property"; } ) : number
17+
>classes.push : (...items: any[]) => number
18+
>classes : any[]
19+
>push : (...items: any[]) => number
20+
21+
class A {
22+
>class A { [i] = "my property"; } : typeof A
23+
>A : typeof A
24+
25+
[i] = "my property";
26+
>[i] : string
27+
>i : number
28+
>"my property" : "my property"
29+
}
30+
);
31+
}
32+
for (const clazz of classes) {
33+
>clazz : any
34+
>classes : any[]
35+
36+
console.log(Object.getOwnPropertyNames(new clazz()));
37+
>console.log(Object.getOwnPropertyNames(new clazz())) : void
38+
>console.log : (message?: any, ...optionalParams: any[]) => void
39+
>console : Console
40+
>log : (message?: any, ...optionalParams: any[]) => void
41+
>Object.getOwnPropertyNames(new clazz()) : string[]
42+
>Object.getOwnPropertyNames : (o: any) => string[]
43+
>Object : ObjectConstructor
44+
>getOwnPropertyNames : (o: any) => string[]
45+
>new clazz() : any
46+
>clazz : any
47+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
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.
2+
3+
4+
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames52_ES6.ts (1 errors) ====
5+
const classes = [];
6+
for (let i = 0; i <= 10; ++i) {
7+
classes.push(
8+
class A {
9+
[i] = "my property";
10+
~~~
11+
!!! 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.
12+
}
13+
);
14+
}
15+
for (const clazz of classes) {
16+
console.log(Object.getOwnPropertyNames(new clazz()));
17+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [computedPropertyNames52_ES6.ts]
2+
const classes = [];
3+
for (let i = 0; i <= 10; ++i) {
4+
classes.push(
5+
class A {
6+
[i] = "my property";
7+
}
8+
);
9+
}
10+
for (const clazz of classes) {
11+
console.log(Object.getOwnPropertyNames(new clazz()));
12+
}
13+
14+
//// [computedPropertyNames52_ES6.js]
15+
const classes = [];
16+
for (let i = 0; i <= 10; ++i) {
17+
let _a, _b;
18+
classes.push((_b = class A {
19+
constructor() {
20+
this[_a] = "my property";
21+
}
22+
},
23+
_a = i,
24+
_b));
25+
}
26+
for (const clazz of classes) {
27+
console.log(Object.getOwnPropertyNames(new clazz()));
28+
}

0 commit comments

Comments
 (0)