Visitar URL original
Compress/minifier: switches incorrectly removes switch case containing closure over initializer binding · Issue #12492 · swc-project/swc · GitHub
Skip to content

Compress/minifier: switches incorrectly removes switch case containing closure over initializer binding #12492

Closed

Description

Describe the bug

I tried to isolate the required compress switches down to switches, but this reproduces with the default settings as well.

Input code

function startAsync(options) {
  setTimeout(options.onDone, 3000);
  return 3;
}

function handleClick(type) {
  switch (type) {
    case 'async': {
      const asyncId = startAsync({
        onDone: () => {
          console.info(asyncId);
        },
      });
      break;
    }
  }

  console.info('after');
}

handleClick('async');

Config

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": {
        "arguments": false,
        "arrows": false,
        "booleans": false,
        "booleans_as_integers": false,
        "collapse_vars": false,
        "comparisons": false,
        "computed_props": false,
        "conditionals": false,
        "dead_code": true,
        "directives": false,
        "drop_console": false,
        "drop_debugger": false,
        "evaluate": false,
        "expression": false,
        "hoist_funs": false,
        "hoist_props": false,
        "hoist_vars": false,
        "if_return": false,
        "join_vars": false,
        "keep_classnames": false,
        "keep_fargs": false,
        "keep_fnames": false,
        "keep_infinity": false,
        "loops": false,
        "negate_iife": false,
        "properties": false,
        "reduce_funcs": false,
        "reduce_vars": false,
        "side_effects": false,
        "switches": true,
        "typeofs": false,
        "unsafe": false,
        "unsafe_arrows": false,
        "unsafe_comps": false,
        "unsafe_Function": false,
        "unsafe_math": false,
        "unsafe_symbols": false,
        "unsafe_methods": false,
        "unsafe_proto": false,
        "unsafe_regexp": false,
        "unsafe_undefined": false,
        "unused": false,
        "const_to_let": false,
        "pristine_globals": false
      },
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}

Link to the code that reproduces this issue

https://play.swc.rs/?version=1.16.2&code=H4sIAAAAAAAAA12P3QrCMAyF7%2FsU524tDBl451AQvfHeF6g1w7LZSpshInt399PpMBBIPpKTk6p1hq13iKwD7%2BPLGekfA4kKbwFE4rO9k2955ivvjt5RjnVRFKrsZwJxGxzWpeiEqGbFm3bXhg6NNbXk14OS3tOyuWFBAKMjIdPD8WyTWE%2F7W4yRnq7YLh3OI8DkZQOpsN3hx6d139DKusrLpDK6naLLU9l94SWQrqemE0OKP5VMV0whU%2BOfy%2FeSd1V%2BADHbLNpPAQAA&config=H4sIAAAAAAAAA32UTXLbMAyF9z6FR%2Btu20UPkF3PwKFJUKYrEhoCdKzJ%2BO6F%2FhwnBb2T8D0I4iOAj8Px2F3Idb%2BPH%2FIoL6MtBOXxLhGaMtubRDpwyZIrceTux04vNKNgB4IldF9Jx7b0wEsW%2Fdzk3YBIsMu3WIo5hum5oMM0FiB6iklUvlcTZKav%2BRsr%2BK6BE%2BIANr9CxpKJmaGHoskcDoMdCczVNngSyyKhWmSmlcGbseCoC7KPHDFLXMEerDcO%2FWwal%2FoFxQKO4xXUPCkneZnkjC3s4VT7frnr7xyudqiWtVS4LXcjf6zAM0ZiE6rqxQpbRqy0YXIMpgDXotW8YMytvL8AYsNgibJNqlGLIkhvteHL1JiD9C9PCpdmV0%2BaoRdrTYxB83f2BwpHtWQBXx3MBrsXuGEGRQ8GQpCuUfF7ZHdeyn7rNJ5GwKDlyEVb9RQrMM253Pg8Hi%2Fwm5yT9U7bFMnyuU1pSidU52pPBz6jfyGQ22Bs4yJb4za2ec0epD%2FAq5JKKpjHlg2jGZb9%2BX%2BDyKDIN00%2F4Olpa2yK%2B2MzJ5v7z%2Blfl%2FNhE3QJfV3gtvbnO16X9a%2FuU7Sv5sc%2FdJH%2B7Jlzlxzu%2FwBEeaKZQgYAAA%3D%3D

SWC Info output

No response

Expected behavior

The switch and case statement should not be removed, because there is a side effect that needs to run.
In the test code, this will print "after", wait roughly 3 seconds, and then print "3".

Actual behavior

The switch and case statement is removed, and the side effect does not happen.
In the compressed result, this will only print "after" and finish immediately.

Version

1.16.2

Additional context

No response

Activity

  1. Austaras commented on Oct 8, 2026

    @Austaras
    Member

    The provided swc playground link produces identical result, they both print after immediately and then print 3 after 3 seconds.

  2. August-Z commented on Oct 9, 2026

    @August-Z
    Contributor

    Thanks for checking. The linked playground targets ES5, where const is lowered to var before compression, so it doesn't reproduce there. Changing only jsc.target to es2015 reproduces it, leaving only console.info('after'): playground (es2015)

    Cause: optimize_switch_cases relies on StmtExt::may_have_side_effects (swc_ecma_utils), which reports every let/const declaration as side-effect free without checking the initializer, so the trailing case is dropped. The closure is what keeps asyncId alive past DCE. Without it, DCE turns the declaration into a plain call and nothing breaks.

    A fix with regression tests is in #12495 (closed automatically). One caveat I found since: treating every declaration as effectful there also sends let/const into the default-merge branch (cases.drain(start..) in switches.rs), which already drops var bodies today. For example, default: case f(): case 1: var id = g(); loses g(). That branch needs an adjustment before the patch is complete.

  3. Austaras commented on Oct 10, 2026

    @Austaras
    Member

    That would make it same issue as #12145.

  4. added a commit that references this issue on Oct 11, 2026
    6841927
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions