#12225·swc

es/minifier: conditional call merging moves common argument effects before the test

Author: kdy1Created Sep 5, 2026Updated Sep 9, 2026
LabelsC-bug

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:

javascript
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:

json
{
  "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:

1

Actual behavior

conditional_call_common_sideeffect emits:

javascript
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:

2

With default compression (compress: {"conditionals": true}) this input currently retains its original behavior due to the other enabled passes. Observed stdout:

1

Exit 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/issues using 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_minifier without UPDATE after refreshing fixture expectations.

conditional_call_closure_callee:

javascript
function run(){let f=x=>"before";function test(){f=x=>"after";return true}return test()?f(1):f(2)}console.log(run());

Config:

json
{
  "compress": {
    "defaults": false,
    "conditionals": true
  },
  "mangle": false,
  "module": false
}

Both original/output exit 0 and stdout is:

after

Duplicate 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.