diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2edfc6a7924..a27e8ba524d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -636,11 +636,24 @@ namespace ts { if (declaration.pos <= usage.pos) { // declaration is before usage - // still might be illegal if usage is in the initializer of the variable declaration - return declaration.kind !== SyntaxKind.VariableDeclaration || - !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration, usage); + if (declaration.kind === SyntaxKind.BindingElement) { + // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) + const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; + if (errorBindingElement) { + return getAncestorBindingPattern(errorBindingElement) !== getAncestorBindingPattern(declaration) || + declaration.pos < errorBindingElement.pos; + } + // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) + return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); + } + else if (declaration.kind === SyntaxKind.VariableDeclaration) { + // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) + return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); + } + return true; } + // declaration is after usage // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) const container = getEnclosingBlockScopeContainer(declaration); @@ -697,6 +710,16 @@ namespace ts { } return false; } + + function getAncestorBindingPattern(node: Node): BindingPattern { + while (node) { + if (isBindingPattern(node)) { + return node; + } + node = node.parent; + } + return undefined; + } } // Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and @@ -1063,7 +1086,7 @@ namespace ts { Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); - if (!isInAmbientContext(declaration) && !isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration), errorLocation)) { + if (!isInAmbientContext(declaration) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); } } @@ -17157,7 +17180,8 @@ namespace ts { // so we need to do a bit of extra work to check if reference is legal const enclosingContainer = getEnclosingBlockScopeContainer(symbol.valueDeclaration); if (enclosingContainer === func) { - if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) { + if (symbol.valueDeclaration.kind === SyntaxKind.Parameter || + symbol.valueDeclaration.kind === SyntaxKind.BindingElement) { // it is ok to reference parameter in initializer if either // - parameter is located strictly on the left of current parameter declaration if (symbol.valueDeclaration.pos < node.pos) { diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.errors.txt b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.errors.txt new file mode 100644 index 00000000000..a68affcf73d --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.errors.txt @@ -0,0 +1,26 @@ +tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(2,22): error TS2448: Block-scoped variable 'e' used before its declaration. +tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(3,22): error TS2448: Block-scoped variable 'i' used before its declaration. +tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(7,27): error TS2372: Parameter 'e' cannot be referenced in its initializer. +tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(9,27): error TS2373: Initializer of parameter 'h' cannot reference identifier 'i' declared after it. + + +==== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts (4 errors) ==== + const [a, b = a] = [1]; // ok + const [c, d = c, e = e] = [1]; // error for e = e + ~ +!!! error TS2448: Block-scoped variable 'e' used before its declaration. + const [f, g = f, h = i, i = f] = [1]; // error for h = i + ~ +!!! error TS2448: Block-scoped variable 'i' used before its declaration. + + (function ([a, b = a]) { // ok + })([1]); + (function ([c, d = c, e = e]) { // error for e = e + ~ +!!! error TS2372: Parameter 'e' cannot be referenced in its initializer. + })([1]); + (function ([f, g = f, h = i, i = f]) { // error for h = i + ~ +!!! error TS2373: Initializer of parameter 'h' cannot reference identifier 'i' declared after it. + })([1]) + \ No newline at end of file diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.js b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.js new file mode 100644 index 00000000000..7073061dec6 --- /dev/null +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.js @@ -0,0 +1,26 @@ +//// [destructuringArrayBindingPatternAndAssignment3.ts] +const [a, b = a] = [1]; // ok +const [c, d = c, e = e] = [1]; // error for e = e +const [f, g = f, h = i, i = f] = [1]; // error for h = i + +(function ([a, b = a]) { // ok +})([1]); +(function ([c, d = c, e = e]) { // error for e = e +})([1]); +(function ([f, g = f, h = i, i = f]) { // error for h = i +})([1]) + + +//// [destructuringArrayBindingPatternAndAssignment3.js] +var _a = [1], a = _a[0], _b = _a[1], b = _b === void 0 ? a : _b; // ok +var _c = [1], c = _c[0], _d = _c[1], d = _d === void 0 ? c : _d, _e = _c[2], e = _e === void 0 ? e : _e; // error for e = e +var _f = [1], f = _f[0], _g = _f[1], g = _g === void 0 ? f : _g, _h = _f[2], h = _h === void 0 ? i : _h, _j = _f[3], i = _j === void 0 ? f : _j; // error for h = i +(function (_a) { + var a = _a[0], _b = _a[1], b = _b === void 0 ? a : _b; +})([1]); +(function (_a) { + var c = _a[0], _b = _a[1], d = _b === void 0 ? c : _b, _c = _a[2], e = _c === void 0 ? e : _c; +})([1]); +(function (_a) { + var f = _a[0], _b = _a[1], g = _b === void 0 ? f : _b, _c = _a[2], h = _c === void 0 ? i : _c, _d = _a[3], i = _d === void 0 ? f : _d; +})([1]); diff --git a/tests/baselines/reference/destructuringObjectBindingPatternAndAssignment4.errors.txt b/tests/baselines/reference/destructuringObjectBindingPatternAndAssignment4.errors.txt new file mode 100644 index 00000000000..6a3f0f2b457 --- /dev/null +++ b/tests/baselines/reference/destructuringObjectBindingPatternAndAssignment4.errors.txt @@ -0,0 +1,18 @@ +tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts(6,9): error TS2448: Block-scoped variable 'f' used before its declaration. +tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts(7,9): error TS2448: Block-scoped variable 'f' used before its declaration. + + +==== tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts (2 errors) ==== + const { + a = 1, + b = 2, + c = b, // ok + d = a, // ok + e = f, // error + ~ +!!! error TS2448: Block-scoped variable 'f' used before its declaration. + f = f // error + ~ +!!! error TS2448: Block-scoped variable 'f' used before its declaration. + } = { } as any; + \ No newline at end of file diff --git a/tests/baselines/reference/destructuringObjectBindingPatternAndAssignment4.js b/tests/baselines/reference/destructuringObjectBindingPatternAndAssignment4.js new file mode 100644 index 00000000000..c34cbb62c1c --- /dev/null +++ b/tests/baselines/reference/destructuringObjectBindingPatternAndAssignment4.js @@ -0,0 +1,21 @@ +//// [destructuringObjectBindingPatternAndAssignment4.ts] +const { + a = 1, + b = 2, + c = b, // ok + d = a, // ok + e = f, // error + f = f // error +} = { } as any; + + +//// [destructuringObjectBindingPatternAndAssignment4.js] +var _a = {}, _b = _a.a, a = _b === void 0 ? 1 : _b, _c = _a.b, b = _c === void 0 ? 2 : _c, _d = _a.c, c = _d === void 0 ? b : _d, // ok +_e = _a.d, // ok +d = _e === void 0 ? a : _e, // ok +_f = _a.e, // ok +e = _f === void 0 ? f : _f, // error +_g = _a.f // error +, // error +f = _g === void 0 ? f : _g // error +; diff --git a/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts new file mode 100644 index 00000000000..243a297ed43 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts @@ -0,0 +1,10 @@ +const [a, b = a] = [1]; // ok +const [c, d = c, e = e] = [1]; // error for e = e +const [f, g = f, h = i, i = f] = [1]; // error for h = i + +(function ([a, b = a]) { // ok +})([1]); +(function ([c, d = c, e = e]) { // error for e = e +})([1]); +(function ([f, g = f, h = i, i = f]) { // error for h = i +})([1]) diff --git a/tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts b/tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts new file mode 100644 index 00000000000..d138c14db7a --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts @@ -0,0 +1,8 @@ +const { + a = 1, + b = 2, + c = b, // ok + d = a, // ok + e = f, // error + f = f // error +} = { } as any;