fix(minifier): remove pure-annotated initializers of unused declarations#22251
fix(minifier): remove pure-annotated initializers of unused declarations#22251mo-n wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5752d710a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
add04d1 to
f47c21e
Compare
Previously, unused variable declarations with `@__PURE__` initializers (e.g. pure IIFEs) would have their bodies incorrectly preserved as side-effectful expression statements. This replaces `may_have_side_effects` with `remove_unused_expression` and adds an `is_in_unused_variable_declarator` guard that reuses `can_remove_unused_declarators` to respect script-mode and eval constraints.
|
Can you create an issue first? |
Merging this PR will not alter performance
Comparing Footnotes
|
fix(minifier): respect
@__PURE__annotations when removing unused variable declarationsSummary
Fixes #22255
When an unused variable declaration has a
@__PURE__-annotated call expression as its initializer (e.g., a pure IIFE), the minifier incorrectly preserves the initializer body as a side-effectful expression statement instead of removing it entirely.The Bug
Given this input:
Before this fix (incorrect output):
After this fix (correct output):
// (empty — fully removed)Root Cause
In
handle_variable_declaration(minimize_statements.rs), when an unused declarator is removed, its initializer is checked viainit.may_have_side_effects(ctx)to decide whether to keep it as an expression statement:While
CallExpression::may_have_side_effectsdoes respect@__PURE__annotations for direct calls with no arguments, this approach misses the deeper expression-level optimizations thatremove_unused_expressionprovides — such as handling pure IIFEs, stripping pure calls while preserving their side-effectful arguments, and simplifying nested expressions.The real issue surfaces when:
@__PURE__IIFE like/* @__PURE__ */ (() => { return oprt(1) })()may_have_side_effectscorrectly returnsfalsefor the outer call...oprt(1)which IS side-effectfulBy using
remove_unused_expressioninstead, we leverage the full expression simplification pipeline that properly handles these cases.Fix
Replace
init.may_have_side_effects(ctx)with!Self::remove_unused_expression(&mut init, ctx)inhandle_variable_declaration. This delegates to the same expression removal logic used everywhere else in the minifier, which properly handles:@__PURE__annotated calls → fully removed@__PURE__annotated IIFEs → fully removed@__NO_SIDE_EFFECTS__functions → calls removedTest Cases Added
Impact on Downstream (Rolldown)
This bug was discovered while investigating a tree-shaking issue in Rolldown. Rolldown uses the Oxc compressor as a pre-processing step before its own tree-shaking pass. When the compressor incorrectly preserves
oprt(1)as a top-level statement, Rolldown's AST scanner treats it as a side-effectful statement, causing the entire module and its dependencies to be included in the final bundle even when unused.AI Disclosure
AI tools were used to assist with code review and identifying the missing guards in
is_in_unused_variable_declarator. All changes have been reviewed, tested, and verified by the contributor.