Fix evaluation order with object spread (#11412)
* Fix object spread runtime semantics * limit this deoptimization * optimize empty objects * declare variables
This commit is contained in:
@@ -541,6 +541,9 @@ export default declare((api, opts) => {
|
||||
ObjectExpression(path, file) {
|
||||
if (!hasSpread(path.node)) return;
|
||||
|
||||
const { scope } = path;
|
||||
|
||||
// a non-SpreadElement and SpreadElement striped array
|
||||
const args = [];
|
||||
let props = [];
|
||||
|
||||
@@ -579,8 +582,54 @@ export default declare((api, opts) => {
|
||||
helper = file.addHelper("objectSpread");
|
||||
}
|
||||
}
|
||||
// We cannot call _objectSpread with more than two elements directly, since any element could cause side effects. For
|
||||
// example:
|
||||
// var k = { a: 1, b: 2 };
|
||||
// var o = { a: 3, ...k, b: k.a++ };
|
||||
// // expected: { a: 1, b: 1 }
|
||||
// If we translate the above to `_objectSpread({ a: 3 }, k, { b: k.a++ })`, the `k.a++` will evaluate before
|
||||
// `k` is spread and we end up with `{ a: 2, b: 1 }`.
|
||||
// adapted from https://github.com/microsoft/TypeScript/blob/eb105efdcd6db8a73f5b983bf329cb7a5eee55e1/src/compiler/transformers/es2018.ts#L272
|
||||
const chunks = [];
|
||||
let currentChunk = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
currentChunk.push(args[i]);
|
||||
const isCurrentChunkEmptyObject =
|
||||
currentChunk.length === 1 &&
|
||||
t.isObjectExpression(args[i]) &&
|
||||
args[i].properties.length === 0;
|
||||
const isNextArgEffectful =
|
||||
i < args.length - 1 && !scope.isPure(args[i + 1]);
|
||||
// prevent current chunk from pollution unless current chunk is an empty object
|
||||
if (!isCurrentChunkEmptyObject && isNextArgEffectful) {
|
||||
chunks.push(currentChunk);
|
||||
currentChunk = [];
|
||||
}
|
||||
}
|
||||
|
||||
path.replaceWith(t.callExpression(helper, args));
|
||||
if (currentChunk.length) {
|
||||
chunks.push(currentChunk);
|
||||
currentChunk = [];
|
||||
}
|
||||
|
||||
let exp = t.callExpression(helper, chunks[0]);
|
||||
let nthArg = chunks[0].length;
|
||||
for (let i = 1; i < chunks.length; i++) {
|
||||
// reference: packages/babel-helpers/src/helpers.js#objectSpread2
|
||||
if (nthArg % 2) {
|
||||
exp = t.callExpression(helper, [exp, ...chunks[i]]);
|
||||
} else {
|
||||
exp = t.callExpression(helper, [
|
||||
exp,
|
||||
t.objectExpression([]),
|
||||
...chunks[i],
|
||||
]);
|
||||
}
|
||||
|
||||
nthArg += chunks[i].length;
|
||||
}
|
||||
|
||||
path.replaceWith(exp);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user