Skip to content

Commit 8ca1a24

Browse files
committed
Adds spread syntax in new expressions in ES5
1 parent 5320faa commit 8ca1a24

File tree

5 files changed

+116
-32
lines changed

5 files changed

+116
-32
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6585,7 +6585,7 @@ module ts {
65856585
result.splice(spliceIndex, 0, signature);
65866586
}
65876587
}
6588-
6588+
65896589
function getSpreadArgumentIndex(args: Expression[]): number {
65906590
for (let i = 0; i < args.length; i++) {
65916591
if (args[i].kind === SyntaxKind.SpreadElementExpression) {
@@ -7107,10 +7107,10 @@ module ts {
71077107
}
71087108

71097109
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature {
7110-
if (node.arguments && languageVersion < ScriptTarget.ES6) {
7110+
if (node.arguments && languageVersion < ScriptTarget.ES5) {
71117111
let spreadIndex = getSpreadArgumentIndex(node.arguments);
71127112
if (spreadIndex >= 0) {
7113-
error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher);
7113+
error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher);
71147114
}
71157115
}
71167116

@@ -7128,7 +7128,7 @@ module ts {
71287128
// If ConstructExpr's apparent type(section 3.8.1) is an object type with one or
71297129
// more construct signatures, the expression is processed in the same manner as a
71307130
// function call, but using the construct signatures as the initial set of candidate
7131-
// signatures for overload resolution.The result type of the function call becomes
7131+
// signatures for overload resolution. The result type of the function call becomes
71327132
// the result type of the operation.
71337133
expressionType = getApparentType(expressionType);
71347134
if (expressionType === unknownType) {

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ module ts {
334334
The_0_operator_cannot_be_applied_to_type_symbol: { code: 2469, category: DiagnosticCategory.Error, key: "The '{0}' operator cannot be applied to type 'symbol'." },
335335
Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object: { code: 2470, category: DiagnosticCategory.Error, key: "'Symbol' reference does not refer to the global Symbol constructor object." },
336336
A_computed_property_name_of_the_form_0_must_be_of_type_symbol: { code: 2471, category: DiagnosticCategory.Error, key: "A computed property name of the form '{0}' must be of type 'symbol'." },
337-
Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher: { code: 2472, category: DiagnosticCategory.Error, key: "Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher." },
337+
Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher: { code: 2472, category: DiagnosticCategory.Error, key: "Spread operator in 'new' expressions is only available when targeting ECMAScript 5 and higher." },
338338
Enum_declarations_must_all_be_const_or_non_const: { code: 2473, category: DiagnosticCategory.Error, key: "Enum declarations must all be const or non-const." },
339339
In_const_enum_declarations_member_initializer_must_be_constant_expression: { code: 2474, category: DiagnosticCategory.Error, key: "In 'const' enum declarations member initializer must be constant expression." },
340340
const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment: { code: 2475, category: DiagnosticCategory.Error, key: "'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment." },

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,7 +1324,7 @@
13241324
"category": "Error",
13251325
"code": 2471
13261326
},
1327-
"Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher.": {
1327+
"Spread operator in 'new' expressions is only available when targeting ECMAScript 5 and higher.": {
13281328
"category": "Error",
13291329
"code": 2472
13301330
},

src/compiler/emitter.ts

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,28 +1809,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
18091809
}
18101810

18111811
function emitCallWithSpread(node: CallExpression) {
1812-
let target: Expression;
1813-
let expr = skipParentheses(node.expression);
1814-
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
1815-
// Target will be emitted as "this" argument
1816-
target = emitCallTarget((<PropertyAccessExpression>expr).expression);
1817-
write(".");
1818-
emit((<PropertyAccessExpression>expr).name);
1819-
}
1820-
else if (expr.kind === SyntaxKind.ElementAccessExpression) {
1821-
// Target will be emitted as "this" argument
1822-
target = emitCallTarget((<PropertyAccessExpression>expr).expression);
1823-
write("[");
1824-
emit((<ElementAccessExpression>expr).argumentExpression);
1825-
write("]");
1826-
}
1827-
else if (expr.kind === SyntaxKind.SuperKeyword) {
1828-
target = expr;
1829-
write("_super");
1830-
}
1831-
else {
1832-
emit(node.expression);
1833-
}
1812+
let { target } = emitCallTargetAndAccessor(node.expression);
18341813
write(".apply(");
18351814
if (target) {
18361815
if (target.kind === SyntaxKind.SuperKeyword) {
@@ -1881,13 +1860,109 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
18811860
}
18821861
}
18831862

1863+
/** Emit call target and accessor from an expression. The call target represents
1864+
* the default call context of "this". If it can be decided — this function will
1865+
* return the node representhing the call target — otherwise it returns undefined.
1866+
* The accessor is a property accessor of the target. If an expression has an
1867+
* accessor this function will both emit it and return it. To support spread syntax
1868+
* in TypeScript, we sometimes need to emit the target twice in an expression that
1869+
* only expresses it once. The target could be a call expression and therefore calling
1870+
* it twice would alter its' state differently as oppose to calling it once. This
1871+
* function is very handy for those situations because it is emitting and returning
1872+
* the call target and the accessor. */
1873+
function emitCallTargetAndAccessor(node: Expression): CallTargetAndAccessor {
1874+
node = skipParentheses(node);
1875+
let target: Expression;
1876+
let accessor: Expression;
1877+
if (node.kind === SyntaxKind.PropertyAccessExpression) {
1878+
// Target will be emitted as "this" argument
1879+
target = emitCallTarget((<PropertyAccessExpression>node).expression);
1880+
accessor = (<PropertyAccessExpression>node).name;
1881+
write(".");
1882+
emit(accessor);
1883+
}
1884+
else if (node.kind === SyntaxKind.ElementAccessExpression) {
1885+
// Target will be emitted as "this" argument
1886+
target = emitCallTarget((<PropertyAccessExpression>node).expression);
1887+
accessor = (<ElementAccessExpression>node).argumentExpression;
1888+
write("[");
1889+
emit(accessor);
1890+
write("]");
1891+
}
1892+
else if (node.kind === SyntaxKind.SuperKeyword) {
1893+
target = node;
1894+
write("_super");
1895+
}
1896+
else {
1897+
emit(node);
1898+
}
1899+
return { target, accessor };
1900+
}
1901+
18841902
function emitNewExpression(node: NewExpression) {
18851903
write("new ");
1886-
emit(node.expression);
1887-
if (node.arguments) {
1904+
1905+
// Spread operator logic can be supported in new expressions in ES5 using a combination
1906+
// of Function.prototype.bind() and Function.prototype.apply().
1907+
//
1908+
// Example 1, with a non property accessor constructor:
1909+
//
1910+
// var arguments = [1, 2, 3, 4, 5];
1911+
// new Array(...arguments);
1912+
//
1913+
// Could be transpiled into ES5:
1914+
//
1915+
// var arguments = [1, 2, 3, 4, 5];
1916+
// new (Array.bind.apply(Array, [void 0].concat(arguments)));
1917+
//
1918+
// Example 2, with a property accessor constructor:
1919+
//
1920+
// var arguments = [1, 2, 3, 4, 5];
1921+
// new object.Array(...arguments);
1922+
//
1923+
// Could be transpiled into ES5:
1924+
//
1925+
// var arguments = [1, 2, 3, 4, 5];
1926+
// new ((_a = object.Array).bind.apply(_a, [void 0].concat(arguments)));
1927+
// var _a;
1928+
//
1929+
// `[void 0]` is the first argument which represents `thisArg` to the bind method above.
1930+
// And `thisArg` will be set to the return value of the constructor when instantiated
1931+
// with the new operator — regardless of any value we set `thisArg` to. Thus, we set it
1932+
// to an empty object, `void 0`.
1933+
if (languageVersion === ScriptTarget.ES5 &&
1934+
node.arguments &&
1935+
hasSpreadElement(node.arguments)) {
1936+
18881937
write("(");
1889-
emitCommaList(node.arguments);
1890-
write(")");
1938+
let { target, accessor } = emitCallTargetAndAccessor(node.expression);
1939+
write(".bind.apply(");
1940+
if (target) {
1941+
emit(target);
1942+
if (accessor.kind === SyntaxKind.Identifier) {
1943+
write(".");
1944+
emit(accessor);
1945+
}
1946+
else {
1947+
write("[");
1948+
emit(accessor);
1949+
write("]");
1950+
}
1951+
}
1952+
else {
1953+
emit(node.expression);
1954+
}
1955+
write(", [void 0].concat(");
1956+
emitListWithSpread(node.arguments, /*multiline*/false, /*trailingComma*/false);
1957+
write(")))");
1958+
}
1959+
else {
1960+
emit(node.expression);
1961+
if (node.arguments) {
1962+
write("(");
1963+
emitCommaList(node.arguments);
1964+
write(")");
1965+
}
18911966
}
18921967
}
18931968

src/compiler/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,15 @@ module ts {
394394
originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later
395395
}
396396

397+
/** Target and accessor from a function like call. Target is the object that represent the default call context
398+
* "this". The accessor is a property member of the target used in a call expression. This interface is only
399+
* used in the emitter for emitting expressions with spread syntax.*/
400+
/* @internal */
401+
export interface CallTargetAndAccessor {
402+
target?: Expression;
403+
accessor?: Expression;
404+
}
405+
397406
export interface QualifiedName extends Node {
398407
// Must have same layout as PropertyAccess
399408
left: EntityName;

0 commit comments

Comments
 (0)