diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 47f4a44db92a2..2541be0d79e1c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -435,53 +435,60 @@ module ts { return undefined; } if (result.flags & SymbolFlags.BlockScopedVariable) { - // Block-scoped variables cannot be used before their definition - var declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) ? d : undefined); + checkResolvedBlockScopedVariable(result, errorLocation); + } + } + return result; + } + + function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { + Debug.assert((result.flags & SymbolFlags.BlockScopedVariable) !== 0) + // Block-scoped variables cannot be used before their definition + var declaration = forEach(result.declarations, d => isBlockOrCatchScoped(d) ? d : undefined); - Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); + Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); - // 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) + // first check if usage is lexically located after the declaration + var isUsedBeforeDeclaration = !isDefinedBefore(declaration, errorLocation); + if (!isUsedBeforeDeclaration) { + // lexical check succeeded 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); + // 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)); - } + if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement || + variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) { + // variable statement/for statement case, + // use site should not be inside variable declaration (initializer of declaration or binding element) + isUsedBeforeDeclaration = isDescendentOf(errorLocation, variableDeclaration, 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 + var expression = (variableDeclaration.parent.parent).expression; + isUsedBeforeDeclaration = isDescendentOf(errorLocation, expression, container); } } - return result; + if (isUsedBeforeDeclaration) { + error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); + } } /* 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. + * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. */ - function isChildNode(initial: Node, parent: Node, stopAt: Node): boolean { + function isDescendentOf(initial: Node, parent: Node, stopAt: Node): boolean { if (!parent) { return false; } - for (var current = initial; current && current !== stopAt; current = current.parent) { + for (var current = initial; current && current !== stopAt && !isFunctionLike(current); current = current.parent) { if (current === parent) { return true; } diff --git a/tests/baselines/reference/recursiveLetConst.errors.txt b/tests/baselines/reference/recursiveLetConst.errors.txt index 99a904fe761b3..b02d0819a3f46 100644 --- a/tests/baselines/reference/recursiveLetConst.errors.txt +++ b/tests/baselines/reference/recursiveLetConst.errors.txt @@ -7,9 +7,10 @@ tests/cases/compiler/recursiveLetConst.ts(7,16): error TS2448: Block-scoped vari 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(11,11): error TS2448: Block-scoped variable 'x2' used before its declaration. -==== tests/cases/compiler/recursiveLetConst.ts (9 errors) ==== +==== tests/cases/compiler/recursiveLetConst.ts (10 errors) ==== 'use strict' let x = x + 1; ~ @@ -37,4 +38,10 @@ tests/cases/compiler/recursiveLetConst.ts(10,17): error TS2448: Block-scoped var !!! 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 +!!! error TS2448: Block-scoped variable 'v' used before its declaration. + let [x2 = x2] = [] + ~~ +!!! error TS2448: Block-scoped variable 'x2' used before its declaration. + let z0 = () => z0; + let z1 = function () { return z1; } + let z2 = { f() { return z2;}} \ No newline at end of file diff --git a/tests/baselines/reference/recursiveLetConst.js b/tests/baselines/reference/recursiveLetConst.js index f34d85d86a9a3..7d9aea3a754d7 100644 --- a/tests/baselines/reference/recursiveLetConst.js +++ b/tests/baselines/reference/recursiveLetConst.js @@ -8,7 +8,11 @@ for (let v = v; ; ) { } for (let [v] = v; ;) { } for (let v in v) { } for (let v of v) { } -for (let [v] of v) { } +for (let [v] of v) { } +let [x2 = x2] = [] +let z0 = () => z0; +let z1 = function () { return z1; } +let z2 = { f() { return z2;}} //// [recursiveLetConst.js] 'use strict'; @@ -26,3 +30,13 @@ for (let v of v) { } for (let [v] of v) { } +let [x2 = x2] = []; +let z0 = () => z0; +let z1 = function () { + return z1; +}; +let z2 = { + f() { + return z2; + } +}; diff --git a/tests/cases/compiler/recursiveLetConst.ts b/tests/cases/compiler/recursiveLetConst.ts index c80aa6ef63853..9e00ae22e9468 100644 --- a/tests/cases/compiler/recursiveLetConst.ts +++ b/tests/cases/compiler/recursiveLetConst.ts @@ -8,4 +8,8 @@ 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 +for (let [v] of v) { } +let [x2 = x2] = [] +let z0 = () => z0; +let z1 = function () { return z1; } +let z2 = { f() { return z2;}} \ No newline at end of file