es/minifier: conditional call merging moves common argument effects before the test
Describe the bug
The guard tests whether the condition has side effects and then guards common argument purity only in that case. A pure read of x can still be affected by an earlier common argument side(), which assigns x=false. Moving the test to the differing second argument switches the chosen1 into2.
Input code
conditional_call_common_sideeffect:
function run(){let x=true;function side(){x=false;return 0}function f(a,b){return b}return x?f(side(),1):f(side(),2)}console.log(run());Config
Options passed to minify, equivalent to the exact-source harness settings:
conditional_call_common_sideeffect:
{
"compress": {
"defaults": false,
"conditionals": true
},
"mangle": false,
"module": false
}Link to the code that reproduces this issue
Primary input and options in SWC Playground
SWC Info output
Source build of swc_ecma_minifier 61.0.5 at 5dada7eaeafc39f7e3ad97ad1a1ea6eed57ceb65, macOS arm64, Rust 1.96.0-nightly (f5eca4fcf 2026-04-09), Node.js v20.20.2.
Observed checkout and freshly fetched authoritative origin/main are the same commit, fetched 2026-09-05T07:51:04Z. Original/emitted execution was independently repeated using an isolated checkout of that commit. The harness follows tests/exec.rs: parse Script, resolve bindings, convert Terser compressor options, optimize, apply hygiene/fixer, emit, then execute each program separately. The playground carries the portable source/settings; the source-build results do not depend on a published release matching main.
Expected behavior
conditional_call_common_sideeffect: exit 0, stdout:
1Actual behavior
conditional_call_common_sideeffect emits:
function run(){let x=true;function side(){x=false;return 0;}function f(a,b){return b;}return f(side(),x?1:2);}console.log(run());Exit 0, stdout:
2With default compression (compress: {"conditionals": true}) this input currently retains its original behavior due to the other enabled passes. Observed stdout:
1Exit 0.
Version
swc_ecma_minifier 61.0.5, commit 5dada7eaeafc39f7e3ad97ad1a1ea6eed57ceb65, also freshly fetched main.
Additional context
Responsible code: compress_similar_cons_alt Expr::Call pair.
The guard tests whether the condition has side effects and then guards common argument purity only in that case. A pure read of x can still be affected by an earlier common argument side(), which assigns x=false. Moving the test to the differing second argument switches the chosen1 into2.
No TDZ violations, externally redefined undefined/NaN/Infinity, primitive-coercion side effects, Proxy/watch, frozen or locked objects, function source/stack dependence, callee/caller reflection, or arithmetic exceptions are needed. Local parameter shadowing is distinct from overriding a built-in object or externally redefining a global.
Proposed Scope
Before moving the condition past a common earlier argument, prove that argument cannot affect the condition; conservatively reject side-effecting common arguments before the differing index. Preserve merges with pure common arguments.
Limit the change to this transformation and its regression coverage. No public API, dependency, migration, deployment, or rollout change is required.
Acceptance Criteria and Test Scenarios
- The isolated conditionals-only repro returns1, not2.
- The existing closure-callee condition remains correct.
- Default compression currently simplifies the entire sample to1, so this must be exercised with the supplied supported isolated config.
- Add SWC-owned regression fixtures under
crates/swc_ecma_minifier/tests/fixture/issuesusing the exact inputs and configurations above. Assert the expected execution stdout/exit status as well as valid generated syntax. - Preserve the concrete passing controls below. Use the existing fixture/execution harness, then run
cargo test -p swc_ecma_minifierwithoutUPDATEafter refreshing fixture expectations.
conditional_call_closure_callee:
function run(){let f=x=>"before";function test(){f=x=>"after";return true}return test()?f(1):f(2)}console.log(run());Config:
{
"compress": {
"defaults": false,
"conditionals": true
},
"mangle": false,
"module": false
}Both original/output exit 0 and stdout is:
afterDuplicate search
No same-root open duplicate found in bounded searches: conditional minifier.
Out of Scope
Unrelated rewrites, new options, and changing documented minifier semantic assumptions.
This is a message for readers, not the author of this issue.
Please read no +1 before leaving a comment.
Source: swc-project/swc