From 751b1aee16135d74d9e33eb24c62a505a8de314d Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 11 Mar 2015 16:54:18 -0700 Subject: [PATCH] disallow recursive references for block-scoped bindings --- src/compiler/checker.ts | 44 ++++++++++++++++++- src/compiler/emitter.ts | 28 ------------ src/compiler/utilities.ts | 27 ++++++++++++ tests/baselines/reference/for-of55.errors.txt | 10 +++++ tests/baselines/reference/for-of55.types | 12 ----- .../reference/recursiveLetConst.errors.txt | 40 +++++++++++++++++ .../baselines/reference/recursiveLetConst.js | 28 ++++++++++++ tests/cases/compiler/recursiveLetConst.ts | 11 +++++ 8 files changed, 159 insertions(+), 41 deletions(-) create mode 100644 tests/baselines/reference/for-of55.errors.txt delete mode 100644 tests/baselines/reference/for-of55.types create mode 100644 tests/baselines/reference/recursiveLetConst.errors.txt create mode 100644 tests/baselines/reference/recursiveLetConst.js create mode 100644 tests/cases/compiler/recursiveLetConst.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index afcf12fed1b..47f4a44db92 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -439,7 +439,33 @@ module ts { var declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) ? d : undefined); Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); - if (!isDefinedBefore(declaration, errorLocation)) { + + // first check if usage is lexically located after the declaration + var isUsedBeforeDeclaration = !isDefinedBefore(declaration, errorLocation); + if (!isUsedBeforeDeclaration) { + // lexical check succedded however code still can be illegal. + // - block scoped variables cannot be used in its initializers + // let x = x; // illegal but usage is lexically after definition + // - in ForIn/ForOf statements variable cannot be contained in expression part + // for (let x in x) + // for (let x of x) + + // climb up to the variable declaration skipping binding patterns + var variableDeclaration = getAncestor(declaration, SyntaxKind.VariableDeclaration); + var container = getEnclosingBlockScopeContainer(variableDeclaration); + + if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement || + variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) { + // variable statement/for statement case, use site should not be inside initializer + isUsedBeforeDeclaration = isChildNode(errorLocation, variableDeclaration.initializer, container); + } + else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement || + variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) { + // ForIn/ForOf case - use site should not be used in expression part + isUsedBeforeDeclaration = isChildNode(errorLocation, (variableDeclaration.parent.parent).expression, container); + } + } + if (isUsedBeforeDeclaration) { error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); } } @@ -447,6 +473,22 @@ module ts { return result; } + /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. + * If at any point current node is equal to 'parent' node - return true. + * Return false if 'stopAt' node is reached. + */ + function isChildNode(initial: Node, parent: Node, stopAt: Node): boolean { + if (!parent) { + return false; + } + for (var current = initial; current && current !== stopAt; current = current.parent) { + if (current === parent) { + return true; + } + } + return false; + } + // An alias symbol is created by one of the following declarations: // import = ... // import from ... diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 4b038ab1c4c..3e7d64eedc9 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -4116,34 +4116,6 @@ module ts { } } - function getEnclosingBlockScopeContainer(node: Node): Node { - var current = node; - while (current) { - if (isFunctionLike(current)) { - return current; - } - switch (current.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.CaseBlock: - case SyntaxKind.CatchClause: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - return current; - case SyntaxKind.Block: - // function block is not considered block-scope container - // see comment in binder.ts: bind(...), case for SyntaxKind.Block - if (!isFunctionLike(current.parent)) { - return current; - } - } - - current = current.parent; - } - } - - function getCombinedFlagsForIdentifier(node: Identifier): NodeFlags { if (!node.parent || (node.parent.kind !== SyntaxKind.VariableDeclaration && node.parent.kind !== SyntaxKind.BindingElement)) { return 0; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 02ec5eb76ad..b0e2540bdf0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -203,6 +203,33 @@ module ts { isCatchClauseVariableDeclaration(declaration); } + export function getEnclosingBlockScopeContainer(node: Node): Node { + var current = node; + while (current) { + if (isFunctionLike(current)) { + return current; + } + switch (current.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.CaseBlock: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + return current; + case SyntaxKind.Block: + // function block is not considered block-scope container + // see comment in binder.ts: bind(...), case for SyntaxKind.Block + if (!isFunctionLike(current.parent)) { + return current; + } + } + + current = current.parent; + } + } + export function isCatchClauseVariableDeclaration(declaration: Declaration) { return declaration && declaration.kind === SyntaxKind.VariableDeclaration && diff --git a/tests/baselines/reference/for-of55.errors.txt b/tests/baselines/reference/for-of55.errors.txt new file mode 100644 index 00000000000..aa1285afbd6 --- /dev/null +++ b/tests/baselines/reference/for-of55.errors.txt @@ -0,0 +1,10 @@ +tests/cases/conformance/es6/for-ofStatements/for-of55.ts(2,15): error TS2448: Block-scoped variable 'v' used before its declaration. + + +==== tests/cases/conformance/es6/for-ofStatements/for-of55.ts (1 errors) ==== + let v = [1]; + for (let v of v) { + ~ +!!! error TS2448: Block-scoped variable 'v' used before its declaration. + v; + } \ No newline at end of file diff --git a/tests/baselines/reference/for-of55.types b/tests/baselines/reference/for-of55.types deleted file mode 100644 index b0f5aab3fee..00000000000 --- a/tests/baselines/reference/for-of55.types +++ /dev/null @@ -1,12 +0,0 @@ -=== tests/cases/conformance/es6/for-ofStatements/for-of55.ts === -let v = [1]; ->v : number[] ->[1] : number[] - -for (let v of v) { ->v : any ->v : any - - v; ->v : any -} diff --git a/tests/baselines/reference/recursiveLetConst.errors.txt b/tests/baselines/reference/recursiveLetConst.errors.txt new file mode 100644 index 00000000000..99a904fe761 --- /dev/null +++ b/tests/baselines/reference/recursiveLetConst.errors.txt @@ -0,0 +1,40 @@ +tests/cases/compiler/recursiveLetConst.ts(2,9): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(3,12): error TS2448: Block-scoped variable 'x1' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(4,11): error TS2448: Block-scoped variable 'y' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(5,14): error TS2448: Block-scoped variable 'y1' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(6,14): error TS2448: Block-scoped variable 'v' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(7,16): error TS2448: Block-scoped variable 'v' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(8,15): error TS2448: Block-scoped variable 'v' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(9,15): error TS2448: Block-scoped variable 'v' used before its declaration. +tests/cases/compiler/recursiveLetConst.ts(10,17): error TS2448: Block-scoped variable 'v' used before its declaration. + + +==== tests/cases/compiler/recursiveLetConst.ts (9 errors) ==== + 'use strict' + let x = x + 1; + ~ +!!! error TS2448: Block-scoped variable 'x' used before its declaration. + let [x1] = x1 + 1; + ~~ +!!! error TS2448: Block-scoped variable 'x1' used before its declaration. + const y = y + 2; + ~ +!!! error TS2448: Block-scoped variable 'y' used before its declaration. + const [y1] = y1 + 1; + ~~ +!!! error TS2448: Block-scoped variable 'y1' used before its declaration. + for (let v = v; ; ) { } + ~ +!!! error TS2448: Block-scoped variable 'v' used before its declaration. + for (let [v] = v; ;) { } + ~ +!!! error TS2448: Block-scoped variable 'v' used before its declaration. + for (let v in v) { } + ~ +!!! error TS2448: Block-scoped variable 'v' used before its declaration. + for (let v of v) { } + ~ +!!! error TS2448: Block-scoped variable 'v' used before its declaration. + for (let [v] of v) { } + ~ +!!! error TS2448: Block-scoped variable 'v' used before its declaration. \ No newline at end of file diff --git a/tests/baselines/reference/recursiveLetConst.js b/tests/baselines/reference/recursiveLetConst.js new file mode 100644 index 00000000000..f34d85d86a9 --- /dev/null +++ b/tests/baselines/reference/recursiveLetConst.js @@ -0,0 +1,28 @@ +//// [recursiveLetConst.ts] +'use strict' +let x = x + 1; +let [x1] = x1 + 1; +const y = y + 2; +const [y1] = y1 + 1; +for (let v = v; ; ) { } +for (let [v] = v; ;) { } +for (let v in v) { } +for (let v of v) { } +for (let [v] of v) { } + +//// [recursiveLetConst.js] +'use strict'; +let x = x + 1; +let [x1] = x1 + 1; +const y = y + 2; +const [y1] = y1 + 1; +for (let v = v;;) { +} +for (let [v] = v;;) { +} +for (let v in v) { +} +for (let v of v) { +} +for (let [v] of v) { +} diff --git a/tests/cases/compiler/recursiveLetConst.ts b/tests/cases/compiler/recursiveLetConst.ts new file mode 100644 index 00000000000..c80aa6ef638 --- /dev/null +++ b/tests/cases/compiler/recursiveLetConst.ts @@ -0,0 +1,11 @@ +// @target:es6 +'use strict' +let x = x + 1; +let [x1] = x1 + 1; +const y = y + 2; +const [y1] = y1 + 1; +for (let v = v; ; ) { } +for (let [v] = v; ;) { } +for (let v in v) { } +for (let v of v) { } +for (let [v] of v) { } \ No newline at end of file