Skip to content

Commit 0321275

Browse files
committed
Merge pull request microsoft#2754 from Microsoft/destructuringFixes
Destructuring fixes
2 parents 4dfc518 + 2c45e72 commit 0321275

101 files changed

Lines changed: 739 additions & 556 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/compiler/checker.ts

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,25 +2197,7 @@ module ts {
21972197
}
21982198
else if (hasSpreadElement) {
21992199
let unionOfElements = getUnionType(elementTypes);
2200-
if (languageVersion >= ScriptTarget.ES6) {
2201-
// If the user has something like:
2202-
//
2203-
// function fun(...[a, ...b]) { }
2204-
//
2205-
// Normally, in ES6, the implied type of an array binding pattern with a rest element is
2206-
// an iterable. However, there is a requirement in our type system that all rest
2207-
// parameters be array types. To satisfy this, we have an exception to the rule that
2208-
// says the type of an array binding pattern with a rest element is an array type
2209-
// if it is *itself* in a rest parameter. It will still be compatible with a spreaded
2210-
// iterable argument, but within the function it will be an array.
2211-
let parent = pattern.parent;
2212-
let isRestParameter = parent.kind === SyntaxKind.Parameter &&
2213-
pattern === (<ParameterDeclaration>parent).name &&
2214-
(<ParameterDeclaration>parent).dotDotDotToken !== undefined;
2215-
return isRestParameter ? createArrayType(unionOfElements) : createIterableType(unionOfElements);
2216-
}
2217-
2218-
return createArrayType(unionOfElements);
2200+
return languageVersion >= ScriptTarget.ES6 ? createIterableType(unionOfElements) : createArrayType(unionOfElements);
22192201
}
22202202

22212203
// If the pattern has at least one element, and no rest element, then it should imply a tuple type.
@@ -6035,14 +6017,38 @@ module ts {
60356017
}
60366018
let hasSpreadElement = false;
60376019
let elementTypes: Type[] = [];
6020+
let inDestructuringPattern = isAssignmentTarget(node);
60386021
for (let e of elements) {
6039-
let type = checkExpression(e, contextualMapper);
6040-
elementTypes.push(type);
6022+
if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElementExpression) {
6023+
// Given the following situation:
6024+
// var c: {};
6025+
// [...c] = ["", 0];
6026+
//
6027+
// c is represented in the tree as a spread element in an array literal.
6028+
// But c really functions as a rest element, and its purpose is to provide
6029+
// a contextual type for the right hand side of the assignment. Therefore,
6030+
// instead of calling checkExpression on "...c", which will give an error
6031+
// if c is not iterable/array-like, we need to act as if we are trying to
6032+
// get the contextual element type from it. So we do something similar to
6033+
// getContextualTypeForElementExpression, which will crucially not error
6034+
// if there is no index type / iterated type.
6035+
let restArrayType = checkExpression((<SpreadElementExpression>e).expression, contextualMapper);
6036+
let restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) ||
6037+
(languageVersion >= ScriptTarget.ES6 ? checkIteratedType(restArrayType, /*expressionForError*/ undefined) : undefined);
6038+
6039+
if (restElementType) {
6040+
elementTypes.push(restElementType);
6041+
}
6042+
}
6043+
else {
6044+
let type = checkExpression(e, contextualMapper);
6045+
elementTypes.push(type);
6046+
}
60416047
hasSpreadElement = hasSpreadElement || e.kind === SyntaxKind.SpreadElementExpression;
60426048
}
60436049
if (!hasSpreadElement) {
60446050
let contextualType = getContextualType(node);
6045-
if (contextualType && contextualTypeIsTupleLikeType(contextualType) || isAssignmentTarget(node)) {
6051+
if (contextualType && contextualTypeIsTupleLikeType(contextualType) || inDestructuringPattern) {
60466052
return createTupleType(elementTypes);
60476053
}
60486054
}
@@ -7633,7 +7639,7 @@ module ts {
76337639
// This elementType will be used if the specific property corresponding to this index is not
76347640
// present (aka the tuple element property). This call also checks that the parentType is in
76357641
// fact an iterable or array (depending on target language).
7636-
let elementType = checkIteratedTypeOrElementType(sourceType, node, /*allowStringInput*/ false);
7642+
let elementType = checkIteratedTypeOrElementType(sourceType, node, /*allowStringInput*/ false) || unknownType;
76377643
let elements = node.elements;
76387644
for (let i = 0; i < elements.length; i++) {
76397645
let e = elements[i];
@@ -7657,11 +7663,17 @@ module ts {
76577663
}
76587664
}
76597665
else {
7660-
if (i === elements.length - 1) {
7661-
checkReferenceAssignment((<SpreadElementExpression>e).expression, createArrayType(elementType), contextualMapper);
7666+
if (i < elements.length - 1) {
7667+
error(e, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
76627668
}
76637669
else {
7664-
error(e, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
7670+
let restExpression = (<SpreadElementExpression>e).expression;
7671+
if (restExpression.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>restExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
7672+
error((<BinaryExpression>restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer);
7673+
}
7674+
else {
7675+
checkDestructuringAssignment(restExpression, createArrayType(elementType), contextualMapper);
7676+
}
76657677
}
76667678
}
76677679
}
@@ -8121,10 +8133,11 @@ module ts {
81218133
if (node.questionToken && isBindingPattern(node.name) && func.body) {
81228134
error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature);
81238135
}
8124-
if (node.dotDotDotToken) {
8125-
if (!isArrayType(getTypeOfSymbol(node.symbol))) {
8126-
error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type);
8127-
}
8136+
8137+
// Only check rest parameter type if it's not a binding pattern. Since binding patterns are
8138+
// not allowed in a rest parameter, we already have an error from checkGrammarParameterList.
8139+
if (node.dotDotDotToken && !isBindingPattern(node.name) && !isArrayType(getTypeOfSymbol(node.symbol))) {
8140+
error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type);
81288141
}
81298142
}
81308143

@@ -9427,6 +9440,10 @@ module ts {
94279440
}
94289441

94299442
function checkIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean): Type {
9443+
if (inputType.flags & TypeFlags.Any) {
9444+
return inputType;
9445+
}
9446+
94309447
if (languageVersion >= ScriptTarget.ES6) {
94319448
return checkIteratedType(inputType, errorNode) || anyType;
94329449
}
@@ -12270,6 +12287,10 @@ module ts {
1227012287
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list);
1227112288
}
1227212289

12290+
if (isBindingPattern(parameter.name)) {
12291+
return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern);
12292+
}
12293+
1227312294
if (parameter.questionToken) {
1227412295
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional);
1227512296
}
@@ -12756,6 +12777,11 @@ module ts {
1275612777
if (node !== elements[elements.length - 1]) {
1275712778
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
1275812779
}
12780+
12781+
if (node.name.kind === SyntaxKind.ArrayBindingPattern || node.name.kind === SyntaxKind.ObjectBindingPattern) {
12782+
return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern);
12783+
}
12784+
1275912785
if (node.initializer) {
1276012786
// Error on equals token which immediate precedes the initializer
1276112787
return grammarErrorAtPos(getSourceFileOfNode(node), node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer);

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ module ts {
363363
External_module_0_uses_export_and_cannot_be_used_with_export_Asterisk: { code: 2498, category: DiagnosticCategory.Error, key: "External module '{0}' uses 'export =' and cannot be used with 'export *'." },
364364
An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments: { code: 2499, category: DiagnosticCategory.Error, key: "An interface can only extend an identifier/qualified-name with optional type arguments." },
365365
A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments: { code: 2500, category: DiagnosticCategory.Error, key: "A class can only implement an identifier/qualified-name with optional type arguments." },
366+
A_rest_element_cannot_contain_a_binding_pattern: { code: 2501, category: DiagnosticCategory.Error, key: "A rest element cannot contain a binding pattern." },
366367
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
367368
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." },
368369
Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." },

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,10 @@
14391439
"category": "Error",
14401440
"code": 2500
14411441
},
1442+
"A rest element cannot contain a binding pattern.": {
1443+
"category": "Error",
1444+
"code": 2501
1445+
},
14421446

14431447
"Import declaration '{0}' is using private name '{1}'.": {
14441448
"category": "Error",

0 commit comments

Comments
 (0)