From ebc7e7e0e326cd501d959b7aa8f58f6a8dbfb36e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Dec 2014 06:45:01 -0800 Subject: [PATCH 01/13] Parsing of spread element expressions --- src/compiler/checker.ts | 2 ++ src/compiler/parser.ts | 12 +++++++++++- src/compiler/types.ts | 7 ++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8c383368df2..ce58b6d27c7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4553,6 +4553,7 @@ module ts { case SyntaxKind.VoidExpression: case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.ConditionalExpression: + case SyntaxKind.SpreadElementExpression: case SyntaxKind.Block: case SyntaxKind.VariableStatement: case SyntaxKind.ExpressionStatement: @@ -8876,6 +8877,7 @@ module ts { case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.BinaryExpression: case SyntaxKind.ConditionalExpression: + case SyntaxKind.SpreadElementExpression: case SyntaxKind.Block: case SyntaxKind.ModuleBlock: case SyntaxKind.VariableStatement: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d406b938d4e..fe9944f930d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -356,6 +356,8 @@ module ts { return child((node).condition) || child((node).whenTrue) || child((node).whenFalse); + case SyntaxKind.SpreadElementExpression: + return child((node).expression); case SyntaxKind.Block: case SyntaxKind.TryBlock: case SyntaxKind.FinallyBlock: @@ -622,6 +624,7 @@ module ts { case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.BinaryExpression: case SyntaxKind.ConditionalExpression: + case SyntaxKind.SpreadElementExpression: case SyntaxKind.TemplateExpression: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.OmittedExpression: @@ -3354,8 +3357,15 @@ module ts { : parseAssignmentExpressionOrHigher(); } + function parseSpreadElement(): Expression { + var node = createNode(SyntaxKind.SpreadElementExpression); + parseExpected(SyntaxKind.DotDotDotToken); + node.expression = parseAssignmentExpressionOrHigher(); + return finishNode(node); + } + function parseArrayLiteralElement(): Expression { - return parseAssignmentExpressionOrOmittedExpression(); + return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() : parseAssignmentExpressionOrOmittedExpression(); } function parseArgumentExpression(): Expression { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 19baf052406..72514a622d1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -192,6 +192,7 @@ module ts { ConditionalExpression, TemplateExpression, YieldExpression, + SpreadElementExpression, OmittedExpression, // Misc TemplateSpan, @@ -649,7 +650,11 @@ module ts { export interface ArrayLiteralExpression extends PrimaryExpression { elements: NodeArray; } - + + export interface SpreadElementExpression extends Expression { + expression: Expression; + } + // An ObjectLiteralExpression is the declaration node for an anonymous symbol. export interface ObjectLiteralExpression extends PrimaryExpression, Declaration { properties: NodeArray; From afa31119551613ddf17f289c2026fe0f164955ec Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Dec 2014 16:23:57 -0800 Subject: [PATCH 02/13] Check and emit of spread elements in array literals --- src/compiler/checker.ts | 26 +++++- src/compiler/emitter.ts | 176 ++++++++++++++++++++++++++-------------- src/compiler/parser.ts | 2 +- 3 files changed, 138 insertions(+), 66 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce58b6d27c7..18922653e60 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5315,14 +5315,34 @@ module ts { return false; } + function checkSpreadElementExpression(node: SpreadElementExpression, contextualMapper?: TypeMapper): Type { + var type = checkExpressionCached(node.expression, contextualMapper); + if (!isTypeAssignableTo(type, anyArrayType)) { + error(node.expression, Diagnostics.Type_0_is_not_an_array_type, typeToString(type)); + return unknownType; + } + return type; + } + function checkArrayLiteral(node: ArrayLiteralExpression, contextualMapper?: TypeMapper): Type { var elements = node.elements; if (!elements.length) { return createArrayType(undefinedType); } - var elementTypes = map(elements, e => checkExpression(e, contextualMapper)); var contextualType = getContextualType(node); - if ((contextualType && contextualTypeIsTupleLikeType(contextualType)) || isAssignmentTarget(node)) { + var isTupleType = (contextualType && contextualTypeIsTupleLikeType(contextualType)) || isAssignmentTarget(node); + var elementTypes: Type[] = []; + forEach(elements, e => { + var type = checkExpression(e, contextualMapper); + if (e.kind === SyntaxKind.SpreadElementExpression) { + elementTypes.push(getIndexTypeOfType(type, IndexKind.Number) || anyType); + isTupleType = false; + } + else { + elementTypes.push(type); + } + }); + if (isTupleType) { return createTupleType(elementTypes); } return createArrayType(getUnionType(elementTypes)); @@ -7009,6 +7029,8 @@ module ts { return checkBinaryExpression(node, contextualMapper); case SyntaxKind.ConditionalExpression: return checkConditionalExpression(node, contextualMapper); + case SyntaxKind.SpreadElementExpression: + return checkSpreadElementExpression(node, contextualMapper); case SyntaxKind.OmittedExpression: return undefinedType; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 9428f94bb52..18a5571906b 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1949,35 +1949,58 @@ module ts { } } + function emitParenthesized(node: Node, parenthesized: boolean) { + if (parenthesized) { + write("("); + } + emit(node); + if (parenthesized) { + write(")"); + } + } + function emitTrailingCommaIfPresent(nodeList: NodeArray): void { if (nodeList.hasTrailingComma) { write(","); } } - function emitCommaList(nodes: Node[], count?: number) { - if (!(count >= 0)) { - count = nodes.length; + function emitList(nodes: Node[], start: number, count: number, multiLine: boolean, trailingComma: boolean) { + if (multiLine) { + increaseIndent(); } - if (nodes) { - for (var i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { + if (multiLine) { + if (i) { + write(","); + } + writeLine(); + } + else { if (i) { write(", "); } - emit(nodes[i]); } + emit(nodes[start + i]); + } + if (trailingComma) { + write(","); + } + if (multiLine) { + decreaseIndent(); + writeLine(); + } + } + + function emitCommaList(nodes: Node[]) { + if (nodes) { + emitList(nodes, 0, nodes.length, /*multiline*/ false, /*trailingComma*/ false); } } function emitMultiLineList(nodes: Node[]) { if (nodes) { - for (var i = 0; i < nodes.length; i++) { - if (i) { - write(","); - } - writeLine(); - emit(nodes[i]); - } + emitList(nodes, 0, nodes.length, /*multiline*/ true, /*trailingComma*/ false); } } @@ -2055,17 +2078,8 @@ module ts { // "abc" + (1 << 2) + "" var needsParens = templateSpan.expression.kind !== SyntaxKind.ParenthesizedExpression && comparePrecedenceToBinaryPlus(templateSpan.expression) !== Comparison.GreaterThan; - write(" + "); - - if (needsParens) { - write("("); - } - emit(templateSpan.expression); - if (needsParens) { - write(")"); - } - + emitParenthesized(templateSpan.expression, needsParens); // Only emit if the literal is non-empty. // The binary '+' operator is left-associative, so the first string concatenation // with the head will force the result up to this point to be a string. @@ -2235,59 +2249,95 @@ module ts { function emitObjectBindingPattern(node: BindingPattern) { write("{ "); - emitCommaList(node.elements); - emitTrailingCommaIfPresent(node.elements); + var elements = node.elements; + emitList(elements, 0, elements.length, /*multiLine*/ false, /*trailingComma*/ elements.hasTrailingComma); write(" }"); } function emitArrayBindingPattern(node: BindingPattern) { write("["); - emitCommaList(node.elements); - emitTrailingCommaIfPresent(node.elements); + var elements = node.elements; + emitList(elements, 0, elements.length, /*multiLine*/ false, /*trailingComma*/ elements.hasTrailingComma); write("]"); } - function emitArrayLiteral(node: ArrayLiteralExpression) { - if (node.flags & NodeFlags.MultiLine) { - write("["); - increaseIndent(); - emitMultiLineList(node.elements); - emitTrailingCommaIfPresent(node.elements); - decreaseIndent(); - writeLine(); - write("]"); + function emitSpreadElementExpression(node: SpreadElementExpression) { + write("..."); + emit((node).expression); + } + + function needsParenthesisForPropertyAccess(node: Expression) { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.ParenthesizedExpression: + return false; } - else { + return true; + } + + function emitArrayLiteral(node: ArrayLiteralExpression) { + var elements = node.elements; + var length = elements.length; + if (length === 0) { + write("[]"); + return; + } + if (compilerOptions.target >= ScriptTarget.ES6) { write("["); - emitCommaList(node.elements); - emitTrailingCommaIfPresent(node.elements); + emitList(elements, 0, elements.length, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0, + /*trailingComma*/ elements.hasTrailingComma); write("]"); + return; + } + var pos = 0; + var group = 0; + while (pos < length) { + if (group === 1) { + write(".concat("); + } + else if (group > 1) { + write(", "); + } + var e = elements[pos]; + if (e.kind === SyntaxKind.SpreadElementExpression) { + e = (e).expression; + emitParenthesized(e, /*parenthesized*/ group === 0 && needsParenthesisForPropertyAccess(e)); + pos++; + } + else { + for (var i = pos; i < length && elements[i].kind !== SyntaxKind.SpreadElementExpression; i++); + write("["); + emitList(elements, pos, i - pos, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0, + /*trailingComma*/ elements.hasTrailingComma); + write("]"); + pos = i; + } + group++; + } + if (group > 1) { + write(")"); } } function emitObjectLiteral(node: ObjectLiteralExpression) { - if (!node.properties.length) { - write("{}"); - } - else if (node.flags & NodeFlags.MultiLine) { - write("{"); - increaseIndent(); - emitMultiLineList(node.properties); - if (compilerOptions.target >= ScriptTarget.ES5) { - emitTrailingCommaIfPresent(node.properties); + write("{"); + var properties = node.properties; + if (properties.length) { + var multiLine = (node.flags & NodeFlags.MultiLine) !== 0; + if (!multiLine) { + write(" "); } - decreaseIndent(); - writeLine(); - write("}"); - } - else { - write("{ "); - emitCommaList(node.properties); - if (compilerOptions.target >= ScriptTarget.ES5) { - emitTrailingCommaIfPresent(node.properties); + emitList(properties, 0, properties.length, /*multiLine*/ multiLine, + /*trailingComma*/ properties.hasTrailingComma && compilerOptions.target >= ScriptTarget.ES5); + if (!multiLine) { + write(" "); } - write(" }"); } + write("}"); } function emitComputedPropertyName(node: ComputedPropertyName) { @@ -2554,11 +2604,8 @@ module ts { } function emitExpressionStatement(node: ExpressionStatement) { - var isArrowExpression = node.expression.kind === SyntaxKind.ArrowFunction; emitLeadingComments(node); - if (isArrowExpression) write("("); - emit(node.expression); - if (isArrowExpression) write(")"); + emitParenthesized(node.expression, /*parenthesized*/ node.expression.kind === SyntaxKind.ArrowFunction); write(";"); emitTrailingComments(node); } @@ -3137,7 +3184,8 @@ module ts { increaseIndent(); write("("); if (node) { - emitCommaList(node.parameters, node.parameters.length - (hasRestParameters(node) ? 1 : 0)); + var parameters = node.parameters; + emitList(parameters, 0, parameters.length - (hasRestParameters(node) ? 1 : 0), /*multiLine*/ false, /*trailingComma*/ false); } write(")"); decreaseIndent(); @@ -3904,6 +3952,8 @@ module ts { return emitBinaryExpression(node); case SyntaxKind.ConditionalExpression: return emitConditionalExpression(node); + case SyntaxKind.SpreadElementExpression: + return emitSpreadElementExpression(node); case SyntaxKind.OmittedExpression: return; case SyntaxKind.Block: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index fe9944f930d..89467ba456f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1591,7 +1591,7 @@ module ts { case ParsingContext.ArgumentExpressions: return token === SyntaxKind.CommaToken || isStartOfExpression(); case ParsingContext.ArrayLiteralMembers: - return token === SyntaxKind.CommaToken || isStartOfExpression(); + return token === SyntaxKind.CommaToken || token === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: return isStartOfParameter(); case ParsingContext.TypeArguments: From 6babef417f3ac5ffde4beeccea915c19a0161f1f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Dec 2014 17:41:24 -0800 Subject: [PATCH 03/13] Rest element support in array literal destructuring assignment --- src/compiler/checker.ts | 28 +++++++++++---- .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 +++ src/compiler/emitter.ts | 34 +++++++++++++------ 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 18922653e60..4b8437f4f1c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6689,15 +6689,25 @@ module ts { for (var i = 0; i < elements.length; i++) { var e = elements[i]; if (e.kind !== SyntaxKind.OmittedExpression) { - var propName = "" + i; - var type = sourceType.flags & TypeFlags.Any ? sourceType : - isTupleLikeType(sourceType) ? getTypeOfPropertyOfType(sourceType, propName) : - getIndexTypeOfType(sourceType, IndexKind.Number); - if (type) { - checkDestructuringAssignment(e, type, contextualMapper); + if (e.kind !== SyntaxKind.SpreadElementExpression) { + var propName = "" + i; + var type = sourceType.flags & TypeFlags.Any ? sourceType : + isTupleLikeType(sourceType) ? getTypeOfPropertyOfType(sourceType, propName) : + getIndexTypeOfType(sourceType, IndexKind.Number); + if (type) { + checkDestructuringAssignment(e, type, contextualMapper); + } + else { + error(e, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName); + } } else { - error(e, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName); + if (i === elements.length - 1) { + checkReferenceAssignment((e).expression, sourceType, contextualMapper); + } + else { + error(e, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern); + } } } } @@ -6715,6 +6725,10 @@ module ts { if (target.kind === SyntaxKind.ArrayLiteralExpression) { return checkArrayLiteralAssignment(target, sourceType, contextualMapper); } + return checkReferenceAssignment(target, sourceType, contextualMapper); + } + + function checkReferenceAssignment(target: Expression, sourceType: Type, contextualMapper?: TypeMapper): Type { var targetType = checkExpression(target, contextualMapper); if (checkReferenceExpression(target, Diagnostics.Invalid_left_hand_side_of_assignment_expression, Diagnostics.Left_hand_side_of_assignment_expression_cannot_be_a_constant)) { checkTypeAssignableTo(sourceType, targetType, target, /*headMessage*/ undefined); diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 17dbae83bf2..04049238661 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -294,6 +294,7 @@ module ts { Type_0_has_no_property_1_and_no_string_index_signature: { code: 2459, category: DiagnosticCategory.Error, key: "Type '{0}' has no property '{1}' and no string index signature." }, Type_0_has_no_property_1: { code: 2460, category: DiagnosticCategory.Error, key: "Type '{0}' has no property '{1}'." }, Type_0_is_not_an_array_type: { code: 2461, category: DiagnosticCategory.Error, key: "Type '{0}' is not an array type." }, + A_rest_element_must_be_last_in_an_array_destructuring_pattern: { code: 2462, category: DiagnosticCategory.Error, key: "A rest element must be last in an array destructuring pattern" }, Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." }, 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}'." }, 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}'." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a06b71d460e..6663ff057b8 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1173,6 +1173,10 @@ "category": "Error", "code": 2461 }, + "A rest element must be last in an array destructuring pattern": { + "category": "Error", + "code": 2462 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 18a5571906b..239a89c6694 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2947,7 +2947,16 @@ module ts { for (var i = 0; i < elements.length; i++) { var e = elements[i]; if (e.kind !== SyntaxKind.OmittedExpression) { - emitDestructuringAssignment(e, createElementAccess(value, createNumericLiteral(i))); + if (e.kind !== SyntaxKind.SpreadElementExpression) { + emitDestructuringAssignment(e, createElementAccess(value, createNumericLiteral(i))); + } + else { + if (i === elements.length - 1) { + value = ensureIdentifier(value); + emitAssignment((e).expression, value); + write(".slice(" + i + ")"); + } + } } } } @@ -3772,17 +3781,18 @@ module ts { increaseIndent(); emitCaptureThisForNodeIfNecessary(node); emitLinesStartingAt(node.statements, startIndex); + emitTempDeclarations(/*newLine*/ true); var exportName = resolver.getExportAssignmentName(node); if (exportName) { writeLine(); - var exportAssignement = getFirstExportAssignment(node); - emitStart(exportAssignement); + var exportAssignment = getFirstExportAssignment(node); + emitStart(exportAssignment); write("return "); - emitStart(exportAssignement.exportName); + emitStart(exportAssignment.exportName); write(exportName); - emitEnd(exportAssignement.exportName); + emitEnd(exportAssignment.exportName); write(";"); - emitEnd(exportAssignement); + emitEnd(exportAssignment); } decreaseIndent(); writeLine(); @@ -3792,17 +3802,18 @@ module ts { function emitCommonJSModule(node: SourceFile, startIndex: number) { emitCaptureThisForNodeIfNecessary(node); emitLinesStartingAt(node.statements, startIndex); + emitTempDeclarations(/*newLine*/ true); var exportName = resolver.getExportAssignmentName(node); if (exportName) { writeLine(); - var exportAssignement = getFirstExportAssignment(node); - emitStart(exportAssignement); + var exportAssignment = getFirstExportAssignment(node); + emitStart(exportAssignment); write("module.exports = "); - emitStart(exportAssignement.exportName); + emitStart(exportAssignment.exportName); write(exportName); - emitEnd(exportAssignement.exportName); + emitEnd(exportAssignment.exportName); write(";"); - emitEnd(exportAssignement); + emitEnd(exportAssignment); } } @@ -3858,6 +3869,7 @@ module ts { else { emitCaptureThisForNodeIfNecessary(node); emitLinesStartingAt(node.statements, startIndex); + emitTempDeclarations(/*newLine*/ true); } emitLeadingComments(node.endOfFileToken); From a42df7547e5df8c191240490bce91ba20a0f9dc4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Dec 2014 18:27:08 -0800 Subject: [PATCH 04/13] Rest element support in array destructuring declarations --- src/compiler/diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/emitter.ts | 13 +++++++++++-- src/compiler/parser.ts | 13 ++++++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 04049238661..e1ea3e21de0 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -144,6 +144,7 @@ module ts { Array_element_destructuring_pattern_expected: { code: 1181, category: DiagnosticCategory.Error, key: "Array element destructuring pattern expected." }, A_destructuring_declaration_must_have_an_initializer: { code: 1182, category: DiagnosticCategory.Error, key: "A destructuring declaration must have an initializer." }, Destructuring_declarations_are_not_allowed_in_ambient_contexts: { code: 1183, category: DiagnosticCategory.Error, key: "Destructuring declarations are not allowed in ambient contexts." }, + A_rest_element_cannot_have_an_initializer: { code: 1184, category: DiagnosticCategory.Error, key: "A rest element cannot have an initializer." }, Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." }, Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." }, Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 6663ff057b8..6b4274fbc5d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -568,6 +568,10 @@ "category": "Error", "code": 1183 }, + "A rest element cannot have an initializer.": { + "category": "Error", + "code": 1184 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 239a89c6694..2fcdd876864 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -3022,8 +3022,17 @@ module ts { emitBindingElement(element, createPropertyAccess(value, propName)); } else if (element.kind !== SyntaxKind.OmittedExpression) { - // Rewrite element to a declaration that accesses array element at index i - emitBindingElement(element, createElementAccess(value, createNumericLiteral(i))); + if (!element.dotDotDotToken) { + // Rewrite element to a declaration that accesses array element at index i + emitBindingElement(element, createElementAccess(value, createNumericLiteral(i))); + } + else { + if (i === elements.length - 1) { + value = ensureIdentifier(value); + emitAssignment(element.name, value); + write(".slice(" + i + ")"); + } + } } } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 89467ba456f..94cab916a05 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1585,7 +1585,7 @@ module ts { case ParsingContext.VariableDeclarations: return isIdentifierOrPattern(); case ParsingContext.ArrayBindingElements: - return token === SyntaxKind.CommaToken || isIdentifierOrPattern(); + return token === SyntaxKind.CommaToken || token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern(); case ParsingContext.TypeParameters: return isIdentifier(); case ParsingContext.ArgumentExpressions: @@ -3886,6 +3886,7 @@ module ts { } } else { + node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); node.name = parseIdentifierOrPattern(); } node.initializer = parseInitializer(/*inParameter*/ false); @@ -5748,6 +5749,16 @@ module ts { } function checkBindingElement(node: BindingElement) { + if (node.dotDotDotToken) { + var elements = (node.parent).elements; + if (node !== elements[elements.length - 1]) { + return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern); + } + if (node.initializer) { + // Error on equals token which immediate precedes the initializer + return grammarErrorAtPos(node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); + } + } if (node.parserContextFlags & ParserContextFlags.StrictMode && isEvalOrArgumentsIdentifier(node.name)) { // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code // and its Identifier is eval or arguments From e26c3a68fc14393518fd488bb28503ec33636071 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Dec 2014 07:38:08 -0800 Subject: [PATCH 05/13] Correct type for rest element in destructuring declaration --- src/compiler/checker.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4b8437f4f1c..5e076ebe203 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1717,19 +1717,26 @@ module ts { error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name)); return unknownType; } - return type; } - // For an array binding element the specified or inferred type of the parent must be assignable to any[] - if (!isTypeAssignableTo(parentType, anyArrayType)) { - error(pattern, Diagnostics.Type_0_is_not_an_array_type, typeToString(parentType)); - return unknownType; - } - // Use specific property type when parent is a tuple or numeric index type when parent is an array - var propName = "" + indexOf(pattern.elements, declaration); - var type = isTupleLikeType(parentType) ? getTypeOfPropertyOfType(parentType, propName) : getIndexTypeOfType(parentType, IndexKind.Number); - if (!type) { - error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), propName); - return unknownType; + else { + // For an array binding element the specified or inferred type of the parent must be assignable to any[] + if (!isTypeAssignableTo(parentType, anyArrayType)) { + error(pattern, Diagnostics.Type_0_is_not_an_array_type, typeToString(parentType)); + return unknownType; + } + if (!declaration.dotDotDotToken) { + // Use specific property type when parent is a tuple or numeric index type when parent is an array + var propName = "" + indexOf(pattern.elements, declaration); + var type = isTupleLikeType(parentType) ? getTypeOfPropertyOfType(parentType, propName) : getIndexTypeOfType(parentType, IndexKind.Number); + if (!type) { + error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), propName); + return unknownType; + } + } + else { + // Rest element has an array type with the same element type as the parent type + var type = createArrayType(getIndexTypeOfType(parentType, IndexKind.Number)); + } } return type; } From a1533b695b9ff00ecfc4c9df0d76dba623ff6561 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Dec 2014 10:55:21 -0800 Subject: [PATCH 06/13] Implied type of binding pattern with rest element is always an array type --- src/compiler/checker.ts | 51 ++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e076ebe203..8c2bac87085 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1794,17 +1794,8 @@ module ts { return anyType; } - // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself - // and without regard to its context (i.e. without regard any type annotation or initializer associated with the - // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] - // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is - // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring - // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of - // the parameter. - function getTypeFromBindingPattern(pattern: BindingPattern): Type { - if (pattern.kind === SyntaxKind.ArrayBindingPattern) { - return createTupleType(map(pattern.elements, e => e.kind === SyntaxKind.OmittedExpression ? anyType : getTypeFromBindingElement(e))); - } + // Return the type implied by an object binding pattern + function getTypeFromObjectBindingPattern(pattern: BindingPattern): Type { var members: SymbolTable = {}; forEach(pattern.elements, e => { var flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0); @@ -1816,6 +1807,32 @@ module ts { return createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); } + // Return the type implied by an array binding pattern + function getTypeFromArrayBindingPattern(pattern: BindingPattern): Type { + var hasSpreadElement: boolean = false; + var elementTypes: Type[] = []; + forEach(pattern.elements, e => { + elementTypes.push(e.kind === SyntaxKind.OmittedExpression || e.dotDotDotToken ? anyType : getTypeFromBindingElement(e)); + if (e.dotDotDotToken) { + hasSpreadElement = true; + } + }); + return !elementTypes.length ? anyArrayType : hasSpreadElement ? createArrayType(getUnionType(elementTypes)) : createTupleType(elementTypes); + } + + // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself + // and without regard to its context (i.e. without regard any type annotation or initializer associated with the + // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] + // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is + // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring + // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of + // the parameter. + function getTypeFromBindingPattern(pattern: BindingPattern): Type { + return pattern.kind === SyntaxKind.ObjectBindingPattern + ? getTypeFromObjectBindingPattern(pattern) + : getTypeFromArrayBindingPattern(pattern); + } + // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it // is a bit more involved. For example: @@ -5336,21 +5353,23 @@ module ts { if (!elements.length) { return createArrayType(undefinedType); } - var contextualType = getContextualType(node); - var isTupleType = (contextualType && contextualTypeIsTupleLikeType(contextualType)) || isAssignmentTarget(node); + var hasSpreadElement: boolean = false; var elementTypes: Type[] = []; forEach(elements, e => { var type = checkExpression(e, contextualMapper); if (e.kind === SyntaxKind.SpreadElementExpression) { elementTypes.push(getIndexTypeOfType(type, IndexKind.Number) || anyType); - isTupleType = false; + hasSpreadElement = true; } else { elementTypes.push(type); } }); - if (isTupleType) { - return createTupleType(elementTypes); + if (!hasSpreadElement) { + var contextualType = getContextualType(node); + if (contextualType && contextualTypeIsTupleLikeType(contextualType) || isAssignmentTarget(node)) { + return createTupleType(elementTypes); + } } return createArrayType(getUnionType(elementTypes)); } From 86a9c80ff87a93a32ab1fe9b8b6610e2c6ada79a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Dec 2014 10:56:22 -0800 Subject: [PATCH 07/13] Adding tests and accepting new baselines --- .../baselines/reference/arrayLiteralSpread.js | 46 ++++++++++ .../reference/arrayLiteralSpread.types | 90 +++++++++++++++++++ .../declarationsAndAssignments.errors.txt | 34 ++++++- .../reference/declarationsAndAssignments.js | 68 +++++++++++++- .../restElementMustBeLast.errors.txt | 12 +++ .../declarationsAndAssignments.ts | 34 ++++++- .../destructuring/restElementMustBeLast.ts | 2 + .../es6/spread/arrayLiteralSpread.ts | 22 +++++ 8 files changed, 300 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/arrayLiteralSpread.js create mode 100644 tests/baselines/reference/arrayLiteralSpread.types create mode 100644 tests/baselines/reference/restElementMustBeLast.errors.txt create mode 100644 tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts create mode 100644 tests/cases/conformance/es6/spread/arrayLiteralSpread.ts diff --git a/tests/baselines/reference/arrayLiteralSpread.js b/tests/baselines/reference/arrayLiteralSpread.js new file mode 100644 index 00000000000..73a60714526 --- /dev/null +++ b/tests/baselines/reference/arrayLiteralSpread.js @@ -0,0 +1,46 @@ +//// [arrayLiteralSpread.ts] +function f0() { + var a = [1, 2, 3]; + var a1 = [...a]; + var a2 = [1, ...a]; + var a3 = [1, 2, ...a]; + var a4 = [...a, 1]; + var a5 = [...a, 1, 2]; + var a6 = [1, 2, ...a, 1, 2]; + var a7 = [1, ...a, 2, ...a]; + var a8 = [...a, ...a, ...a]; +} + +function f1() { + var a = [1, 2, 3]; + var b = ["hello", ...a, true]; + var b: (string | number | boolean)[]; +} + +function f2() { + var a = [...[...[...[...[...[]]]]]]; + var b = [...[...[...[...[...[5]]]]]]; +} + + +//// [arrayLiteralSpread.js] +function f0() { + var a = [1, 2, 3]; + var a1 = a; + var a2 = [1].concat(a); + var a3 = [1, 2].concat(a); + var a4 = a.concat([1]); + var a5 = a.concat([1, 2]); + var a6 = [1, 2].concat(a, [1, 2]); + var a7 = [1].concat(a, [2], a); + var a8 = a.concat(a, a); +} +function f1() { + var a = [1, 2, 3]; + var b = ["hello"].concat(a, [true]); + var b; +} +function f2() { + var a = []; + var b = [5]; +} diff --git a/tests/baselines/reference/arrayLiteralSpread.types b/tests/baselines/reference/arrayLiteralSpread.types new file mode 100644 index 00000000000..0dae59521bf --- /dev/null +++ b/tests/baselines/reference/arrayLiteralSpread.types @@ -0,0 +1,90 @@ +=== tests/cases/conformance/es6/spread/arrayLiteralSpread.ts === +function f0() { +>f0 : () => void + + var a = [1, 2, 3]; +>a : number[] +>[1, 2, 3] : number[] + + var a1 = [...a]; +>a1 : number[] +>[...a] : number[] +>a : number[] + + var a2 = [1, ...a]; +>a2 : number[] +>[1, ...a] : number[] +>a : number[] + + var a3 = [1, 2, ...a]; +>a3 : number[] +>[1, 2, ...a] : number[] +>a : number[] + + var a4 = [...a, 1]; +>a4 : number[] +>[...a, 1] : number[] +>a : number[] + + var a5 = [...a, 1, 2]; +>a5 : number[] +>[...a, 1, 2] : number[] +>a : number[] + + var a6 = [1, 2, ...a, 1, 2]; +>a6 : number[] +>[1, 2, ...a, 1, 2] : number[] +>a : number[] + + var a7 = [1, ...a, 2, ...a]; +>a7 : number[] +>[1, ...a, 2, ...a] : number[] +>a : number[] +>a : number[] + + var a8 = [...a, ...a, ...a]; +>a8 : number[] +>[...a, ...a, ...a] : number[] +>a : number[] +>a : number[] +>a : number[] +} + +function f1() { +>f1 : () => void + + var a = [1, 2, 3]; +>a : number[] +>[1, 2, 3] : number[] + + var b = ["hello", ...a, true]; +>b : (string | number | boolean)[] +>["hello", ...a, true] : (string | number | boolean)[] +>a : number[] + + var b: (string | number | boolean)[]; +>b : (string | number | boolean)[] +} + +function f2() { +>f2 : () => void + + var a = [...[...[...[...[...[]]]]]]; +>a : any[] +>[...[...[...[...[...[]]]]]] : undefined[] +>[...[...[...[...[]]]]] : undefined[] +>[...[...[...[]]]] : undefined[] +>[...[...[]]] : undefined[] +>[...[]] : undefined[] +>[] : undefined[] + + var b = [...[...[...[...[...[5]]]]]]; +>b : number[] +>[...[...[...[...[...[5]]]]]] : number[] +>[...[...[...[...[5]]]]] : number[] +>[...[...[...[5]]]] : number[] +>[...[...[5]]] : number[] +>[...[5]] : number[] +>[5] : number[] +} + diff --git a/tests/baselines/reference/declarationsAndAssignments.errors.txt b/tests/baselines/reference/declarationsAndAssignments.errors.txt index 2c9a54c869c..4eefc2fcc23 100644 --- a/tests/baselines/reference/declarationsAndAssignments.errors.txt +++ b/tests/baselines/reference/declarationsAndAssignments.errors.txt @@ -176,7 +176,7 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9): f17({ c: true }); f17(f15()); - function g4() { + function f18() { var a: number; var b: string; var aa: number[]; @@ -191,7 +191,7 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9): [a = 1, b = "abc"] = [2, "def"]; } - function g5() { + function f19() { var a, b; [a, b] = [1, 2]; [a, b] = [b, a]; @@ -199,4 +199,34 @@ tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts(138,9): [[a, b] = [1, 2]] = [[2, 3]]; var x = ([a, b] = [1, 2]); } + + function f20() { + var a: number[]; + var x: number; + var y: number; + var z: number; + var [...a] = [1, 2, 3]; + var [x, ...a] = [1, 2, 3]; + var [x, y, ...a] = [1, 2, 3]; + var [x, y, z, ...a] = [1, 2, 3]; + [...a] = [1, 2, 3]; + [x, ...a] = [1, 2, 3]; + [x, y, ...a] = [1, 2, 3]; + [x, y, z, ...a] = [1, 2, 3]; + } + + function f21() { + var a: (number | string | boolean)[]; + var x: number | string | boolean; + var y: number | string | boolean; + var z: number | string | boolean; + var [...a] = [1, "hello", true]; + var [x, ...a] = [1, "hello", true]; + var [x, y, ...a] = [1, "hello", true]; + var [x, y, z, ...a] = [1, "hello", true]; + [...a] = [1, "hello", true]; + [x, ...a] = [1, "hello", true]; + [x, y, ...a] = [1, "hello", true]; + [x, y, z, ...a] = [1, "hello", true]; + } \ No newline at end of file diff --git a/tests/baselines/reference/declarationsAndAssignments.js b/tests/baselines/reference/declarationsAndAssignments.js index 82272a55559..23e1fe773da 100644 --- a/tests/baselines/reference/declarationsAndAssignments.js +++ b/tests/baselines/reference/declarationsAndAssignments.js @@ -129,7 +129,7 @@ f17({ a: "hello" }); f17({ c: true }); f17(f15()); -function g4() { +function f18() { var a: number; var b: string; var aa: number[]; @@ -140,7 +140,7 @@ function g4() { [a = 1, b = "abc"] = [2, "def"]; } -function g5() { +function f19() { var a, b; [a, b] = [1, 2]; [a, b] = [b, a]; @@ -148,6 +148,36 @@ function g5() { [[a, b] = [1, 2]] = [[2, 3]]; var x = ([a, b] = [1, 2]); } + +function f20() { + var a: number[]; + var x: number; + var y: number; + var z: number; + var [...a] = [1, 2, 3]; + var [x, ...a] = [1, 2, 3]; + var [x, y, ...a] = [1, 2, 3]; + var [x, y, z, ...a] = [1, 2, 3]; + [...a] = [1, 2, 3]; + [x, ...a] = [1, 2, 3]; + [x, y, ...a] = [1, 2, 3]; + [x, y, z, ...a] = [1, 2, 3]; +} + +function f21() { + var a: (number | string | boolean)[]; + var x: number | string | boolean; + var y: number | string | boolean; + var z: number | string | boolean; + var [...a] = [1, "hello", true]; + var [x, ...a] = [1, "hello", true]; + var [x, y, ...a] = [1, "hello", true]; + var [x, y, z, ...a] = [1, "hello", true]; + [...a] = [1, "hello", true]; + [x, ...a] = [1, "hello", true]; + [x, y, ...a] = [1, "hello", true]; + [x, y, z, ...a] = [1, "hello", true]; +} //// [declarationsAndAssignments.js] @@ -266,7 +296,7 @@ f17({}); f17({ a: "hello" }); f17({ c: true }); f17(f15()); -function g4() { +function f18() { var a; var b; var aa; @@ -277,7 +307,7 @@ function g4() { _e = [2, "def"], _f = _e[0], a = _f === void0 ? 1 : _f, _g = _e[1], b = _g === void0 ? "abc" : _g; var _a, _b, _c, _d, _e, _f, _g; } -function g5() { +function f19() { var a, b; _a = [1, 2], a = _a[0], b = _a[1]; _b = [b, a], a = _b[0], b = _b[1]; @@ -286,3 +316,33 @@ function g5() { var x = (_f = [1, 2], a = _f[0], b = _f[1], _f); var _a, _b, _c, _d, _e, _f; } +function f20() { + var a; + var x; + var y; + var z; + var _a = [1, 2, 3], a = _a.slice(0); + var _b = [1, 2, 3], x = _b[0], a = _b.slice(1); + var _c = [1, 2, 3], x = _c[0], y = _c[1], a = _c.slice(2); + var _d = [1, 2, 3], x = _d[0], y = _d[1], z = _d[2], a = _d.slice(3); + _e = [1, 2, 3], a = _e.slice(0); + _f = [1, 2, 3], x = _f[0], a = _f.slice(1); + _g = [1, 2, 3], x = _g[0], y = _g[1], a = _g.slice(2); + _h = [1, 2, 3], x = _h[0], y = _h[1], z = _h[2], a = _h.slice(3); + var _e, _f, _g, _h; +} +function f21() { + var a; + var x; + var y; + var z; + var _a = [1, "hello", true], a = _a.slice(0); + var _b = [1, "hello", true], x = _b[0], a = _b.slice(1); + var _c = [1, "hello", true], x = _c[0], y = _c[1], a = _c.slice(2); + var _d = [1, "hello", true], x = _d[0], y = _d[1], z = _d[2], a = _d.slice(3); + _e = [1, "hello", true], a = _e.slice(0); + _f = [1, "hello", true], x = _f[0], a = _f.slice(1); + _g = [1, "hello", true], x = _g[0], y = _g[1], a = _g.slice(2); + _h = [1, "hello", true], x = _h[0], y = _h[1], z = _h[2], a = _h.slice(3); + var _e, _f, _g, _h; +} diff --git a/tests/baselines/reference/restElementMustBeLast.errors.txt b/tests/baselines/reference/restElementMustBeLast.errors.txt new file mode 100644 index 00000000000..269d984bc00 --- /dev/null +++ b/tests/baselines/reference/restElementMustBeLast.errors.txt @@ -0,0 +1,12 @@ +tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts(1,9): error TS2462: A rest element must be last in an array destructuring pattern +tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts(2,2): error TS2462: A rest element must be last in an array destructuring pattern + + +==== tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts (2 errors) ==== + var [...a, x] = [1, 2, 3]; // Error, rest must be last element + ~ +!!! error TS2462: A rest element must be last in an array destructuring pattern + [...a, x] = [1, 2, 3]; // Error, rest must be last element + ~~~~ +!!! error TS2462: A rest element must be last in an array destructuring pattern + \ No newline at end of file diff --git a/tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts b/tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts index 04cdd153a57..2202a482f8b 100644 --- a/tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts +++ b/tests/cases/conformance/es6/destructuring/declarationsAndAssignments.ts @@ -128,7 +128,7 @@ f17({ a: "hello" }); f17({ c: true }); f17(f15()); -function g4() { +function f18() { var a: number; var b: string; var aa: number[]; @@ -139,7 +139,7 @@ function g4() { [a = 1, b = "abc"] = [2, "def"]; } -function g5() { +function f19() { var a, b; [a, b] = [1, 2]; [a, b] = [b, a]; @@ -147,3 +147,33 @@ function g5() { [[a, b] = [1, 2]] = [[2, 3]]; var x = ([a, b] = [1, 2]); } + +function f20() { + var a: number[]; + var x: number; + var y: number; + var z: number; + var [...a] = [1, 2, 3]; + var [x, ...a] = [1, 2, 3]; + var [x, y, ...a] = [1, 2, 3]; + var [x, y, z, ...a] = [1, 2, 3]; + [...a] = [1, 2, 3]; + [x, ...a] = [1, 2, 3]; + [x, y, ...a] = [1, 2, 3]; + [x, y, z, ...a] = [1, 2, 3]; +} + +function f21() { + var a: (number | string | boolean)[]; + var x: number | string | boolean; + var y: number | string | boolean; + var z: number | string | boolean; + var [...a] = [1, "hello", true]; + var [x, ...a] = [1, "hello", true]; + var [x, y, ...a] = [1, "hello", true]; + var [x, y, z, ...a] = [1, "hello", true]; + [...a] = [1, "hello", true]; + [x, ...a] = [1, "hello", true]; + [x, y, ...a] = [1, "hello", true]; + [x, y, z, ...a] = [1, "hello", true]; +} diff --git a/tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts b/tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts new file mode 100644 index 00000000000..6e7b91033a1 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/restElementMustBeLast.ts @@ -0,0 +1,2 @@ +var [...a, x] = [1, 2, 3]; // Error, rest must be last element +[...a, x] = [1, 2, 3]; // Error, rest must be last element diff --git a/tests/cases/conformance/es6/spread/arrayLiteralSpread.ts b/tests/cases/conformance/es6/spread/arrayLiteralSpread.ts new file mode 100644 index 00000000000..da088cc09a0 --- /dev/null +++ b/tests/cases/conformance/es6/spread/arrayLiteralSpread.ts @@ -0,0 +1,22 @@ +function f0() { + var a = [1, 2, 3]; + var a1 = [...a]; + var a2 = [1, ...a]; + var a3 = [1, 2, ...a]; + var a4 = [...a, 1]; + var a5 = [...a, 1, 2]; + var a6 = [1, 2, ...a, 1, 2]; + var a7 = [1, ...a, 2, ...a]; + var a8 = [...a, ...a, ...a]; +} + +function f1() { + var a = [1, 2, 3]; + var b = ["hello", ...a, true]; + var b: (string | number | boolean)[]; +} + +function f2() { + var a = [...[...[...[...[...[]]]]]]; + var b = [...[...[...[...[...[5]]]]]]; +} From 3e436c5047f30c8c3cf731bc4cd0326bfdc91edd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Dec 2014 14:34:12 -0800 Subject: [PATCH 08/13] Adding new tests --- .../reference/restElementWithInitializer1.errors.txt | 9 +++++++++ .../reference/restElementWithInitializer2.errors.txt | 10 ++++++++++ .../baselines/reference/restElementWithInitializer2.js | 10 ++++++++++ .../es6/destructuring/restElementWithInitializer1.ts | 2 ++ .../es6/destructuring/restElementWithInitializer2.ts | 3 +++ 5 files changed, 34 insertions(+) create mode 100644 tests/baselines/reference/restElementWithInitializer1.errors.txt create mode 100644 tests/baselines/reference/restElementWithInitializer2.errors.txt create mode 100644 tests/baselines/reference/restElementWithInitializer2.js create mode 100644 tests/cases/conformance/es6/destructuring/restElementWithInitializer1.ts create mode 100644 tests/cases/conformance/es6/destructuring/restElementWithInitializer2.ts diff --git a/tests/baselines/reference/restElementWithInitializer1.errors.txt b/tests/baselines/reference/restElementWithInitializer1.errors.txt new file mode 100644 index 00000000000..6cee9d5286a --- /dev/null +++ b/tests/baselines/reference/restElementWithInitializer1.errors.txt @@ -0,0 +1,9 @@ +tests/cases/conformance/es6/destructuring/restElementWithInitializer1.ts(2,11): error TS1185: A rest element cannot have an initializer. + + +==== tests/cases/conformance/es6/destructuring/restElementWithInitializer1.ts (1 errors) ==== + var a: number[]; + var [...x = a] = a; // Error, rest element cannot have initializer + ~ +!!! error TS1185: A rest element cannot have an initializer. + \ No newline at end of file diff --git a/tests/baselines/reference/restElementWithInitializer2.errors.txt b/tests/baselines/reference/restElementWithInitializer2.errors.txt new file mode 100644 index 00000000000..9c4e95e3963 --- /dev/null +++ b/tests/baselines/reference/restElementWithInitializer2.errors.txt @@ -0,0 +1,10 @@ +tests/cases/conformance/es6/destructuring/restElementWithInitializer2.ts(3,5): error TS2364: Invalid left-hand side of assignment expression. + + +==== tests/cases/conformance/es6/destructuring/restElementWithInitializer2.ts (1 errors) ==== + var a: number[]; + var x: number[]; + [...x = a] = a; // Error, rest element cannot have initializer + ~~~~~ +!!! error TS2364: Invalid left-hand side of assignment expression. + \ No newline at end of file diff --git a/tests/baselines/reference/restElementWithInitializer2.js b/tests/baselines/reference/restElementWithInitializer2.js new file mode 100644 index 00000000000..8874f6a83d0 --- /dev/null +++ b/tests/baselines/reference/restElementWithInitializer2.js @@ -0,0 +1,10 @@ +//// [restElementWithInitializer2.ts] +var a: number[]; +var x: number[]; +[...x = a] = a; // Error, rest element cannot have initializer + + +//// [restElementWithInitializer2.js] +var a; +var x; +x = a = a.slice(0); // Error, rest element cannot have initializer diff --git a/tests/cases/conformance/es6/destructuring/restElementWithInitializer1.ts b/tests/cases/conformance/es6/destructuring/restElementWithInitializer1.ts new file mode 100644 index 00000000000..7357e36cc05 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/restElementWithInitializer1.ts @@ -0,0 +1,2 @@ +var a: number[]; +var [...x = a] = a; // Error, rest element cannot have initializer diff --git a/tests/cases/conformance/es6/destructuring/restElementWithInitializer2.ts b/tests/cases/conformance/es6/destructuring/restElementWithInitializer2.ts new file mode 100644 index 00000000000..dc41aacf0b9 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/restElementWithInitializer2.ts @@ -0,0 +1,3 @@ +var a: number[]; +var x: number[]; +[...x = a] = a; // Error, rest element cannot have initializer From b878a271139e30621964e1f91a39776a19416695 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 14 Dec 2014 09:43:14 -0800 Subject: [PATCH 09/13] Support -target ES6 emit for destructuring, spread, and rest. --- src/compiler/emitter.ts | 115 +++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 42 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index dcf9d4f3df2..482cba29ff3 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2261,6 +2261,23 @@ module ts { write("]"); } + function emitBindingElement(node: BindingElement) { + if (node.propertyName) { + emit(node.propertyName); + write(": "); + } + if (node.dotDotDotToken) { + write("..."); + } + if (isBindingPattern(node.name)) { + emit(node.name); + } + else { + emitModuleMemberName(node); + } + emitOptional(" = ", node.initializer); + } + function emitSpreadElementExpression(node: SpreadElementExpression) { write("..."); emit((node).expression); @@ -2552,7 +2569,8 @@ module ts { function emitBinaryExpression(node: BinaryExpression) { - if (node.operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { + if (compilerOptions.target < ScriptTarget.ES6 && node.operator === SyntaxKind.EqualsToken && + (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { emitDestructuring(node); } else { @@ -3044,7 +3062,13 @@ module ts { function emitVariableDeclaration(node: VariableDeclaration) { emitLeadingComments(node); if (isBindingPattern(node.name)) { - emitDestructuring(node); + if (compilerOptions.target < ScriptTarget.ES6) { + emitDestructuring(node); + } + else { + emit(node.name); + emitOptional(" = ", node.initializer); + } } else { emitModuleMemberName(node); @@ -3073,57 +3097,61 @@ module ts { function emitParameter(node: ParameterDeclaration) { emitLeadingComments(node); - if (isBindingPattern(node.name)) { - var name = createTempVariable(node); - if (!tempParameters) { - tempParameters = []; + if (compilerOptions.target < ScriptTarget.ES6) { + if (isBindingPattern(node.name)) { + var name = createTempVariable(node); + if (!tempParameters) { + tempParameters = []; + } + tempParameters.push(name); + emit(name); + } + else { + emit(node.name); } - tempParameters.push(name); - emit(name); } else { + if (node.dotDotDotToken) { + write("..."); + } emit(node.name); + emitOptional(" = ", node.initializer); } - // TODO(andersh): Enable ES6 code generation below - //if (node.propertyName) { - // emit(node.propertyName); - // write(": "); - //} - //emit(node.name); - //emitOptional(" = ", node.initializer); emitTrailingComments(node); } function emitDefaultValueAssignments(node: FunctionLikeDeclaration) { - var tempIndex = 0; - forEach(node.parameters, p => { - if (isBindingPattern(p.name)) { - writeLine(); - write("var "); - emitDestructuring(p, tempParameters[tempIndex]); - write(";"); - tempIndex++; - } - else if (p.initializer) { - writeLine(); - emitStart(p); - write("if ("); - emitNode(p.name); - write(" === void 0)"); - emitEnd(p); - write(" { "); - emitStart(p); - emitNode(p.name); - write(" = "); - emitNode(p.initializer); - emitEnd(p); - write("; }"); - } - }); + if (compilerOptions.target < ScriptTarget.ES6) { + var tempIndex = 0; + forEach(node.parameters, p => { + if (isBindingPattern(p.name)) { + writeLine(); + write("var "); + emitDestructuring(p, tempParameters[tempIndex]); + write(";"); + tempIndex++; + } + else if (p.initializer) { + writeLine(); + emitStart(p); + write("if ("); + emitNode(p.name); + write(" === void 0)"); + emitEnd(p); + write(" { "); + emitStart(p); + emitNode(p.name); + write(" = "); + emitNode(p.initializer); + emitEnd(p); + write("; }"); + } + }); + } } function emitRestParameter(node: FunctionLikeDeclaration) { - if (hasRestParameters(node)) { + if (compilerOptions.target < ScriptTarget.ES6 && hasRestParameters(node)) { var restIndex = node.parameters.length - 1; var restParam = node.parameters[restIndex]; var tempName = createTempVariable(node, /*forLoopVariable*/ true).text; @@ -3202,7 +3230,8 @@ module ts { write("("); if (node) { var parameters = node.parameters; - emitList(parameters, 0, parameters.length - (hasRestParameters(node) ? 1 : 0), /*multiLine*/ false, /*trailingComma*/ false); + var omitCount = compilerOptions.target < ScriptTarget.ES6 && hasRestParameters(node) ? 1 : 0; + emitList(parameters, 0, parameters.length - omitCount, /*multiLine*/ false, /*trailingComma*/ false); } write(")"); decreaseIndent(); @@ -3930,6 +3959,8 @@ module ts { return emitObjectBindingPattern(node); case SyntaxKind.ArrayBindingPattern: return emitArrayBindingPattern(node); + case SyntaxKind.BindingElement: + return emitBindingElement(node); case SyntaxKind.ArrayLiteralExpression: return emitArrayLiteral(node); case SyntaxKind.ObjectLiteralExpression: From b9db74711744f74e0bcc4fbfda9a783806319aa1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 14 Dec 2014 09:47:26 -0800 Subject: [PATCH 10/13] Accepting new baselines. --- ...ggedTemplateStringsWithOverloadResolution1_ES6.js | 6 +----- ...ggedTemplateStringsWithOverloadResolution2_ES6.js | 12 ++---------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1_ES6.js b/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1_ES6.js index a2a96168fd8..bd121933b32 100644 --- a/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1_ES6.js +++ b/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution1_ES6.js @@ -23,11 +23,7 @@ var z = foo `${1}${2}${3}`; // any (with error) //// [taggedTemplateStringsWithOverloadResolution1_ES6.js] -function foo() { - var stuff = []; - for (var _i = 0; _i < arguments.length; _i++) { - stuff[_i - 0] = arguments[_i]; - } +function foo(...stuff) { return undefined; } var a = foo([]); // number diff --git a/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution2_ES6.js b/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution2_ES6.js index 44da93f8041..f208a63354d 100644 --- a/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution2_ES6.js +++ b/tests/baselines/reference/taggedTemplateStringsWithOverloadResolution2_ES6.js @@ -18,20 +18,12 @@ var c = foo2 `${1}`; // number var d = foo2([], 1); // number //// [taggedTemplateStringsWithOverloadResolution2_ES6.js] -function foo1() { - var stuff = []; - for (var _i = 0; _i < arguments.length; _i++) { - stuff[_i - 0] = arguments[_i]; - } +function foo1(...stuff) { return undefined; } var a = foo1 `${1}`; // string var b = foo1([], 1); // number -function foo2() { - var stuff = []; - for (var _i = 0; _i < arguments.length; _i++) { - stuff[_i - 0] = arguments[_i]; - } +function foo2(...stuff) { return undefined; } var c = foo2 `${1}`; // number From 667e82fba25c9ab0bb79e73ad6fc9cf57c77848d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Dec 2014 15:16:54 -0800 Subject: [PATCH 11/13] Addressing CR feedback + Run fourslash tests with ES5 emit --- src/compiler/emitter.ts | 6 +++++- src/harness/fourslash.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 482cba29ff3..20d9585fc00 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2313,6 +2313,7 @@ module ts { var pos = 0; var group = 0; while (pos < length) { + // Emit using the pattern .concat(, , ...) if (group === 1) { write(".concat("); } @@ -2326,7 +2327,10 @@ module ts { pos++; } else { - for (var i = pos; i < length && elements[i].kind !== SyntaxKind.SpreadElementExpression; i++); + var i = pos; + while (i < length && elements[i].kind !== SyntaxKind.SpreadElementExpression) { + i++; + } write("["); emitList(elements, pos, i - pos, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0, /*trailingComma*/ elements.hasTrailingComma); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index e253f7b575b..b981ac1d624 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -146,7 +146,7 @@ module FourSlash { testOptMetadataNames.outDir, testOptMetadataNames.sourceMap, testOptMetadataNames.sourceRoot] function convertGlobalOptionsToCompilerOptions(globalOptions: { [idx: string]: string }): ts.CompilerOptions { - var settings: ts.CompilerOptions = {}; + var settings: ts.CompilerOptions = { target: ts.ScriptTarget.ES5 }; // Convert all property in globalOptions into ts.CompilationSettings for (var prop in globalOptions) { if (globalOptions.hasOwnProperty(prop)) { From fed3b5c0de0418b7f0cd02c292ebe8e3d8836f03 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 16 Dec 2014 15:47:22 -0800 Subject: [PATCH 12/13] Adding comment --- src/compiler/emitter.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 20d9585fc00..bb3de86bc7a 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2291,6 +2291,8 @@ module ts { case SyntaxKind.ElementAccessExpression: case SyntaxKind.CallExpression: case SyntaxKind.ParenthesizedExpression: + // This list is not exhaustive and only includes those cases that are relevant + // to the check in emitArrayLiteral. More cases can be added as needed. return false; } return true; From a667959b1fcc0f22c3695fb71e9abf7da31e948b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Dec 2014 11:50:03 -0800 Subject: [PATCH 13/13] Accepting new baselines --- tests/baselines/reference/restElementMustBeLast.js | 9 +++++++++ tests/baselines/reference/restElementWithInitializer1.js | 8 ++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/baselines/reference/restElementMustBeLast.js create mode 100644 tests/baselines/reference/restElementWithInitializer1.js diff --git a/tests/baselines/reference/restElementMustBeLast.js b/tests/baselines/reference/restElementMustBeLast.js new file mode 100644 index 00000000000..337cb6de6f6 --- /dev/null +++ b/tests/baselines/reference/restElementMustBeLast.js @@ -0,0 +1,9 @@ +//// [restElementMustBeLast.ts] +var [...a, x] = [1, 2, 3]; // Error, rest must be last element +[...a, x] = [1, 2, 3]; // Error, rest must be last element + + +//// [restElementMustBeLast.js] +var _a = [1, 2, 3], x = _a[1]; // Error, rest must be last element +_b = [1, 2, 3], x = _b[1]; // Error, rest must be last element +var _b; diff --git a/tests/baselines/reference/restElementWithInitializer1.js b/tests/baselines/reference/restElementWithInitializer1.js new file mode 100644 index 00000000000..26df58e3cf3 --- /dev/null +++ b/tests/baselines/reference/restElementWithInitializer1.js @@ -0,0 +1,8 @@ +//// [restElementWithInitializer1.ts] +var a: number[]; +var [...x = a] = a; // Error, rest element cannot have initializer + + +//// [restElementWithInitializer1.js] +var a; +var x = a.slice(0); // Error, rest element cannot have initializer