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:
Zen
2020-04-24 05:59:35 +08:00
committed by GitHub
parent 5f299bab18
commit af669297ef
12 changed files with 144 additions and 1 deletions

View File

@@ -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);
},
},
};