Skip to content

Commit ea054f7

Browse files
committed
Merge pull request #5412 from Microsoft/classesInLoop
treat local classes as block scoped variables
2 parents 847a074 + 654befa commit ea054f7

File tree

9 files changed

+244
-13
lines changed

9 files changed

+244
-13
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6581,26 +6581,34 @@ namespace ts {
65816581

65826582
function checkBlockScopedBindingCapturedInLoop(node: Identifier, symbol: Symbol): void {
65836583
if (languageVersion >= ScriptTarget.ES6 ||
6584-
(symbol.flags & SymbolFlags.BlockScopedVariable) === 0 ||
6584+
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
65856585
symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) {
65866586
return;
65876587
}
65886588

6589-
// - check if binding is used in some function
6590-
// (stop the walk when reaching container of binding declaration)
6591-
// - if first check succeeded - check if variable is declared inside the loop
6589+
// 1. walk from the use site up to the declaration and check
6590+
// if there is anything function like between declaration and use-site (is binding/class is captured in function).
6591+
// 2. walk from the declaration up to the boundary of lexical environment and check
6592+
// if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement)
65926593

6593-
// nesting structure:
6594-
// (variable declaration or binding element) -> variable declaration list -> container
6595-
let container: Node = symbol.valueDeclaration;
6596-
while (container.kind !== SyntaxKind.VariableDeclarationList) {
6597-
container = container.parent;
6594+
let container: Node;
6595+
if (symbol.flags & SymbolFlags.Class) {
6596+
// get parent of class declaration
6597+
container = getClassLikeDeclarationOfSymbol(symbol).parent;
65986598
}
6599-
// get the parent of variable declaration list
6600-
container = container.parent;
6601-
if (container.kind === SyntaxKind.VariableStatement) {
6602-
// if parent is variable statement - get its parent
6599+
else {
6600+
// nesting structure:
6601+
// (variable declaration or binding element) -> variable declaration list -> container
6602+
container = symbol.valueDeclaration;
6603+
while (container.kind !== SyntaxKind.VariableDeclarationList) {
6604+
container = container.parent;
6605+
}
6606+
// get the parent of variable declaration list
66036607
container = container.parent;
6608+
if (container.kind === SyntaxKind.VariableStatement) {
6609+
// if parent is variable statement - get its parent
6610+
container = container.parent;
6611+
}
66046612
}
66056613

66066614
let inFunction = isInsideFunction(node.parent, container);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [localClassesInLoop.ts]
2+
declare function use(a: any);
3+
4+
"use strict"
5+
var data = [];
6+
for (let x = 0; x < 2; ++x) {
7+
class C { }
8+
data.push(() => C);
9+
}
10+
11+
use(data[0]() === data[1]());
12+
13+
//// [localClassesInLoop.js]
14+
"use strict";
15+
var data = [];
16+
var _loop_1 = function(x) {
17+
var C = (function () {
18+
function C() {
19+
}
20+
return C;
21+
})();
22+
data.push(function () { return C; });
23+
};
24+
for (var x = 0; x < 2; ++x) {
25+
_loop_1(x);
26+
}
27+
use(data[0]() === data[1]());
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/localClassesInLoop.ts ===
2+
declare function use(a: any);
3+
>use : Symbol(use, Decl(localClassesInLoop.ts, 0, 0))
4+
>a : Symbol(a, Decl(localClassesInLoop.ts, 0, 21))
5+
6+
"use strict"
7+
var data = [];
8+
>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3))
9+
10+
for (let x = 0; x < 2; ++x) {
11+
>x : Symbol(x, Decl(localClassesInLoop.ts, 4, 8))
12+
>x : Symbol(x, Decl(localClassesInLoop.ts, 4, 8))
13+
>x : Symbol(x, Decl(localClassesInLoop.ts, 4, 8))
14+
15+
class C { }
16+
>C : Symbol(C, Decl(localClassesInLoop.ts, 4, 29))
17+
18+
data.push(() => C);
19+
>data.push : Symbol(Array.push, Decl(lib.d.ts, --, --))
20+
>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3))
21+
>push : Symbol(Array.push, Decl(lib.d.ts, --, --))
22+
>C : Symbol(C, Decl(localClassesInLoop.ts, 4, 29))
23+
}
24+
25+
use(data[0]() === data[1]());
26+
>use : Symbol(use, Decl(localClassesInLoop.ts, 0, 0))
27+
>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3))
28+
>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3))
29+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
=== tests/cases/compiler/localClassesInLoop.ts ===
2+
declare function use(a: any);
3+
>use : (a: any) => any
4+
>a : any
5+
6+
"use strict"
7+
>"use strict" : string
8+
9+
var data = [];
10+
>data : any[]
11+
>[] : undefined[]
12+
13+
for (let x = 0; x < 2; ++x) {
14+
>x : number
15+
>0 : number
16+
>x < 2 : boolean
17+
>x : number
18+
>2 : number
19+
>++x : number
20+
>x : number
21+
22+
class C { }
23+
>C : C
24+
25+
data.push(() => C);
26+
>data.push(() => C) : number
27+
>data.push : (...items: any[]) => number
28+
>data : any[]
29+
>push : (...items: any[]) => number
30+
>() => C : () => typeof C
31+
>C : typeof C
32+
}
33+
34+
use(data[0]() === data[1]());
35+
>use(data[0]() === data[1]()) : any
36+
>use : (a: any) => any
37+
>data[0]() === data[1]() : boolean
38+
>data[0]() : any
39+
>data[0] : any
40+
>data : any[]
41+
>0 : number
42+
>data[1]() : any
43+
>data[1] : any
44+
>data : any[]
45+
>1 : number
46+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// [localClassesInLoop_ES6.ts]
2+
3+
declare function use(a: any);
4+
5+
"use strict"
6+
var data = [];
7+
for (let x = 0; x < 2; ++x) {
8+
class C { }
9+
data.push(() => C);
10+
}
11+
12+
use(data[0]() === data[1]());
13+
14+
//// [localClassesInLoop_ES6.js]
15+
"use strict";
16+
var data = [];
17+
for (let x = 0; x < 2; ++x) {
18+
class C {
19+
}
20+
data.push(() => C);
21+
}
22+
use(data[0]() === data[1]());
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/compiler/localClassesInLoop_ES6.ts ===
2+
3+
declare function use(a: any);
4+
>use : Symbol(use, Decl(localClassesInLoop_ES6.ts, 0, 0))
5+
>a : Symbol(a, Decl(localClassesInLoop_ES6.ts, 1, 21))
6+
7+
"use strict"
8+
var data = [];
9+
>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3))
10+
11+
for (let x = 0; x < 2; ++x) {
12+
>x : Symbol(x, Decl(localClassesInLoop_ES6.ts, 5, 8))
13+
>x : Symbol(x, Decl(localClassesInLoop_ES6.ts, 5, 8))
14+
>x : Symbol(x, Decl(localClassesInLoop_ES6.ts, 5, 8))
15+
16+
class C { }
17+
>C : Symbol(C, Decl(localClassesInLoop_ES6.ts, 5, 29))
18+
19+
data.push(() => C);
20+
>data.push : Symbol(Array.push, Decl(lib.d.ts, --, --))
21+
>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3))
22+
>push : Symbol(Array.push, Decl(lib.d.ts, --, --))
23+
>C : Symbol(C, Decl(localClassesInLoop_ES6.ts, 5, 29))
24+
}
25+
26+
use(data[0]() === data[1]());
27+
>use : Symbol(use, Decl(localClassesInLoop_ES6.ts, 0, 0))
28+
>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3))
29+
>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3))
30+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/compiler/localClassesInLoop_ES6.ts ===
2+
3+
declare function use(a: any);
4+
>use : (a: any) => any
5+
>a : any
6+
7+
"use strict"
8+
>"use strict" : string
9+
10+
var data = [];
11+
>data : any[]
12+
>[] : undefined[]
13+
14+
for (let x = 0; x < 2; ++x) {
15+
>x : number
16+
>0 : number
17+
>x < 2 : boolean
18+
>x : number
19+
>2 : number
20+
>++x : number
21+
>x : number
22+
23+
class C { }
24+
>C : C
25+
26+
data.push(() => C);
27+
>data.push(() => C) : number
28+
>data.push : (...items: any[]) => number
29+
>data : any[]
30+
>push : (...items: any[]) => number
31+
>() => C : () => typeof C
32+
>C : typeof C
33+
}
34+
35+
use(data[0]() === data[1]());
36+
>use(data[0]() === data[1]()) : any
37+
>use : (a: any) => any
38+
>data[0]() === data[1]() : boolean
39+
>data[0]() : any
40+
>data[0] : any
41+
>data : any[]
42+
>0 : number
43+
>data[1]() : any
44+
>data[1] : any
45+
>data : any[]
46+
>1 : number
47+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
declare function use(a: any);
2+
3+
"use strict"
4+
var data = [];
5+
for (let x = 0; x < 2; ++x) {
6+
class C { }
7+
data.push(() => C);
8+
}
9+
10+
use(data[0]() === data[1]());
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @target: ES6
2+
3+
declare function use(a: any);
4+
5+
"use strict"
6+
var data = [];
7+
for (let x = 0; x < 2; ++x) {
8+
class C { }
9+
data.push(() => C);
10+
}
11+
12+
use(data[0]() === data[1]());

0 commit comments

Comments
 (0)