Skip to content

Commit 340f810

Browse files
authored
Merge pull request #32178 from microsoft/improveTupleDestructuring
Simplify tuple destructuring logic
2 parents 440ed83 + 17153a6 commit 340f810

File tree

37 files changed

+727
-164
lines changed

37 files changed

+727
-164
lines changed

src/compiler/checker.ts

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ namespace ts {
723723
NoIndexSignatures = 1 << 0,
724724
Writing = 1 << 1,
725725
CacheSymbol = 1 << 2,
726+
NoTupleBoundsCheck = 1 << 3,
726727
}
727728

728729
const enum CallbackCheck {
@@ -5119,21 +5120,25 @@ namespace ts {
51195120
}
51205121
else if (isArrayLikeType(parentType)) {
51215122
const indexType = getLiteralType(index);
5122-
const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, declaration.name), declaration.name);
5123+
const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0;
5124+
const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags) || errorType, declaration.name);
51235125
type = getFlowTypeOfDestructuring(declaration, declaredType);
51245126
}
51255127
else {
51265128
type = elementType;
51275129
}
51285130
}
5129-
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
5130-
// undefined from the final type.
5131-
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) {
5132-
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
5131+
if (!declaration.initializer) {
5132+
return type;
51335133
}
5134-
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ?
5135-
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) :
5136-
type;
5134+
if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) {
5135+
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
5136+
// undefined from the final type.
5137+
return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ?
5138+
getTypeWithFacts(type, TypeFacts.NEUndefined) :
5139+
type;
5140+
}
5141+
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
51375142
}
51385143

51395144
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
@@ -10144,7 +10149,7 @@ namespace ts {
1014410149
propType;
1014510150
}
1014610151
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
10147-
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) {
10152+
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
1014810153
const indexNode = getIndexNodeForAccessExpression(accessNode);
1014910154
if (isTupleType(objectType)) {
1015010155
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2,
@@ -19218,26 +19223,7 @@ namespace ts {
1921819223
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) {
1921919224
// Infer a tuple type when the contextual type is or contains a tuple-like type
1922019225
if (readonly || (contextualType && forEachType(contextualType, isTupleLikeType))) {
19221-
const minLength = elementCount - (hasRestElement ? 1 : 0);
19222-
const pattern = contextualType && contextualType.pattern;
19223-
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
19224-
// tuple type with the corresponding binding or assignment element types to make the lengths equal.
19225-
if (!hasRestElement && pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
19226-
const patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
19227-
for (let i = elementCount; i < patternElements.length; i++) {
19228-
const e = patternElements[i];
19229-
if (hasDefaultValue(e)) {
19230-
elementTypes.push((<TypeReference>contextualType).typeArguments![i]);
19231-
}
19232-
else if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && (<BindingElement>e).dotDotDotToken || e.kind === SyntaxKind.SpreadElement)) {
19233-
if (e.kind !== SyntaxKind.OmittedExpression) {
19234-
error(e, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
19235-
}
19236-
elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType);
19237-
}
19238-
}
19239-
}
19240-
return createTupleType(elementTypes, minLength, hasRestElement, readonly);
19226+
return createTupleType(elementTypes, elementCount - (hasRestElement ? 1 : 0), hasRestElement, readonly);
1924119227
}
1924219228
}
1924319229

@@ -23766,8 +23752,10 @@ namespace ts {
2376623752
if (isArrayLikeType(sourceType)) {
2376723753
// We create a synthetic expression so that getIndexedAccessType doesn't get confused
2376823754
// when the element is a SyntaxKind.ElementAccessExpression.
23769-
const elementType = getIndexedAccessType(sourceType, indexType, createSyntheticExpression(element, indexType));
23770-
const type = getFlowTypeOfDestructuring(element, elementType);
23755+
const accessFlags = hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0;
23756+
const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, createSyntheticExpression(element, indexType), accessFlags) || errorType;
23757+
const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType;
23758+
const type = getFlowTypeOfDestructuring(element, assignedType);
2377123759
return checkDestructuringAssignment(element, type, checkMode);
2377223760
}
2377323761
return checkDestructuringAssignment(element, elementType, checkMode);
@@ -24356,10 +24344,13 @@ namespace ts {
2435624344
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
2435724345
const initializer = getEffectiveInitializer(declaration)!;
2435824346
const type = getTypeOfExpression(initializer, /*cache*/ true);
24347+
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
24348+
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
24349+
padTupleType(type, declaration.name) : type;
2435924350
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
2436024351
isDeclarationReadonly(declaration) ||
2436124352
isTypeAssertion(initializer) ||
24362-
isLiteralOfContextualType(type, getContextualType(initializer)) ? type : getWidenedLiteralType(type);
24353+
isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded);
2436324354
if (isInJSFile(declaration)) {
2436424355
if (widened.flags & TypeFlags.Nullable) {
2436524356
reportImplicitAny(declaration, anyType);
@@ -24373,6 +24364,22 @@ namespace ts {
2437324364
return widened;
2437424365
}
2437524366

24367+
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
24368+
const patternElements = pattern.elements;
24369+
const arity = getTypeReferenceArity(type);
24370+
const elementTypes = arity ? type.typeArguments!.slice() : [];
24371+
for (let i = arity; i < patternElements.length; i++) {
24372+
const e = patternElements[i];
24373+
if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) {
24374+
elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType);
24375+
if (!isOmittedExpression(e) && !hasDefaultValue(e)) {
24376+
reportImplicitAny(e, anyType);
24377+
}
24378+
}
24379+
}
24380+
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
24381+
}
24382+
2437624383
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
2437724384
if (contextualType) {
2437824385
if (contextualType.flags & TypeFlags.UnionOrIntersection) {

tests/baselines/reference/declarationEmitDestructuringArrayPattern2.errors.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,6): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
2-
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,11): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
3-
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
1+
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,6): error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
2+
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,11): error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
3+
tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,16): error TS2493: Tuple type '[]' of length '0' has no element at index '2'.
44

55

66
==== tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts (3 errors) ====
@@ -9,11 +9,11 @@ tests/cases/compiler/declarationEmitDestructuringArrayPattern2.ts(4,16): error T
99
var [x11 = 0, y11 = ""] = [1, "hello"];
1010
var [a11, b11, c11] = [];
1111
~~~
12-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
12+
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
1313
~~~
14-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
14+
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
1515
~~~
16-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
16+
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '2'.
1717

1818
var [a2, [b2, { x12, y12: c2 }]=["abc", { x12: 10, y12: false }]] = [1, ["hello", { x12: 5, y12: true }]];
1919

tests/baselines/reference/declarationEmitDestructuringArrayPattern2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var _m = [[x13, y13], { x: x13, y: y13 }], a3 = _m[0], b3 = _m[1];
2222
//// [declarationEmitDestructuringArrayPattern2.d.ts]
2323
declare var x10: number, y10: string, z10: boolean;
2424
declare var x11: number, y11: string;
25-
declare var a11: any, b11: any, c11: any;
25+
declare var a11: undefined, b11: undefined, c11: undefined;
2626
declare var a2: number, b2: string, x12: number, c2: boolean;
2727
declare var x13: number, y13: string;
2828
declare var a3: (string | number)[], b3: {

tests/baselines/reference/declarationEmitDestructuringArrayPattern2.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ var [x11 = 0, y11 = ""] = [1, "hello"];
2020
>"hello" : "hello"
2121

2222
var [a11, b11, c11] = [];
23-
>a11 : any
24-
>b11 : any
25-
>c11 : any
26-
>[] : [undefined?, undefined?, undefined?]
23+
>a11 : undefined
24+
>b11 : undefined
25+
>c11 : undefined
26+
>[] : []
2727

2828
var [a2, [b2, { x12, y12: c2 }]=["abc", { x12: 10, y12: false }]] = [1, ["hello", { x12: 5, y12: true }]];
2929
>a2 : number

tests/baselines/reference/declarationsAndAssignments.errors.txt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(5,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
1+
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(5,16): error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'.
22
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(22,17): error TS2353: Object literal may only specify known properties, and 'x' does not exist in type '{}'.
33
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(22,23): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{}'.
44
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(23,25): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: any; }'.
55
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(24,19): error TS2353: Object literal may only specify known properties, and 'x' does not exist in type '{ y: any; }'.
66
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(28,28): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: any; }'.
77
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(29,22): error TS2353: Object literal may only specify known properties, and 'x' does not exist in type '{ y: any; }'.
88
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(58,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'y' must be of type 'string | number', but here has type 'string'.
9-
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,10): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
10-
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,13): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
11-
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
12-
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,13): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
13-
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,16): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
9+
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,10): error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
10+
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,13): error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
11+
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(62,16): error TS2493: Tuple type '[]' of length '0' has no element at index '2'.
12+
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,13): error TS2493: Tuple type '[number]' of length '1' has no element at index '1'.
13+
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(63,16): error TS2493: Tuple type '[number]' of length '1' has no element at index '2'.
1414
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(67,9): error TS2461: Type '{}' is not an array type.
1515
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(68,9): error TS2461: Type '{ 0: number; 1: number; }' is not an array type.
1616
tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(73,11): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
@@ -29,7 +29,7 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9):
2929
var [x, y] = [1, "hello"];
3030
var [x, y, z] = [1, "hello"];
3131
~
32-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
32+
!!! error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'.
3333
var [,, x] = [0, 1, 2];
3434
var x: number;
3535
var y: string;
@@ -103,16 +103,16 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9):
103103
function f8() {
104104
var [a, b, c] = []; // Error, [] is an empty tuple
105105
~
106-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
106+
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '0'.
107107
~
108-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
108+
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '1'.
109109
~
110-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
110+
!!! error TS2493: Tuple type '[]' of length '0' has no element at index '2'.
111111
var [d, e, f] = [1]; // Error, [1] is a tuple
112112
~
113-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
113+
!!! error TS2493: Tuple type '[number]' of length '1' has no element at index '1'.
114114
~
115-
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
115+
!!! error TS2493: Tuple type '[number]' of length '1' has no element at index '2'.
116116
}
117117

118118
function f9() {

tests/baselines/reference/declarationsAndAssignments.types

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ function f0() {
2323
var [x, y, z] = [1, "hello"];
2424
>x : number
2525
>y : string
26-
>z : any
27-
>[1, "hello"] : [number, string, undefined?]
26+
>z : undefined
27+
>[1, "hello"] : [number, string]
2828
>1 : 1
2929
>"hello" : "hello"
3030

@@ -255,16 +255,16 @@ function f8() {
255255
>f8 : () => void
256256

257257
var [a, b, c] = []; // Error, [] is an empty tuple
258-
>a : any
259-
>b : any
260-
>c : any
261-
>[] : [undefined?, undefined?, undefined?]
258+
>a : undefined
259+
>b : undefined
260+
>c : undefined
261+
>[] : []
262262

263263
var [d, e, f] = [1]; // Error, [1] is a tuple
264264
>d : number
265-
>e : any
266-
>f : any
267-
>[1] : [number, undefined?, undefined?]
265+
>e : undefined
266+
>f : undefined
267+
>[1] : [number]
268268
>1 : 1
269269
}
270270

0 commit comments

Comments
 (0)