Visitar URL original
es/minifier: conditional constructor folding evaluates the callee before its condition · Issue #12216 · swc-project/swc · GitHub
Skip to content

es/minifier: conditional constructor folding evaluates the callee before its condition #12216

Open

Description

@kdy1

Describe the bug

Two new expressions with an equal callee and one argument are merged without checking whether the condition changes that callee. The output new F((F=G)?1:2) resolves F before assigning G, whereas the original resolves F after the condition. The sample constructs A1 instead of B1.

Input code

conditional_new_callee:

function A(n){this.value="A"+n}function B(n){this.value="B"+n}function run(F,G){return (F=G)?new F(1):new F(2)}console.log(run(A,B).value);

Config

Options passed to minify, equivalent to the exact-source harness settings:

conditional_new_callee:

{
  "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_new_callee: exit 0, stdout:

B1

Actual behavior

conditional_new_callee emits:

function A(n){this.value="A"+n;}function B(n){this.value="B"+n;}function run(F,G){return new F((F=G)?1:2);}console.log(run(A,B).value);

Exit 0, stdout:

A1

With default compression (compress: {"conditionals": true}) this input also changes behavior. Observed stdout:

A1

Exit 0.

Version

swc_ecma_minifier 61.0.5, commit 5dada7eaeafc39f7e3ad97ad1a1ea6eed57ceb65, also freshly fetched main.

Additional context

Responsible code: compress_similar_cons_alt Expr::New pair.

Two new expressions with an equal callee and one argument are merged without checking whether the condition changes that callee. The output new F((F=G)?1:2) resolves F before assigning G, whereas the original resolves F after the condition. The sample constructs A1 instead of B1.

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

Restrict constructor-argument merging to a callee proven stable and side-effect-free across the test; conservatively decline when the test references/modifies that callee or when the callee itself has effects.

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 assigning-condition repro constructs B with value B1.
  • A stable constructor and boolean parameter condition can still fold and produce A1.
  • 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.

new_control:

function A(n){this.value="A"+n}function run(F,test){return test?new F(1):new F(2)}console.log(run(A,true).value);

Config:

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

Both original/output exit 0 and stdout is:

A1

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions