Skip to content

Commit ae859d6

Browse files
committed
Update destructuring to support optional and rest elements in tuples
1 parent 43bac20 commit ae859d6

File tree

1 file changed

+60
-41
lines changed

1 file changed

+60
-41
lines changed

src/compiler/checker.ts

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4514,22 +4514,27 @@ namespace ts {
45144514
// present (aka the tuple element property). This call also checks that the parentType is in
45154515
// fact an iterable or array (depending on target language).
45164516
const elementType = checkIteratedTypeOrElementType(parentType, pattern, /*allowStringInput*/ false, /*allowAsyncIterables*/ false);
4517+
const index = pattern.elements.indexOf(declaration);
45174518
if (declaration.dotDotDotToken) {
4518-
// Rest element has an array type with the same element type as the parent type
4519-
type = createArrayType(elementType);
4519+
// If the parent is a tuple type, the rest element has an array type with a union of the
4520+
// remaining tuple element types. Otherwise, the rest element has an array type with same
4521+
// element type as the parent type.
4522+
type = isTupleType(parentType) ?
4523+
getArrayLiteralType((parentType.typeArguments || emptyArray).slice(index, getTypeReferenceArity(parentType))) :
4524+
createArrayType(elementType);
45204525
}
45214526
else {
45224527
// Use specific property type when parent is a tuple or numeric index type when parent is an array
4523-
const propName = "" + pattern.elements.indexOf(declaration);
4524-
type = isTupleLikeType(parentType)
4525-
? getTypeOfPropertyOfType(parentType, propName as __String)
4526-
: elementType;
4528+
const index = pattern.elements.indexOf(declaration);
4529+
type = isTupleLikeType(parentType) ?
4530+
getTupleElementType(parentType, index) || declaration.initializer && checkDeclarationInitializer(declaration) :
4531+
elementType;
45274532
if (!type) {
45284533
if (isTupleType(parentType)) {
45294534
error(declaration, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(parentType), getTypeReferenceArity(<TypeReference>parentType), pattern.elements.length);
45304535
}
45314536
else {
4532-
error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), propName);
4537+
error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), "" + index);
45334538
}
45344539
return errorType;
45354540
}
@@ -4800,7 +4805,7 @@ namespace ts {
48004805
// pattern. Otherwise, it is the type any.
48014806
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type {
48024807
if (element.initializer) {
4803-
return checkDeclarationInitializer(element);
4808+
return addOptionality(checkDeclarationInitializer(element));
48044809
}
48054810
if (isBindingPattern(element.name)) {
48064811
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
@@ -4848,12 +4853,13 @@ namespace ts {
48484853
function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
48494854
const elements = pattern.elements;
48504855
const lastElement = lastOrUndefined(elements);
4851-
if (!lastElement || (!isOmittedExpression(lastElement) && lastElement.dotDotDotToken)) {
4856+
const hasRestElement = !!(lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken);
4857+
if (elements.length === 0 || elements.length === 1 && hasRestElement) {
48524858
return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType;
48534859
}
4854-
// If the pattern has at least one element, and no rest element, then it should imply a tuple type.
48554860
const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors));
4856-
let result = <TypeReference>createTupleType(elementTypes);
4861+
const minLength = findLastIndex(elements, e => !isOmittedExpression(e) && !hasDefaultValue(e), elements.length - (hasRestElement ? 2 : 1)) + 1;
4862+
let result = <TypeReference>createTupleType(elementTypes, minLength, hasRestElement);
48574863
if (includePatternInType) {
48584864
result = cloneTypeReference(result);
48594865
result.pattern = pattern;
@@ -12076,6 +12082,12 @@ namespace ts {
1207612082
return isTupleType(type) || !!getPropertyOfType(type, "0" as __String);
1207712083
}
1207812084

12085+
function getTupleElementType(type: Type, index: number) {
12086+
return isTupleType(type) ?
12087+
index < getLengthOfTupleType(type) ? type.typeArguments![index] : getRestTypeOfTupleType(type) :
12088+
getTypeOfPropertyOfType(type, "" + index as __String);
12089+
}
12090+
1207912091
function isNeitherUnitTypeNorNever(type: Type): boolean {
1208012092
return !(type.flags & (TypeFlags.Unit | TypeFlags.Never));
1208112093
}
@@ -13471,7 +13483,7 @@ namespace ts {
1347113483
}
1347213484

1347313485
function getTypeOfDestructuredArrayElement(type: Type, index: number) {
13474-
return isTupleLikeType(type) && getTypeOfPropertyOfType(type, "" + index as __String) ||
13486+
return isTupleLikeType(type) && getTupleElementType(type, index) ||
1347513487
checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) ||
1347613488
errorType;
1347713489
}
@@ -15993,11 +16005,12 @@ namespace ts {
1599316005

1599416006
function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined): Type {
1599516007
const elements = node.elements;
16008+
const elementCount = elements.length;
1599616009
let hasNonEndingSpreadElement = false;
1599716010
const elementTypes: Type[] = [];
1599816011
const inDestructuringPattern = isAssignmentTarget(node);
1599916012
const contextualType = getApparentTypeOfContextualType(node);
16000-
for (let index = 0; index < elements.length; index++) {
16013+
for (let index = 0; index < elementCount; index++) {
1600116014
const e = elements[index];
1600216015
if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElement) {
1600316016
// Given the following situation:
@@ -16024,41 +16037,48 @@ namespace ts {
1602416037
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType);
1602516038
elementTypes.push(type);
1602616039
}
16027-
hasNonEndingSpreadElement = hasNonEndingSpreadElement || (index < elements.length - 1 && e.kind === SyntaxKind.SpreadElement);
16040+
if (index < elementCount - 1 && e.kind === SyntaxKind.SpreadElement) {
16041+
hasNonEndingSpreadElement = true;
16042+
}
1602816043
}
1602916044
if (!hasNonEndingSpreadElement) {
16045+
const hasRestElement = elementCount > 0 && elements[elementCount - 1].kind === SyntaxKind.SpreadElement;
16046+
const minLength = elementCount - (hasRestElement ? 1 : 0);
1603016047
// If array literal is actually a destructuring pattern, mark it as an implied type. We do this such
1603116048
// that we get the same behavior for "var [x, y] = []" and "[x, y] = []".
16032-
if (inDestructuringPattern && elementTypes.length) {
16033-
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes));
16049+
if (inDestructuringPattern && minLength > 0) {
16050+
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasRestElement));
1603416051
type.pattern = node;
1603516052
return type;
1603616053
}
1603716054
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) {
1603816055
const pattern = contextualType.pattern;
1603916056
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
1604016057
// tuple type with the corresponding binding or assignment element types to make the lengths equal.
16041-
if (pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
16058+
if (!hasRestElement && pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
1604216059
const patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
16043-
for (let i = elementTypes.length; i < patternElements.length; i++) {
16044-
const patternElement = patternElements[i];
16045-
if (hasDefaultValue(patternElement)) {
16060+
for (let i = elementCount; i < patternElements.length; i++) {
16061+
const e = patternElements[i];
16062+
if (hasDefaultValue(e)) {
1604616063
elementTypes.push((<TypeReference>contextualType).typeArguments![i]);
1604716064
}
16048-
else {
16049-
if (patternElement.kind !== SyntaxKind.OmittedExpression) {
16050-
error(patternElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
16065+
else if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && (<BindingElement>e).dotDotDotToken || e.kind === SyntaxKind.SpreadElement)) {
16066+
if (e.kind !== SyntaxKind.OmittedExpression) {
16067+
error(e, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
1605116068
}
1605216069
elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType);
1605316070
}
1605416071
}
1605516072
}
16056-
const hasSpreadElement = elements.length > 0 && elements[elements.length - 1].kind === SyntaxKind.SpreadElement;
16057-
return createTupleType(elementTypes, elementTypes.length - (hasSpreadElement ? 1 : 0), hasSpreadElement);
16073+
return createTupleType(elementTypes, minLength, hasRestElement);
1605816074
}
1605916075
}
16076+
return getArrayLiteralType(elementTypes, UnionReduction.Subtype);
16077+
}
16078+
16079+
function getArrayLiteralType(elementTypes: Type[], unionReduction = UnionReduction.Literal) {
1606016080
return createArrayType(elementTypes.length ?
16061-
getUnionType(elementTypes, UnionReduction.Subtype) :
16081+
getUnionType(elementTypes, unionReduction) :
1606216082
strictNullChecks ? implicitNeverType : undefinedWideningType);
1606316083
}
1606416084

@@ -20418,24 +20438,20 @@ namespace ts {
2041820438
if (element.kind !== SyntaxKind.OmittedExpression) {
2041920439
if (element.kind !== SyntaxKind.SpreadElement) {
2042020440
const propName = "" + elementIndex as __String;
20421-
const type = isTypeAny(sourceType)
20422-
? sourceType
20423-
: isTupleLikeType(sourceType)
20424-
? getTypeOfPropertyOfType(sourceType, propName)
20425-
: elementType;
20441+
const type = isTypeAny(sourceType) ? sourceType :
20442+
isTupleLikeType(sourceType) ? getTupleElementType(sourceType, elementIndex) :
20443+
elementType;
2042620444
if (type) {
2042720445
return checkDestructuringAssignment(element, type, checkMode);
2042820446
}
20447+
// We still need to check element expression here because we may need to set appropriate flag on the expression
20448+
// such as NodeCheckFlags.LexicalThis on "this"expression.
20449+
checkExpression(element);
20450+
if (isTupleType(sourceType)) {
20451+
error(element, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(sourceType), getTypeReferenceArity(<TypeReference>sourceType), elements.length);
20452+
}
2042920453
else {
20430-
// We still need to check element expression here because we may need to set appropriate flag on the expression
20431-
// such as NodeCheckFlags.LexicalThis on "this"expression.
20432-
checkExpression(element);
20433-
if (isTupleType(sourceType)) {
20434-
error(element, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(sourceType), getTypeReferenceArity(<TypeReference>sourceType), elements.length);
20435-
}
20436-
else {
20437-
error(element, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName as string);
20438-
}
20454+
error(element, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName as string);
2043920455
}
2044020456
}
2044120457
else {
@@ -20449,7 +20465,10 @@ namespace ts {
2044920465
}
2045020466
else {
2045120467
checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
20452-
return checkDestructuringAssignment(restExpression, createArrayType(elementType), checkMode);
20468+
const type = isTupleType(sourceType) ?
20469+
getArrayLiteralType((sourceType.typeArguments || emptyArray).slice(elementIndex, getTypeReferenceArity(sourceType))) :
20470+
createArrayType(elementType);
20471+
return checkDestructuringAssignment(restExpression, type, checkMode);
2045320472
}
2045420473
}
2045520474
}

0 commit comments

Comments
 (0)