Skip to content

Commit 17d2a1b

Browse files
committed
Merge pull request #2309 from Microsoft/recursiveLetConst
disallow recursive references for block-scoped bindings
2 parents ecfa19a + 1ce105a commit 17d2a1b

File tree

8 files changed

+197
-47
lines changed

8 files changed

+197
-47
lines changed

src/compiler/checker.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -435,18 +435,67 @@ module ts {
435435
return undefined;
436436
}
437437
if (result.flags & SymbolFlags.BlockScopedVariable) {
438-
// Block-scoped variables cannot be used before their definition
439-
var declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) ? d : undefined);
440-
441-
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
442-
if (!isDefinedBefore(declaration, errorLocation)) {
443-
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
444-
}
438+
checkResolvedBlockScopedVariable(result, errorLocation);
445439
}
446440
}
447441
return result;
448442
}
449443

444+
function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void {
445+
Debug.assert((result.flags & SymbolFlags.BlockScopedVariable) !== 0)
446+
// Block-scoped variables cannot be used before their definition
447+
var declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) ? d : undefined);
448+
449+
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
450+
451+
// first check if usage is lexically located after the declaration
452+
var isUsedBeforeDeclaration = !isDefinedBefore(declaration, errorLocation);
453+
if (!isUsedBeforeDeclaration) {
454+
// lexical check succeeded however code still can be illegal.
455+
// - block scoped variables cannot be used in its initializers
456+
// let x = x; // illegal but usage is lexically after definition
457+
// - in ForIn/ForOf statements variable cannot be contained in expression part
458+
// for (let x in x)
459+
// for (let x of x)
460+
461+
// climb up to the variable declaration skipping binding patterns
462+
var variableDeclaration = <VariableDeclaration>getAncestor(declaration, SyntaxKind.VariableDeclaration);
463+
var container = getEnclosingBlockScopeContainer(variableDeclaration);
464+
465+
if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement ||
466+
variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) {
467+
// variable statement/for statement case,
468+
// use site should not be inside variable declaration (initializer of declaration or binding element)
469+
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, variableDeclaration, container);
470+
}
471+
else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement ||
472+
variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) {
473+
// ForIn/ForOf case - use site should not be used in expression part
474+
var expression = (<ForInStatement | ForOfStatement>variableDeclaration.parent.parent).expression;
475+
isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container);
476+
}
477+
}
478+
if (isUsedBeforeDeclaration) {
479+
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
480+
}
481+
}
482+
483+
/* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached.
484+
* If at any point current node is equal to 'parent' node - return true.
485+
* Return false if 'stopAt' node is reached or isFunctionLike(current) === true.
486+
*/
487+
function isSameScopeDescendentOf(initial: Node, parent: Node, stopAt: Node): boolean {
488+
if (!parent) {
489+
return false;
490+
}
491+
for (var current = initial; current && current !== stopAt && !isFunctionLike(current); current = current.parent) {
492+
if (current === parent) {
493+
return true;
494+
}
495+
}
496+
return false;
497+
}
498+
450499
// An alias symbol is created by one of the following declarations:
451500
// import <symbol> = ...
452501
// import <symbol> from ...

src/compiler/emitter.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,34 +4116,6 @@ module ts {
41164116
}
41174117
}
41184118

4119-
function getEnclosingBlockScopeContainer(node: Node): Node {
4120-
var current = node;
4121-
while (current) {
4122-
if (isFunctionLike(current)) {
4123-
return current;
4124-
}
4125-
switch (current.kind) {
4126-
case SyntaxKind.SourceFile:
4127-
case SyntaxKind.CaseBlock:
4128-
case SyntaxKind.CatchClause:
4129-
case SyntaxKind.ModuleDeclaration:
4130-
case SyntaxKind.ForStatement:
4131-
case SyntaxKind.ForInStatement:
4132-
case SyntaxKind.ForOfStatement:
4133-
return current;
4134-
case SyntaxKind.Block:
4135-
// function block is not considered block-scope container
4136-
// see comment in binder.ts: bind(...), case for SyntaxKind.Block
4137-
if (!isFunctionLike(current.parent)) {
4138-
return current;
4139-
}
4140-
}
4141-
4142-
current = current.parent;
4143-
}
4144-
}
4145-
4146-
41474119
function getCombinedFlagsForIdentifier(node: Identifier): NodeFlags {
41484120
if (!node.parent || (node.parent.kind !== SyntaxKind.VariableDeclaration && node.parent.kind !== SyntaxKind.BindingElement)) {
41494121
return 0;

src/compiler/utilities.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,33 @@ module ts {
203203
isCatchClauseVariableDeclaration(declaration);
204204
}
205205

206+
export function getEnclosingBlockScopeContainer(node: Node): Node {
207+
var current = node;
208+
while (current) {
209+
if (isFunctionLike(current)) {
210+
return current;
211+
}
212+
switch (current.kind) {
213+
case SyntaxKind.SourceFile:
214+
case SyntaxKind.CaseBlock:
215+
case SyntaxKind.CatchClause:
216+
case SyntaxKind.ModuleDeclaration:
217+
case SyntaxKind.ForStatement:
218+
case SyntaxKind.ForInStatement:
219+
case SyntaxKind.ForOfStatement:
220+
return current;
221+
case SyntaxKind.Block:
222+
// function block is not considered block-scope container
223+
// see comment in binder.ts: bind(...), case for SyntaxKind.Block
224+
if (!isFunctionLike(current.parent)) {
225+
return current;
226+
}
227+
}
228+
229+
current = current.parent;
230+
}
231+
}
232+
206233
export function isCatchClauseVariableDeclaration(declaration: Declaration) {
207234
return declaration &&
208235
declaration.kind === SyntaxKind.VariableDeclaration &&
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests/cases/conformance/es6/for-ofStatements/for-of55.ts(2,15): error TS2448: Block-scoped variable 'v' used before its declaration.
2+
3+
4+
==== tests/cases/conformance/es6/for-ofStatements/for-of55.ts (1 errors) ====
5+
let v = [1];
6+
for (let v of v) {
7+
~
8+
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
9+
v;
10+
}

tests/baselines/reference/for-of55.types

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
tests/cases/compiler/recursiveLetConst.ts(2,9): error TS2448: Block-scoped variable 'x' used before its declaration.
2+
tests/cases/compiler/recursiveLetConst.ts(3,12): error TS2448: Block-scoped variable 'x1' used before its declaration.
3+
tests/cases/compiler/recursiveLetConst.ts(4,11): error TS2448: Block-scoped variable 'y' used before its declaration.
4+
tests/cases/compiler/recursiveLetConst.ts(5,14): error TS2448: Block-scoped variable 'y1' used before its declaration.
5+
tests/cases/compiler/recursiveLetConst.ts(6,14): error TS2448: Block-scoped variable 'v' used before its declaration.
6+
tests/cases/compiler/recursiveLetConst.ts(7,16): error TS2448: Block-scoped variable 'v' used before its declaration.
7+
tests/cases/compiler/recursiveLetConst.ts(8,15): error TS2448: Block-scoped variable 'v' used before its declaration.
8+
tests/cases/compiler/recursiveLetConst.ts(9,15): error TS2448: Block-scoped variable 'v' used before its declaration.
9+
tests/cases/compiler/recursiveLetConst.ts(10,17): error TS2448: Block-scoped variable 'v' used before its declaration.
10+
tests/cases/compiler/recursiveLetConst.ts(11,11): error TS2448: Block-scoped variable 'x2' used before its declaration.
11+
12+
13+
==== tests/cases/compiler/recursiveLetConst.ts (10 errors) ====
14+
'use strict'
15+
let x = x + 1;
16+
~
17+
!!! error TS2448: Block-scoped variable 'x' used before its declaration.
18+
let [x1] = x1 + 1;
19+
~~
20+
!!! error TS2448: Block-scoped variable 'x1' used before its declaration.
21+
const y = y + 2;
22+
~
23+
!!! error TS2448: Block-scoped variable 'y' used before its declaration.
24+
const [y1] = y1 + 1;
25+
~~
26+
!!! error TS2448: Block-scoped variable 'y1' used before its declaration.
27+
for (let v = v; ; ) { }
28+
~
29+
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
30+
for (let [v] = v; ;) { }
31+
~
32+
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
33+
for (let v in v) { }
34+
~
35+
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
36+
for (let v of v) { }
37+
~
38+
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
39+
for (let [v] of v) { }
40+
~
41+
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
42+
let [x2 = x2] = []
43+
~~
44+
!!! error TS2448: Block-scoped variable 'x2' used before its declaration.
45+
let z0 = () => z0;
46+
let z1 = function () { return z1; }
47+
let z2 = { f() { return z2;}}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [recursiveLetConst.ts]
2+
'use strict'
3+
let x = x + 1;
4+
let [x1] = x1 + 1;
5+
const y = y + 2;
6+
const [y1] = y1 + 1;
7+
for (let v = v; ; ) { }
8+
for (let [v] = v; ;) { }
9+
for (let v in v) { }
10+
for (let v of v) { }
11+
for (let [v] of v) { }
12+
let [x2 = x2] = []
13+
let z0 = () => z0;
14+
let z1 = function () { return z1; }
15+
let z2 = { f() { return z2;}}
16+
17+
//// [recursiveLetConst.js]
18+
'use strict';
19+
let x = x + 1;
20+
let [x1] = x1 + 1;
21+
const y = y + 2;
22+
const [y1] = y1 + 1;
23+
for (let v = v;;) {
24+
}
25+
for (let [v] = v;;) {
26+
}
27+
for (let v in v) {
28+
}
29+
for (let v of v) {
30+
}
31+
for (let [v] of v) {
32+
}
33+
let [x2 = x2] = [];
34+
let z0 = () => z0;
35+
let z1 = function () {
36+
return z1;
37+
};
38+
let z2 = {
39+
f() {
40+
return z2;
41+
}
42+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @target:es6
2+
'use strict'
3+
let x = x + 1;
4+
let [x1] = x1 + 1;
5+
const y = y + 2;
6+
const [y1] = y1 + 1;
7+
for (let v = v; ; ) { }
8+
for (let [v] = v; ;) { }
9+
for (let v in v) { }
10+
for (let v of v) { }
11+
for (let [v] of v) { }
12+
let [x2 = x2] = []
13+
let z0 = () => z0;
14+
let z1 = function () { return z1; }
15+
let z2 = { f() { return z2;}}

0 commit comments

Comments
 (0)