Repository navigation
Compress/minifier: switches incorrectly removes switch case containing closure over initializer binding #12492
Copy link
Copy link
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
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
Reactions are currently unavailable
Activity
Austaras commentedon Oct 8, 2026
The provided swc playground link produces identical result, they both print after immediately and then print 3 after 3 seconds.
August-Z commentedon Oct 9, 2026
Thanks for checking. The linked playground targets ES5, where
constis lowered tovarbefore compression, so it doesn't reproduce there. Changing onlyjsc.targettoes2015reproduces it, leaving onlyconsole.info('after'): playground (es2015)Cause:
optimize_switch_casesrelies onStmtExt::may_have_side_effects(swc_ecma_utils), which reports everylet/constdeclaration as side-effect free without checking the initializer, so the trailing case is dropped. The closure is what keepsasyncIdalive 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/constinto the default-merge branch (cases.drain(start..)inswitches.rs), which already dropsvarbodies today. For example,default: case f(): case 1: var id = g();losesg(). That branch needs an adjustment before the patch is complete.Austaras commentedon Oct 10, 2026
That would make it same issue as #12145.
fix(es/minifier): Check decl for side effect (swc-project#12501)