fix(ecmascript): treat update expressions as unconditionally side-effectful#21456
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Merging this PR will not alter performance
Comparing Footnotes
|
|
I remember the original behavior comes from Rolldown for some tests; maybe we need a fix in Rolldown as well. |
0879511 to
1860cc7
Compare
Merge activity
|
…ectful (#21456) ## Summary `obj.prop++` performs an implicit `GetValue` + `ToNumeric` + `PutValue`. The `ToNumeric` coercion alone can invoke `valueOf`/`Symbol.toPrimitive` on the old value, and the read/write may trigger getters, setters, or Proxy traps. Previously `UpdateExpression::may_have_side_effects` only checked `property_write_side_effects()` and treated the update as free when disabled — letting the minifier drop `counter.value++` even when `counter` is externally observable. ## Fix Return `true` unconditionally for `UpdateExpression`. Terser, esbuild, Rollup, and SWC all hardcode `++`/`--` as unconditionally side-effectful — this matches their behavior, and mirrors how compound assignments like `a.b += 1` are already handled one impl above (`AssignmentExpression`). ## Impact Under rolldown's tree-shake config (`propertyWriteSideEffects: false`), these are now correctly preserved instead of being dropped: ```js counter.value++; (class { static { ++counter.count; } }); class A { [counter.another++] = 123; } ``` ## Reported rolldown/rolldown#9094 ([comment](rolldown/rolldown#9094 (comment)))
1860cc7 to
d7a359a
Compare
### 💥 BREAKING CHANGES - 24fb7eb allocator: [**BREAKING**] Rename `Box` and `Vec` methods (#21395) (overlookmotel) ### 🚀 Features - ce5072d parser: Support `turbopack` magic comments (#20803) (Kane Wang) - f5deb55 napi/transform: Expose `optimizeConstEnums` and `optimizeEnums` options (#21388) (Dunqing) - 24b03de data_structures: Introduce `NonNullConst` and `NonNullMut` pointer types (#21425) (overlookmotel) ### 🐛 Bug Fixes - d7a359a ecmascript: Treat update expressions as unconditionally side-effectful (#21456) (Dunqing) - 56af2f4 transformer/async-to-generator: Correct scope of inferred named FE in async-to-generator (#21458) (Dunqing) - b3ed467 minifier: Avoid illegal `var;` when folding unused arguments copy loop (#21421) (fazba) - b0e8f13 minifier: Preserve `var` inside `catch` with same-named parameter (#21366) (Dunqing) - 4fb73a7 transformer/typescript: Preserve execution order for accessor with `useDefineForClassFields: false` (#21369) (Dunqing) ### ⚡ Performance - da3cc16 parser: Refactor out `LexerContext` (#21275) (Ulrich Stark) ### 📚 Documentation - c5b19bb allocator: Reformat comments in `Arena` (#21448) (overlookmotel) - 091e88e lexer: Update doc comment about perf benefit of reading through references (#21423) (overlookmotel) - 922cbee allocator: Remove references to "bump" from comments (#21397) (overlookmotel) Co-authored-by: Dunqing <[email protected]>

Summary
obj.prop++performs an implicitGetValue+ToNumeric+PutValue. TheToNumericcoercion alone can invokevalueOf/Symbol.toPrimitiveon the old value, and the read/write may trigger getters, setters, or Proxy traps.Previously
UpdateExpression::may_have_side_effectsonly checkedproperty_write_side_effects()and treated the update as free when disabled — letting the minifier dropcounter.value++even whencounteris externally observable.Fix
Return
trueunconditionally forUpdateExpression. Terser, esbuild, Rollup, and SWC all hardcode++/--as unconditionally side-effectful — this matches their behavior, and mirrors how compound assignments likea.b += 1are already handled one impl above (AssignmentExpression).Impact
Under rolldown's tree-shake config (
propertyWriteSideEffects: false), these are now correctly preserved instead of being dropped:Reported
rolldown/rolldown#9094 (comment)