From 654befa781bdf18f597a6483236d2a82f54d379e Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 26 Oct 2015 22:18:04 -0700 Subject: [PATCH] treat local classes as block scoped variables --- src/compiler/checker.ts | 34 +++++++++----- .../baselines/reference/localClassesInLoop.js | 27 +++++++++++ .../reference/localClassesInLoop.symbols | 29 ++++++++++++ .../reference/localClassesInLoop.types | 46 ++++++++++++++++++ .../reference/localClassesInLoop_ES6.js | 22 +++++++++ .../reference/localClassesInLoop_ES6.symbols | 30 ++++++++++++ .../reference/localClassesInLoop_ES6.types | 47 +++++++++++++++++++ tests/cases/compiler/localClassesInLoop.ts | 10 ++++ .../cases/compiler/localClassesInLoop_ES6.ts | 12 +++++ 9 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 tests/baselines/reference/localClassesInLoop.js create mode 100644 tests/baselines/reference/localClassesInLoop.symbols create mode 100644 tests/baselines/reference/localClassesInLoop.types create mode 100644 tests/baselines/reference/localClassesInLoop_ES6.js create mode 100644 tests/baselines/reference/localClassesInLoop_ES6.symbols create mode 100644 tests/baselines/reference/localClassesInLoop_ES6.types create mode 100644 tests/cases/compiler/localClassesInLoop.ts create mode 100644 tests/cases/compiler/localClassesInLoop_ES6.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d01446536e..f7236a65710 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6581,26 +6581,34 @@ namespace ts { function checkBlockScopedBindingCapturedInLoop(node: Identifier, symbol: Symbol): void { if (languageVersion >= ScriptTarget.ES6 || - (symbol.flags & SymbolFlags.BlockScopedVariable) === 0 || + (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { return; } - // - check if binding is used in some function - // (stop the walk when reaching container of binding declaration) - // - if first check succeeded - check if variable is declared inside the loop + // 1. walk from the use site up to the declaration and check + // if there is anything function like between declaration and use-site (is binding/class is captured in function). + // 2. walk from the declaration up to the boundary of lexical environment and check + // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) - // nesting structure: - // (variable declaration or binding element) -> variable declaration list -> container - let container: Node = symbol.valueDeclaration; - while (container.kind !== SyntaxKind.VariableDeclarationList) { - container = container.parent; + let container: Node; + if (symbol.flags & SymbolFlags.Class) { + // get parent of class declaration + container = getClassLikeDeclarationOfSymbol(symbol).parent; } - // get the parent of variable declaration list - container = container.parent; - if (container.kind === SyntaxKind.VariableStatement) { - // if parent is variable statement - get its parent + else { + // nesting structure: + // (variable declaration or binding element) -> variable declaration list -> container + container = symbol.valueDeclaration; + while (container.kind !== SyntaxKind.VariableDeclarationList) { + container = container.parent; + } + // get the parent of variable declaration list container = container.parent; + if (container.kind === SyntaxKind.VariableStatement) { + // if parent is variable statement - get its parent + container = container.parent; + } } let inFunction = isInsideFunction(node.parent, container); diff --git a/tests/baselines/reference/localClassesInLoop.js b/tests/baselines/reference/localClassesInLoop.js new file mode 100644 index 00000000000..7912b2ef0d6 --- /dev/null +++ b/tests/baselines/reference/localClassesInLoop.js @@ -0,0 +1,27 @@ +//// [localClassesInLoop.ts] +declare function use(a: any); + +"use strict" +var data = []; +for (let x = 0; x < 2; ++x) { + class C { } + data.push(() => C); +} + +use(data[0]() === data[1]()); + +//// [localClassesInLoop.js] +"use strict"; +var data = []; +var _loop_1 = function(x) { + var C = (function () { + function C() { + } + return C; + })(); + data.push(function () { return C; }); +}; +for (var x = 0; x < 2; ++x) { + _loop_1(x); +} +use(data[0]() === data[1]()); diff --git a/tests/baselines/reference/localClassesInLoop.symbols b/tests/baselines/reference/localClassesInLoop.symbols new file mode 100644 index 00000000000..6862525768e --- /dev/null +++ b/tests/baselines/reference/localClassesInLoop.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/localClassesInLoop.ts === +declare function use(a: any); +>use : Symbol(use, Decl(localClassesInLoop.ts, 0, 0)) +>a : Symbol(a, Decl(localClassesInLoop.ts, 0, 21)) + +"use strict" +var data = []; +>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3)) + +for (let x = 0; x < 2; ++x) { +>x : Symbol(x, Decl(localClassesInLoop.ts, 4, 8)) +>x : Symbol(x, Decl(localClassesInLoop.ts, 4, 8)) +>x : Symbol(x, Decl(localClassesInLoop.ts, 4, 8)) + + class C { } +>C : Symbol(C, Decl(localClassesInLoop.ts, 4, 29)) + + data.push(() => C); +>data.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>C : Symbol(C, Decl(localClassesInLoop.ts, 4, 29)) +} + +use(data[0]() === data[1]()); +>use : Symbol(use, Decl(localClassesInLoop.ts, 0, 0)) +>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3)) +>data : Symbol(data, Decl(localClassesInLoop.ts, 3, 3)) + diff --git a/tests/baselines/reference/localClassesInLoop.types b/tests/baselines/reference/localClassesInLoop.types new file mode 100644 index 00000000000..f1c06f93463 --- /dev/null +++ b/tests/baselines/reference/localClassesInLoop.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/localClassesInLoop.ts === +declare function use(a: any); +>use : (a: any) => any +>a : any + +"use strict" +>"use strict" : string + +var data = []; +>data : any[] +>[] : undefined[] + +for (let x = 0; x < 2; ++x) { +>x : number +>0 : number +>x < 2 : boolean +>x : number +>2 : number +>++x : number +>x : number + + class C { } +>C : C + + data.push(() => C); +>data.push(() => C) : number +>data.push : (...items: any[]) => number +>data : any[] +>push : (...items: any[]) => number +>() => C : () => typeof C +>C : typeof C +} + +use(data[0]() === data[1]()); +>use(data[0]() === data[1]()) : any +>use : (a: any) => any +>data[0]() === data[1]() : boolean +>data[0]() : any +>data[0] : any +>data : any[] +>0 : number +>data[1]() : any +>data[1] : any +>data : any[] +>1 : number + diff --git a/tests/baselines/reference/localClassesInLoop_ES6.js b/tests/baselines/reference/localClassesInLoop_ES6.js new file mode 100644 index 00000000000..b9e7dccff1f --- /dev/null +++ b/tests/baselines/reference/localClassesInLoop_ES6.js @@ -0,0 +1,22 @@ +//// [localClassesInLoop_ES6.ts] + +declare function use(a: any); + +"use strict" +var data = []; +for (let x = 0; x < 2; ++x) { + class C { } + data.push(() => C); +} + +use(data[0]() === data[1]()); + +//// [localClassesInLoop_ES6.js] +"use strict"; +var data = []; +for (let x = 0; x < 2; ++x) { + class C { + } + data.push(() => C); +} +use(data[0]() === data[1]()); diff --git a/tests/baselines/reference/localClassesInLoop_ES6.symbols b/tests/baselines/reference/localClassesInLoop_ES6.symbols new file mode 100644 index 00000000000..17e8102ad05 --- /dev/null +++ b/tests/baselines/reference/localClassesInLoop_ES6.symbols @@ -0,0 +1,30 @@ +=== tests/cases/compiler/localClassesInLoop_ES6.ts === + +declare function use(a: any); +>use : Symbol(use, Decl(localClassesInLoop_ES6.ts, 0, 0)) +>a : Symbol(a, Decl(localClassesInLoop_ES6.ts, 1, 21)) + +"use strict" +var data = []; +>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3)) + +for (let x = 0; x < 2; ++x) { +>x : Symbol(x, Decl(localClassesInLoop_ES6.ts, 5, 8)) +>x : Symbol(x, Decl(localClassesInLoop_ES6.ts, 5, 8)) +>x : Symbol(x, Decl(localClassesInLoop_ES6.ts, 5, 8)) + + class C { } +>C : Symbol(C, Decl(localClassesInLoop_ES6.ts, 5, 29)) + + data.push(() => C); +>data.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>C : Symbol(C, Decl(localClassesInLoop_ES6.ts, 5, 29)) +} + +use(data[0]() === data[1]()); +>use : Symbol(use, Decl(localClassesInLoop_ES6.ts, 0, 0)) +>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3)) +>data : Symbol(data, Decl(localClassesInLoop_ES6.ts, 4, 3)) + diff --git a/tests/baselines/reference/localClassesInLoop_ES6.types b/tests/baselines/reference/localClassesInLoop_ES6.types new file mode 100644 index 00000000000..518e75f4afa --- /dev/null +++ b/tests/baselines/reference/localClassesInLoop_ES6.types @@ -0,0 +1,47 @@ +=== tests/cases/compiler/localClassesInLoop_ES6.ts === + +declare function use(a: any); +>use : (a: any) => any +>a : any + +"use strict" +>"use strict" : string + +var data = []; +>data : any[] +>[] : undefined[] + +for (let x = 0; x < 2; ++x) { +>x : number +>0 : number +>x < 2 : boolean +>x : number +>2 : number +>++x : number +>x : number + + class C { } +>C : C + + data.push(() => C); +>data.push(() => C) : number +>data.push : (...items: any[]) => number +>data : any[] +>push : (...items: any[]) => number +>() => C : () => typeof C +>C : typeof C +} + +use(data[0]() === data[1]()); +>use(data[0]() === data[1]()) : any +>use : (a: any) => any +>data[0]() === data[1]() : boolean +>data[0]() : any +>data[0] : any +>data : any[] +>0 : number +>data[1]() : any +>data[1] : any +>data : any[] +>1 : number + diff --git a/tests/cases/compiler/localClassesInLoop.ts b/tests/cases/compiler/localClassesInLoop.ts new file mode 100644 index 00000000000..1d5f84d2843 --- /dev/null +++ b/tests/cases/compiler/localClassesInLoop.ts @@ -0,0 +1,10 @@ +declare function use(a: any); + +"use strict" +var data = []; +for (let x = 0; x < 2; ++x) { + class C { } + data.push(() => C); +} + +use(data[0]() === data[1]()); \ No newline at end of file diff --git a/tests/cases/compiler/localClassesInLoop_ES6.ts b/tests/cases/compiler/localClassesInLoop_ES6.ts new file mode 100644 index 00000000000..6f0c2c6b33a --- /dev/null +++ b/tests/cases/compiler/localClassesInLoop_ES6.ts @@ -0,0 +1,12 @@ +// @target: ES6 + +declare function use(a: any); + +"use strict" +var data = []; +for (let x = 0; x < 2; ++x) { + class C { } + data.push(() => C); +} + +use(data[0]() === data[1]()); \ No newline at end of file