Skip to content

Commit e8b3ff0

Browse files
authored
Merge pull request #12694 from Microsoft/destructuring-initialisers-can-reference-previous-elements
Binding element initialisers can reference previous elements
2 parents 445cf0e + 77b6482 commit e8b3ff0

7 files changed

+138
-5
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,24 @@ namespace ts {
636636

637637
if (declaration.pos <= usage.pos) {
638638
// declaration is before usage
639-
// still might be illegal if usage is in the initializer of the variable declaration
640-
return declaration.kind !== SyntaxKind.VariableDeclaration ||
641-
!isImmediatelyUsedInInitializerOfBlockScopedVariable(<VariableDeclaration>declaration, usage);
639+
if (declaration.kind === SyntaxKind.BindingElement) {
640+
// still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2])
641+
const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement;
642+
if (errorBindingElement) {
643+
return getAncestorBindingPattern(errorBindingElement) !== getAncestorBindingPattern(declaration) ||
644+
declaration.pos < errorBindingElement.pos;
645+
}
646+
// or it might be illegal if usage happens before parent variable is declared (eg var [a] = a)
647+
return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage);
648+
}
649+
else if (declaration.kind === SyntaxKind.VariableDeclaration) {
650+
// still might be illegal if usage is in the initializer of the variable declaration (eg var a = a)
651+
return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage);
652+
}
653+
return true;
642654
}
643655

656+
644657
// declaration is after usage
645658
// can be legal if usage is deferred (i.e. inside function or in initializer of instance property)
646659
const container = getEnclosingBlockScopeContainer(declaration);
@@ -697,6 +710,16 @@ namespace ts {
697710
}
698711
return false;
699712
}
713+
714+
function getAncestorBindingPattern(node: Node): BindingPattern {
715+
while (node) {
716+
if (isBindingPattern(node)) {
717+
return node;
718+
}
719+
node = node.parent;
720+
}
721+
return undefined;
722+
}
700723
}
701724

702725
// 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 {
10631086

10641087
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
10651088

1066-
if (!isInAmbientContext(declaration) && !isBlockScopedNameDeclaredBeforeUse(<Declaration>getAncestor(declaration, SyntaxKind.VariableDeclaration), errorLocation)) {
1089+
if (!isInAmbientContext(declaration) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) {
10671090
error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name));
10681091
}
10691092
}
@@ -17157,7 +17180,8 @@ namespace ts {
1715717180
// so we need to do a bit of extra work to check if reference is legal
1715817181
const enclosingContainer = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
1715917182
if (enclosingContainer === func) {
17160-
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
17183+
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter ||
17184+
symbol.valueDeclaration.kind === SyntaxKind.BindingElement) {
1716117185
// it is ok to reference parameter in initializer if either
1716217186
// - parameter is located strictly on the left of current parameter declaration
1716317187
if (symbol.valueDeclaration.pos < node.pos) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(2,22): error TS2448: Block-scoped variable 'e' used before its declaration.
2+
tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(3,22): error TS2448: Block-scoped variable 'i' used before its declaration.
3+
tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(7,27): error TS2372: Parameter 'e' cannot be referenced in its initializer.
4+
tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts(9,27): error TS2373: Initializer of parameter 'h' cannot reference identifier 'i' declared after it.
5+
6+
7+
==== tests/cases/conformance/es6/destructuring/destructuringArrayBindingPatternAndAssignment3.ts (4 errors) ====
8+
const [a, b = a] = [1]; // ok
9+
const [c, d = c, e = e] = [1]; // error for e = e
10+
~
11+
!!! error TS2448: Block-scoped variable 'e' used before its declaration.
12+
const [f, g = f, h = i, i = f] = [1]; // error for h = i
13+
~
14+
!!! error TS2448: Block-scoped variable 'i' used before its declaration.
15+
16+
(function ([a, b = a]) { // ok
17+
})([1]);
18+
(function ([c, d = c, e = e]) { // error for e = e
19+
~
20+
!!! error TS2372: Parameter 'e' cannot be referenced in its initializer.
21+
})([1]);
22+
(function ([f, g = f, h = i, i = f]) { // error for h = i
23+
~
24+
!!! error TS2373: Initializer of parameter 'h' cannot reference identifier 'i' declared after it.
25+
})([1])
26+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [destructuringArrayBindingPatternAndAssignment3.ts]
2+
const [a, b = a] = [1]; // ok
3+
const [c, d = c, e = e] = [1]; // error for e = e
4+
const [f, g = f, h = i, i = f] = [1]; // error for h = i
5+
6+
(function ([a, b = a]) { // ok
7+
})([1]);
8+
(function ([c, d = c, e = e]) { // error for e = e
9+
})([1]);
10+
(function ([f, g = f, h = i, i = f]) { // error for h = i
11+
})([1])
12+
13+
14+
//// [destructuringArrayBindingPatternAndAssignment3.js]
15+
var _a = [1], a = _a[0], _b = _a[1], b = _b === void 0 ? a : _b; // ok
16+
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
17+
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
18+
(function (_a) {
19+
var a = _a[0], _b = _a[1], b = _b === void 0 ? a : _b;
20+
})([1]);
21+
(function (_a) {
22+
var c = _a[0], _b = _a[1], d = _b === void 0 ? c : _b, _c = _a[2], e = _c === void 0 ? e : _c;
23+
})([1]);
24+
(function (_a) {
25+
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;
26+
})([1]);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts(6,9): error TS2448: Block-scoped variable 'f' used before its declaration.
2+
tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts(7,9): error TS2448: Block-scoped variable 'f' used before its declaration.
3+
4+
5+
==== tests/cases/conformance/es6/destructuring/destructuringObjectBindingPatternAndAssignment4.ts (2 errors) ====
6+
const {
7+
a = 1,
8+
b = 2,
9+
c = b, // ok
10+
d = a, // ok
11+
e = f, // error
12+
~
13+
!!! error TS2448: Block-scoped variable 'f' used before its declaration.
14+
f = f // error
15+
~
16+
!!! error TS2448: Block-scoped variable 'f' used before its declaration.
17+
} = { } as any;
18+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [destructuringObjectBindingPatternAndAssignment4.ts]
2+
const {
3+
a = 1,
4+
b = 2,
5+
c = b, // ok
6+
d = a, // ok
7+
e = f, // error
8+
f = f // error
9+
} = { } as any;
10+
11+
12+
//// [destructuringObjectBindingPatternAndAssignment4.js]
13+
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
14+
_e = _a.d, // ok
15+
d = _e === void 0 ? a : _e, // ok
16+
_f = _a.e, // ok
17+
e = _f === void 0 ? f : _f, // error
18+
_g = _a.f // error
19+
, // error
20+
f = _g === void 0 ? f : _g // error
21+
;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const [a, b = a] = [1]; // ok
2+
const [c, d = c, e = e] = [1]; // error for e = e
3+
const [f, g = f, h = i, i = f] = [1]; // error for h = i
4+
5+
(function ([a, b = a]) { // ok
6+
})([1]);
7+
(function ([c, d = c, e = e]) { // error for e = e
8+
})([1]);
9+
(function ([f, g = f, h = i, i = f]) { // error for h = i
10+
})([1])
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const {
2+
a = 1,
3+
b = 2,
4+
c = b, // ok
5+
d = a, // ok
6+
e = f, // error
7+
f = f // error
8+
} = { } as any;

0 commit comments

Comments
 (0)