Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add __spreadArrays helper #31166

Merged
merged 2 commits into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18671,8 +18671,8 @@ namespace ts {
}

function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type {
if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.SpreadIncludes);
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays);
}

const arrayOrIterableType = checkExpression(node.expression, checkMode);
Expand Down Expand Up @@ -30611,6 +30611,7 @@ namespace ts {
case ExternalEmitHelpers.Values: return "__values";
case ExternalEmitHelpers.Read: return "__read";
case ExternalEmitHelpers.Spread: return "__spread";
case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays";
case ExternalEmitHelpers.Await: return "__await";
case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator";
case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator";
Expand Down
26 changes: 26 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2657,6 +2657,7 @@ namespace ts {
valuesHelper,
readHelper,
spreadHelper,
spreadArraysHelper,
restHelper,
decorateHelper,
metadataHelper,
Expand Down Expand Up @@ -3693,6 +3694,31 @@ namespace ts {
);
}

export const spreadArraysHelper: UnscopedEmitHelper = {
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
name: "typescript:spreadArrays",
scoped: false,
text: `
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};`
};

export function createSpreadArraysHelper(context: TransformationContext, argumentList: ReadonlyArray<Expression>, location?: TextRange) {
context.requestEmitHelper(spreadArraysHelper);
return setTextRange(
createCall(
getHelperName("__spreadArrays"),
/*typeArguments*/ undefined,
argumentList
),
location
);
}

// Utilities

export function createForOfBindingStatement(node: ForInitializer, boundValue: Expression): Statement {
Expand Down
38 changes: 27 additions & 11 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3813,8 +3813,11 @@ namespace ts {
// [source]
// [a, ...b, c]
//
// [output (downlevelIteration)]
// __spread([a], b, [c])
//
// [output]
// [a].concat(b, [c])
// __spreadArrays([a], b, [c])

// Map spans of spread expressions into their expressions and spans of other
// expressions into an array literal.
Expand All @@ -3828,10 +3831,7 @@ namespace ts {
if (compilerOptions.downlevelIteration) {
if (segments.length === 1) {
const firstSegment = segments[0];
if (isCallExpression(firstSegment)
&& isIdentifier(firstSegment.expression)
&& (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName)
&& firstSegment.expression.escapedText === "___spread") {
if (isCallToHelper(firstSegment, "___spread" as __String)) {
return segments[0];
}
}
Expand All @@ -3840,17 +3840,33 @@ namespace ts {
}
else {
if (segments.length === 1) {
const firstElement = elements[0];
return needsUniqueCopy && isSpreadElement(firstElement) && firstElement.expression.kind !== SyntaxKind.ArrayLiteralExpression
? createArraySlice(segments[0])
: segments[0];
const firstSegment = segments[0];
if (!needsUniqueCopy
|| isPackedArrayLiteral(firstSegment)
|| isCallToHelper(firstSegment, "___spreadArrays" as __String)) {
return segments[0];
}
}

// Rewrite using the pattern <segment0>.concat(<segment1>, <segment2>, ...)
return createArrayConcat(segments.shift()!, segments);
return createSpreadArraysHelper(context, segments);
}
}

function isPackedElement(node: Expression) {
return !isOmittedExpression(node);
Copy link
Member

@weswigham weswigham Jun 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const innerSpreadEmpty = [1, 2, ...[...[,,,]]]

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works just fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It basically catches [...[1, 2]] and turns it into [1, 2] rather than calling the helper.

}

function isPackedArrayLiteral(node: Expression) {
return isArrayLiteralExpression(node) && every(node.elements, isPackedElement);
}

function isCallToHelper(firstSegment: Expression, helperName: __String) {
return isCallExpression(firstSegment)
&& isIdentifier(firstSegment.expression)
&& (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName)
&& firstSegment.expression.escapedText === helperName;
}

function partitionSpread(node: Expression) {
return isSpreadElement(node)
? visitSpanOfSpreads
Expand Down
13 changes: 7 additions & 6 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5307,12 +5307,13 @@ namespace ts {
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations)
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation)
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations)
Await = 1 << 11, // __await (used by ES2017 async generator transformation)
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation)
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation)
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation)
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation)
MakeTemplateObject = 1 << 16, // __makeTemplateObject (used for constructing template string array objects)
SpreadArrays = 1 << 11, // __spreadArrays (used by ES2015 array spread and argument list spread transformations)
Await = 1 << 12, // __await (used by ES2017 async generator transformation)
AsyncGenerator = 1 << 13, // __asyncGenerator (used by ES2017 async generator transformation)
AsyncDelegator = 1 << 14, // __asyncDelegator (used by ES2017 async generator yield* transformation)
AsyncValues = 1 << 15, // __asyncValues (used by ES2017 for..await..of transformation)
ExportStar = 1 << 16, // __exportStar (used by CommonJS/AMD/UMD module transformation)
MakeTemplateObject = 1 << 17, // __makeTemplateObject (used for constructing template string array objects)
FirstEmitHelper = Extends,
LastEmitHelper = MakeTemplateObject,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ baz(["string", 1, true, ...array]); // Error
foo(o); // Error because x has an array type namely (string|number)[]

//// [argumentExpressionContextualTyping.js]
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
// In a typed function call, argument expressions are contextually typed by their corresponding parameter types.
function foo(_a) {
var _b = _a.x, a = _b[0], b = _b[1], _c = _a.y, c = _c.c, d = _c.d, e = _c.e;
Expand All @@ -36,5 +43,5 @@ var tuple = ["string", 1, true];
baz(tuple);
baz(["string", 1, true]);
baz(array); // Error
baz(["string", 1, true].concat(array)); // Error
baz(__spreadArrays(["string", 1, true], array)); // Error
foo(o); // Error because x has an array type namely (string|number)[]
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ var spr2:[number, number, number] = [1, 2, 3, ...tup]; // Error


//// [arrayLiteralExpressionContextualTyping.js]
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
// In a contextually typed array literal expression containing no spread elements, an element expression at index N is contextually typed by
// the type of the property with the numeric name N in the contextual type, if any, or otherwise
// the numeric index type of the contextual type, if any.
Expand All @@ -26,6 +33,6 @@ var tup1 = [1, 2, 3, "string"];
var tup2 = [1, 2, 3, "string"]; // Error
// In a contextually typed array literal expression containing one or more spread elements,
// an element expression at index N is contextually typed by the numeric index type of the contextual type, if any.
var spr = [1, 2, 3].concat(array);
var spr1 = [1, 2, 3].concat(tup);
var spr2 = [1, 2, 3].concat(tup); // Error
var spr = __spreadArrays([1, 2, 3], array);
var spr1 = __spreadArrays([1, 2, 3], tup);
var spr2 = __spreadArrays([1, 2, 3], tup); // Error
25 changes: 16 additions & 9 deletions tests/baselines/reference/arrayLiteralSpread.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,27 @@ function f2() {


//// [arrayLiteralSpread.js]
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
function f0() {
var a = [1, 2, 3];
var a1 = a.slice();
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);
var a1 = __spreadArrays(a);
var a2 = __spreadArrays([1], a);
var a3 = __spreadArrays([1, 2], a);
var a4 = __spreadArrays(a, [1]);
var a5 = __spreadArrays(a, [1, 2]);
var a6 = __spreadArrays([1, 2], a, [1, 2]);
var a7 = __spreadArrays([1], a, [2], a);
var a8 = __spreadArrays(a, a, a);
}
function f1() {
var a = [1, 2, 3];
var b = ["hello"].concat(a, [true]);
var b = __spreadArrays(["hello"], a, [true]);
var b;
}
function f2() {
Expand Down
33 changes: 20 additions & 13 deletions tests/baselines/reference/arrayLiterals2ES5.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,21 @@ var d9 = [[...temp1], ...["hello"]];
// Elisionopt SpreadElement
// ElementList, Elisionopt AssignmentExpression
// ElementList, Elisionopt SpreadElement
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
// SpreadElement:
// ... AssignmentExpression
var a0 = [, , 2, 3, 4];
var a1 = ["hello", "world"];
var a2 = [, , ].concat(a0, ["hello"]);
var a3 = [, ].concat(a0);
var a2 = __spreadArrays([, , ], a0, ["hello"]);
var a3 = __spreadArrays([, ], a0);
var a4 = [function () { return 1; },];
var a5 = a0.concat([,]);
var a5 = __spreadArrays(a0, [,]);
// Each element expression in a non-empty array literal is processed as follows:
// - If the array literal contains no spread elements, and if the array literal is contextually typed (section 4.19)
// by a type T and T has a property with the numeric name N, where N is the index of the element expression in the array literal,
Expand All @@ -92,13 +99,13 @@ var temp1 = [1, 2, 3];
var temp2 = [[1, 2, 3], ["hello", "string"]];
var temp3 = [undefined, null, undefined];
var temp4 = [];
var d0 = [1, true].concat(temp); // has type (string|number|boolean)[]
var d1 = temp.slice(); // has type string[]
var d2 = temp1.slice();
var d3 = temp1.slice();
var d4 = temp.concat(temp1);
var d5 = temp3.slice();
var d6 = temp4.slice();
var d7 = temp1.slice();
var d8 = [temp1.slice()];
var d9 = [temp1.slice()].concat(["hello"]);
var d0 = __spreadArrays([1, true], temp); // has type (string|number|boolean)[]
var d1 = __spreadArrays(temp); // has type string[]
var d2 = __spreadArrays(temp1);
var d3 = __spreadArrays(temp1);
var d4 = __spreadArrays(temp, temp1);
var d5 = __spreadArrays(temp3);
var d6 = __spreadArrays(temp4);
var d7 = __spreadArrays(temp1);
var d8 = [__spreadArrays(temp1)];
var d9 = __spreadArrays([__spreadArrays(temp1)], ["hello"]);
13 changes: 10 additions & 3 deletions tests/baselines/reference/arrayLiterals3.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ var c2: myArray = [...temp1, ...temp]; // Error cannot assign (number
// - If the array literal contains no spread elements, and if the array literal is contextually typed (section 4.19)
// by a type T and T has a property with the numeric name N, where N is the index of the element expression in the array literal,
// the element expression is contextually typed by the type of that property.
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
// The resulting type an array literal expression is determined as follows:
// - If the array literal contains no spread elements and is contextually typed by a tuple-like type,
// the resulting type is a tuple type constructed from the types of the element expressions.
Expand All @@ -55,6 +62,6 @@ var _a = [1, 2, "string", true], b1 = _a[0], b2 = _a[1];
var temp = ["s", "t", "r"];
var temp1 = [1, 2, 3];
var temp2 = [[1, 2, 3], ["hello", "string"]];
var c0 = temp2.slice(); // Error
var c1 = temp1.slice(); // Error cannot assign number[] to [number, number, number]
var c2 = temp1.concat(temp); // Error cannot assign (number|string)[] to number[]
var c0 = __spreadArrays(temp2); // Error
var c1 = __spreadArrays(temp1); // Error cannot assign number[] to [number, number, number]
var c2 = __spreadArrays(temp1, temp); // Error cannot assign (number|string)[] to number[]
9 changes: 8 additions & 1 deletion tests/baselines/reference/callOverload.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ withRest();
withRest(...n);

//// [callOverload.js]
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var n;
fn(1); // no error
fn(1, 2, 3, 4);
takeTwo(1, 2, 3, 4);
withRest.apply(void 0, ['a'].concat(n)); // no error
withRest.apply(void 0, __spreadArrays(['a'], n)); // no error
withRest();
withRest.apply(void 0, n);
41 changes: 24 additions & 17 deletions tests/baselines/reference/callWithSpread.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ var __extends = (this && this.__extends) || (function () {
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var _a, _b, _c, _d, _e, _f, _g;
function foo(x, y) {
var z = [];
Expand All @@ -84,23 +91,23 @@ var z;
var obj;
var xa;
foo(1, 2, "abc");
foo.apply(void 0, [1, 2].concat(a));
foo.apply(void 0, [1, 2].concat(a, ["abc"]));
foo.apply(void 0, __spreadArrays([1, 2], a));
foo.apply(void 0, __spreadArrays([1, 2], a, ["abc"]));
obj.foo(1, 2, "abc");
obj.foo.apply(obj, [1, 2].concat(a));
obj.foo.apply(obj, [1, 2].concat(a, ["abc"]));
obj.foo.apply(obj, [1, 2].concat(a)).foo(1, 2, "abc");
(_a = obj.foo.apply(obj, [1, 2].concat(a))).foo.apply(_a, [1, 2].concat(a));
(_b = obj.foo.apply(obj, [1, 2].concat(a))).foo.apply(_b, [1, 2].concat(a, ["abc"]));
obj.foo.apply(obj, __spreadArrays([1, 2], a));
obj.foo.apply(obj, __spreadArrays([1, 2], a, ["abc"]));
obj.foo.apply(obj, __spreadArrays([1, 2], a)).foo(1, 2, "abc");
(_a = obj.foo.apply(obj, __spreadArrays([1, 2], a))).foo.apply(_a, __spreadArrays([1, 2], a));
(_b = obj.foo.apply(obj, __spreadArrays([1, 2], a))).foo.apply(_b, __spreadArrays([1, 2], a, ["abc"]));
(obj.foo)(1, 2, "abc");
obj.foo.apply(obj, [1, 2].concat(a));
obj.foo.apply(obj, [1, 2].concat(a, ["abc"]));
(obj.foo.apply(obj, [1, 2].concat(a)).foo)(1, 2, "abc");
(_c = obj.foo.apply(obj, [1, 2].concat(a))).foo.apply(_c, [1, 2].concat(a));
(_d = obj.foo.apply(obj, [1, 2].concat(a))).foo.apply(_d, [1, 2].concat(a, ["abc"]));
obj.foo.apply(obj, __spreadArrays([1, 2], a));
obj.foo.apply(obj, __spreadArrays([1, 2], a, ["abc"]));
(obj.foo.apply(obj, __spreadArrays([1, 2], a)).foo)(1, 2, "abc");
(_c = obj.foo.apply(obj, __spreadArrays([1, 2], a))).foo.apply(_c, __spreadArrays([1, 2], a));
(_d = obj.foo.apply(obj, __spreadArrays([1, 2], a))).foo.apply(_d, __spreadArrays([1, 2], a, ["abc"]));
xa[1].foo(1, 2, "abc");
(_e = xa[1]).foo.apply(_e, [1, 2].concat(a));
(_f = xa[1]).foo.apply(_f, [1, 2].concat(a, ["abc"]));
(_e = xa[1]).foo.apply(_e, __spreadArrays([1, 2], a));
(_f = xa[1]).foo.apply(_f, __spreadArrays([1, 2], a, ["abc"]));
(_g = xa[1]).foo.apply(_g, [1, 2, "abc"]);
var C = /** @class */ (function () {
function C(x, y) {
Expand All @@ -109,7 +116,7 @@ var C = /** @class */ (function () {
z[_i - 2] = arguments[_i];
}
this.foo(x, y);
this.foo.apply(this, [x, y].concat(z));
this.foo.apply(this, __spreadArrays([x, y], z));
}
C.prototype.foo = function (x, y) {
var z = [];
Expand All @@ -123,12 +130,12 @@ var D = /** @class */ (function (_super) {
__extends(D, _super);
function D() {
var _this = _super.call(this, 1, 2) || this;
_this = _super.apply(this, [1, 2].concat(a)) || this;
_this = _super.apply(this, __spreadArrays([1, 2], a)) || this;
return _this;
}
D.prototype.foo = function () {
_super.prototype.foo.call(this, 1, 2);
_super.prototype.foo.apply(this, [1, 2].concat(a));
_super.prototype.foo.apply(this, __spreadArrays([1, 2], a));
};
return D;
}(C));
Loading