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

Fix array spread with sideeffects #41523

Merged
merged 2 commits into from
Jan 6, 2021
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
Next Next commit
Fix array spread with sideeffects
  • Loading branch information
rbuckton committed Jan 5, 2021
commit f9a05eba2621ba5538d06dbf0065e7632283766f
7 changes: 3 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24695,7 +24695,7 @@ namespace ts {

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

const arrayOrIterableType = checkExpression(node.expression, checkMode);
Expand Down Expand Up @@ -24723,7 +24723,7 @@ namespace ts {
const e = elements[i];
if (e.kind === SyntaxKind.SpreadElement) {
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays);
checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray);
}
const spreadType = checkExpression((<SpreadElement>e).expression, checkMode, forceTuple);
if (isArrayLikeType(spreadType)) {
Expand Down Expand Up @@ -39048,8 +39048,7 @@ namespace ts {
case ExternalEmitHelpers.Generator: return "__generator";
case ExternalEmitHelpers.Values: return "__values";
case ExternalEmitHelpers.Read: return "__read";
case ExternalEmitHelpers.Spread: return "__spread";
case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays";
case ExternalEmitHelpers.SpreadArray: return "__spreadArray";
case ExternalEmitHelpers.Await: return "__await";
case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator";
case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator";
Expand Down
39 changes: 21 additions & 18 deletions src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ namespace ts {
// ES2015 Helpers
createExtendsHelper(name: Identifier): Expression;
createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression): Expression;
createSpreadHelper(argumentList: readonly Expression[]): Expression;
createSpreadArraysHelper(argumentList: readonly Expression[]): Expression;
createSpreadArrayHelper(to: Expression, from: Expression): Expression;
// ES2015 Destructuring Helpers
createValuesHelper(expression: Expression): Expression;
createReadHelper(iteratorRecord: Expression, count: number | undefined): Expression;
Expand Down Expand Up @@ -58,8 +57,7 @@ namespace ts {
// ES2015 Helpers
createExtendsHelper,
createTemplateObjectHelper,
createSpreadHelper,
createSpreadArraysHelper,
createSpreadArrayHelper,
// ES2015 Destructuring Helpers
createValuesHelper,
createReadHelper,
Expand Down Expand Up @@ -284,22 +282,12 @@ namespace ts {
);
}

function createSpreadHelper(argumentList: readonly Expression[]) {
context.requestEmitHelper(readHelper);
context.requestEmitHelper(spreadHelper);
return factory.createCallExpression(
getUnscopedHelperName("__spread"),
/*typeArguments*/ undefined,
argumentList
);
}

function createSpreadArraysHelper(argumentList: readonly Expression[]) {
context.requestEmitHelper(spreadArraysHelper);
function createSpreadArrayHelper(to: Expression, from: Expression) {
context.requestEmitHelper(spreadArrayHelper);
return factory.createCallExpression(
getUnscopedHelperName("__spreadArrays"),
getUnscopedHelperName("__spreadArray"),
/*typeArguments*/ undefined,
argumentList
[to, from]
);
}

Expand Down Expand Up @@ -629,6 +617,7 @@ namespace ts {
};`
};

/** @deprecated To be removed in TS >= 4.3, as it may be referenced in a tsbuildinfo */
export const spreadHelper: UnscopedEmitHelper = {
name: "typescript:spread",
importName: "__spread",
Expand All @@ -641,6 +630,7 @@ namespace ts {
};`
};

/** @deprecated To be removed in TS >= 4.3, as it may be referenced in a tsbuildinfo */
export const spreadArraysHelper: UnscopedEmitHelper = {
name: "typescript:spreadArrays",
importName: "__spreadArrays",
Expand All @@ -655,6 +645,18 @@ namespace ts {
};`
};

export const spreadArrayHelper: UnscopedEmitHelper = {
name: "typescript:spreadArray",
importName: "__spreadArray",
scoped: false,
text: `
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};`
};

// ES2015 Destructuring Helpers

export const valuesHelper: UnscopedEmitHelper = {
Expand Down Expand Up @@ -888,6 +890,7 @@ namespace ts {
templateObjectHelper,
spreadHelper,
spreadArraysHelper,
spreadArrayHelper,
valuesHelper,
readHelper,
generatorHelper,
Expand Down
54 changes: 34 additions & 20 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3895,14 +3895,27 @@ namespace ts {
* @param multiLine A value indicating whether the result should be emitted on multiple lines.
*/
function transformAndSpreadElements(elements: NodeArray<Expression>, needsUniqueCopy: boolean, multiLine: boolean, hasTrailingComma: boolean): Expression {
// When there is no leading SpreadElement:
//
// [source]
// [a, ...b, c]
//
// [output (downlevelIteration)]
// __spread([a], b, [c])
// __spreadArray(__spreadArray([a], __read(b)), [c])
//
// [output]
// __spreadArray(__spreadArray([a], b), [c])
//
// When there *is* a leading SpreadElement:
//
// [source]
// [...a, b]
//
// [output (downlevelIteration)]
// __spreadArray(__spreadArray([], __read(a)), [b])
//
// [output]
// __spreadArrays([a], b, [c])
// __spreadArray(__spreadArray([], a), [b])

// Map spans of spread expressions into their expressions and spans of other
// expressions into an array literal.
Expand All @@ -3913,28 +3926,29 @@ namespace ts {
)
);

if (compilerOptions.downlevelIteration) {
if (segments.length === 1) {
const firstSegment = segments[0];
if (isCallToHelper(firstSegment, "___spread" as __String)) {
return segments[0];
}
const helpers = emitHelpers();
if (segments.length === 1) {
const firstSegment = segments[0];
if (!needsUniqueCopy
|| isPackedArrayLiteral(firstSegment)
|| isCallToHelper(firstSegment, "___spreadArray" as __String)) {
return segments[0];
}

return emitHelpers().createSpreadHelper(segments);
}
else {
if (segments.length === 1) {
const firstSegment = segments[0];
if (!needsUniqueCopy
|| isPackedArrayLiteral(firstSegment)
|| isCallToHelper(firstSegment, "___spreadArrays" as __String)) {
return segments[0];
}
}

return emitHelpers().createSpreadArraysHelper(segments);
const startsWithSpread = isSpreadElement(elements[0]);
let expression: Expression =
startsWithSpread ? factory.createArrayLiteralExpression() :
segments[0];
for (let i = startsWithSpread ? 0 : 1; i < segments.length; i++) {
expression = helpers.createSpreadArrayHelper(
expression,
compilerOptions.downlevelIteration && !isArrayLiteralExpression(segments[i]) ?
helpers.createReadHelper(segments[i], /*count*/ undefined) :
segments[i]);
}

return expression;
}

function isPackedElement(node: Expression) {
Expand Down
28 changes: 13 additions & 15 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6605,19 +6605,18 @@ namespace ts {
Generator = 1 << 7, // __generator (used by ES2015 generator transformation)
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)
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)
ImportStar = 1 << 17, // __importStar (used by CommonJS/AMD/UMD module transformation)
ImportDefault = 1 << 18, // __importStar (used by CommonJS/AMD/UMD module transformation)
MakeTemplateObject = 1 << 19, // __makeTemplateObject (used for constructing template string array objects)
ClassPrivateFieldGet = 1 << 20, // __classPrivateFieldGet (used by the class private field transformation)
ClassPrivateFieldSet = 1 << 21, // __classPrivateFieldSet (used by the class private field transformation)
CreateBinding = 1 << 22, // __createBinding (use by the module transform for (re)exports and namespace imports)
SpreadArray = 1 << 10, // __spreadArray (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)
ImportStar = 1 << 16, // __importStar (used by CommonJS/AMD/UMD module transformation)
ImportDefault = 1 << 17, // __importStar (used by CommonJS/AMD/UMD module transformation)
MakeTemplateObject = 1 << 18, // __makeTemplateObject (used for constructing template string array objects)
ClassPrivateFieldGet = 1 << 19, // __classPrivateFieldGet (used by the class private field transformation)
ClassPrivateFieldSet = 1 << 20, // __classPrivateFieldSet (used by the class private field transformation)
CreateBinding = 1 << 21, // __createBinding (use by the module transform for (re)exports and namespace imports)
FirstEmitHelper = Extends,
LastEmitHelper = CreateBinding,

Expand All @@ -6634,8 +6633,7 @@ namespace ts {
AsyncDelegatorIncludes = Await | AsyncDelegator | AsyncValues,

// Helpers included by ES2015 spread
SpreadIncludes = Read | Spread,

SpreadIncludes = Read | SpreadArray,
}

export const enum EmitHint {
Expand Down
12 changes: 5 additions & 7 deletions tests/baselines/reference/argumentExpressionContextualTyping.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ 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;
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
// In a typed function call, argument expressions are contextually typed by their corresponding parameter types.
function foo(_a) {
Expand All @@ -43,5 +41,5 @@ var tuple = ["string", 1, true];
baz(tuple);
baz(["string", 1, true]);
baz(array); // Error
baz(__spreadArrays(["string", 1, true], array)); // Error
baz(__spreadArray(["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,12 +16,10 @@ 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;
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
// 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
Expand All @@ -33,6 +31,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 = __spreadArrays([1, 2, 3], array);
var spr1 = __spreadArrays([1, 2, 3], tup);
var spr2 = __spreadArrays([1, 2, 3], tup); // Error
var spr = __spreadArray([1, 2, 3], array);
var spr1 = __spreadArray([1, 2, 3], tup);
var spr2 = __spreadArray([1, 2, 3], tup); // Error
28 changes: 13 additions & 15 deletions tests/baselines/reference/arrayLiteralSpread.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,25 @@ 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;
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
function f0() {
var a = [1, 2, 3];
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);
var a1 = __spreadArray([], a);
var a2 = __spreadArray([1], a);
var a3 = __spreadArray([1, 2], a);
var a4 = __spreadArray(__spreadArray([], a), [1]);
var a5 = __spreadArray(__spreadArray([], a), [1, 2]);
var a6 = __spreadArray(__spreadArray([1, 2], a), [1, 2]);
var a7 = __spreadArray(__spreadArray(__spreadArray([1], a), [2]), a);
var a8 = __spreadArray(__spreadArray(__spreadArray([], a), a), a);
}
function f1() {
var a = [1, 2, 3];
var b = __spreadArrays(["hello"], a, [true]);
var b = __spreadArray(__spreadArray(["hello"], a), [true]);
var b;
}
function f2() {
Expand Down
29 changes: 15 additions & 14 deletions tests/baselines/reference/arrayLiteralSpreadES5iterable.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,28 @@ var __read = (this && this.__read) || function (o, n) {
}
return ar;
};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
function f0() {
var a = [1, 2, 3];
var a1 = __spread(a);
var a2 = __spread([1], a);
var a3 = __spread([1, 2], a);
var a4 = __spread(a, [1]);
var a5 = __spread(a, [1, 2]);
var a6 = __spread([1, 2], a, [1, 2]);
var a7 = __spread([1], a, [2], a);
var a8 = __spread(a, a, a);
var a1 = __spreadArray([], __read(a));
var a2 = __spreadArray([1], __read(a));
var a3 = __spreadArray([1, 2], __read(a));
var a4 = __spreadArray(__spreadArray([], __read(a)), [1]);
var a5 = __spreadArray(__spreadArray([], __read(a)), [1, 2]);
var a6 = __spreadArray(__spreadArray([1, 2], __read(a)), [1, 2]);
var a7 = __spreadArray(__spreadArray(__spreadArray([1], __read(a)), [2]), __read(a));
var a8 = __spreadArray(__spreadArray(__spreadArray([], __read(a)), __read(a)), __read(a));
}
function f1() {
var a = [1, 2, 3];
var b = __spread(["hello"], a, [true]);
var b = __spreadArray(__spreadArray(["hello"], __read(a)), [true]);
var b;
}
function f2() {
var a = __spread([]);
var b = __spread([5]);
var a = [];
var b = [5];
}
Loading