Skip to content

feat(minifier): minimize switch statements#17523

Open
armano2 wants to merge 36 commits intooxc-project:mainfrom
armano2:feat/minimize-switch
Open

feat(minifier): minimize switch statements#17523
armano2 wants to merge 36 commits intooxc-project:mainfrom
armano2:feat/minimize-switch

Conversation

@armano2
Copy link
Contributor

@armano2 armano2 commented Dec 31, 2025

Phase 3: Advanced Optimizations

  • Switch statement optimization

changes:

  • converts 1-case and 2-case switches into more compact if or if/else statements when safe.
  • removes redundant empty case suffixes at the end of a switch.
  • removes empty switches (preserving the discriminant if it has side effects) and redundant trailing break statements.

note:

  • i left couple of todo's for later additions, that could add additional optimizations, i can remove them if needed
  • all packages seem to be affected due to napi last break removal from switch cases

future improvements:

  • we could remove all empty cases if there is no default or when its empty as long as test has no side effects
  • in-lining of switches with constant discriminants
  • merge of identical consequent cases without side effects

fixes #17544

@github-actions github-actions bot added A-minifier Area - Minifier C-enhancement Category - New feature or request labels Dec 31, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 31, 2025

Merging this PR will not alter performance

✅ 38 untouched benchmarks
⏩ 7 skipped benchmarks1


Comparing armano2:feat/minimize-switch (5118bba) with main (6ec08e8)2

Open in CodSpeed

Footnotes

  1. 7 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (01d7b13) during the generation of this report, so 6ec08e8 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions github-actions bot added A-linter Area - Linter A-parser Area - Parser A-cli Area - CLI A-linter-plugins Area - Linter JS plugins labels Dec 31, 2025
@armano2 armano2 marked this pull request as ready for review December 31, 2025 19:32
@armano2
Copy link
Contributor Author

armano2 commented Dec 31, 2025

i'm pretty bad at naming stuff, suggestions are welcome 😄

Comment on lines +108 to +115
if switch_stmt.discriminant.may_have_side_effects(ctx) {
*stmt = ctx.ast.statement_expression(
switch_stmt.span,
switch_stmt.discriminant.take_in(ctx.ast),
);
} else {
*stmt = ctx.ast.statement_empty(switch_stmt.span);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to change this, I'm just interested to learn.

Is it worth checking if the discrimimnant has side effects here?

If we were to assume it does have side effects, the other pass that visits the statement expression and checks if it does have side effects would remove it anyway?

I guess there's perhaps some careful reasoning here about duplicate checks on may_have_side_effects vs duplicate traversals

Copy link
Contributor Author

@armano2 armano2 Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, we do not want to keep expressions like switch(2){} -> 2

i' will wait for others to give feedback about this

Copy link
Contributor

@camc314 camc314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me! Thank you!

I'll let the experts (sapphi-red and Boshen have a look as well)

}

// TODO: This is to aggressive, we should allow `break` for last elements in statements
impl<'a> Visit<'a> for FindNestedBreak {
Copy link
Contributor Author

@armano2 armano2 Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be made more generic, and used within eg, for (; x; ) break; to check if loop could be completely in-lined,

maybe we could add something like is_statement_terminated or is_terminated to statement nodes, instead of my is_terminated_switch_case and with addition of FindNestedBreak that could be really useful

test("while (x) { if (1) break; z(); }", "for (; x; ) break;");

should i implement it in more generic way?

@armano2
Copy link
Contributor Author

armano2 commented Jan 15, 2026

@Boshen i noticed that you started doing some changes in #17994 for switches, and looks like we worked on same thing

how i should proceed with this change?

/// non-empty case or case with side-effect-producing expressions backward to the last case.
/// - All cases in the identified removable suffix are eliminated, except for the last case,
/// which is preserved and its test is removed (if applicable).
fn collapse_empty_switch_cases(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
Copy link
Contributor Author

@armano2 armano2 Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new optimization has been added to main, and this code is partially redundant, tho my code supports more use cases

eg, trailing empty switch cases,

if let Some(last_case) = switch_stmt.cases.last()
&& last_case.test.is_none()
{
let default_idx = switch_stmt.cases.len() - 1;
let mut first_empty_idx = default_idx;
// Iterate backward through preceding cases
while first_empty_idx > 0 {
let case = &switch_stmt.cases[first_empty_idx - 1];
// Only remove empty cases with primitive literal tests
if case.consequent.is_empty()
&& case.test.as_ref().is_some_and(Expression::is_literal)
{
first_empty_idx -= 1;
} else {
break;
}
}
// If we found cases to remove, keep cases [0..first_empty_idx] + [default]
if first_empty_idx < default_idx {
let default_case = switch_stmt.cases.pop().unwrap();
switch_stmt.cases.truncate(first_empty_idx);
switch_stmt.cases.push(default_case);
ctx.state.changed = true;
}
}

@overlookmotel overlookmotel removed A-linter Area - Linter A-linter-plugins Area - Linter JS plugins A-parser Area - Parser labels Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-minifier Area - Minifier C-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

minifier: switch statement optimization

3 participants