Skip to content

fix(treeshake): preserve side-effect-free spread arguments in manual-pure chains#10432

Merged
graphite-app[bot] merged 1 commit into
mainfrom
fix/manual-pure-safe-spreads
Jul 24, 2026
Merged

fix(treeshake): preserve side-effect-free spread arguments in manual-pure chains#10432
graphite-app[bot] merged 1 commit into
mainfrom
fix/manual-pure-safe-spreads

Conversation

@IWANABETHATGUY

Copy link
Copy Markdown
Member

Follow-up to #10427.

#10427 restored the eagerly-evaluated child effects of manual-pure chains, but its eager-child walk maps every spread argument to an unknown side effect. So a manual-pure chain wrapping a statically-safe spread — make(...[]).value, make(...'').value, make(...[])() — is now retained wholesale, even though spreading an array, string, or template literal only runs built-in iterators. Rollup drops these.

Fix

Surface spread arguments distinctly from plain eager children (new EagerChild enum) and analyze them with analyze_spread_argument, mirroring oxc's Argument::may_have_side_effects:

  • array / string / template-literal operands are analyzed through the (oxc-gated) expression analyzer, so a side-effectful element (make(...[effect()])) or a nested spread (make(...[...x])) still retains the statement;
  • any other operand stays conservatively unknown, since evaluating or iterating it may run a user-defined Symbol.iterator.

Tests

New unit test test_manual_pure_chains_drop_side_effect_free_spreads covers both directions: safe literal spreads become removable, while effectful or opaque spreads stay retained — including the nested-spread (...[...x]) and bare-identifier (...x) cases that must not under-retain.

@netlify

netlify Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit f9c87c2
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a6301471fd48c0008dcc0fa

@codspeed-hq

codspeed-hq Bot commented Jul 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing fix/manual-pure-safe-spreads (1578552) with main (cd742e7)

Open in CodSpeed

Footnotes

  1. 10 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.

hyfdev commented Jul 24, 2026

Copy link
Copy Markdown
Member

Merge activity

  • Jul 24, 6:07 AM UTC: The merge label 'graphite: merge-when-ready' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • Jul 24, 6:07 AM UTC: hyfdev added this pull request to the Graphite merge queue.
  • Jul 24, 6:13 AM UTC: Merged by the Graphite merge queue.

…pure chains (#10432)

Follow-up to #10427.

#10427 restored the eagerly-evaluated child effects of manual-pure chains, but its eager-child walk maps *every* spread argument to an unknown side effect. So a manual-pure chain wrapping a statically-safe spread — `make(...[]).value`, `make(...'').value`, `make(...[])()` — is now retained wholesale, even though spreading an array, string, or template literal only runs built-in iterators. Rollup drops these.

## Fix

Surface spread arguments distinctly from plain eager children (new `EagerChild` enum) and analyze them with `analyze_spread_argument`, mirroring oxc's `Argument::may_have_side_effects`:

- array / string / template-literal operands are analyzed through the (oxc-gated) expression analyzer, so a side-effectful element (`make(...[effect()])`) or a nested spread (`make(...[...x])`) still retains the statement;
- any other operand stays conservatively unknown, since evaluating or iterating it may run a user-defined `Symbol.iterator`.

## Tests

New unit test `test_manual_pure_chains_drop_side_effect_free_spreads` covers both directions: safe literal spreads become removable, while effectful or opaque spreads stay retained — including the nested-spread (`...[...x]`) and bare-identifier (`...x`) cases that must not under-retain.
@graphite-app
graphite-app Bot force-pushed the fix/manual-pure-safe-spreads branch from 1578552 to f9c87c2 Compare July 24, 2026 06:08
@graphite-app
graphite-app Bot merged commit f9c87c2 into main Jul 24, 2026
35 checks passed
@graphite-app
graphite-app Bot deleted the fix/manual-pure-safe-spreads branch July 24, 2026 06:13
graphite-app Bot pushed a commit that referenced this pull request Jul 25, 2026
…move matrix to unit tests (#10436)

Follow-up to #10427 / #10432 (both merged).

There's exactly one JS-side `manualPureFunctions` test — the `manual-pure-functions` fixture (added in #10427). It re-tested the retain/remove behavior matrix that the Rust `stmt_eval_analyzer` unit tests already own. A JS fixture only needs to smoke-test that the option is wired through the pipeline; the exhaustive behavior belongs in fast, precise Rust unit tests.

## Changes

- **Fixture → minimal smoke test.** `main.js` is now a single case: a listed function's call (`` styled.div`...` ``) is dropped, leaving just `import "styled-components";`. That alone proves the `manualPureFunctions` option reaches the pipeline — without it, the call would be retained. Snapshot regenerated against a fresh debug build.
- **Rust unit test.** `test_manual_pure_chains_without_eager_children_are_side_effect_free` covers the removed shapes (`make.div`, `` make.div`x` ``, `make?.div()`, `make()()`, `make().div()`) that previously had end-to-end-only coverage. The retained-child and spread matrices were already unit-tested (`test_manual_pure_chains_keep_eager_child_side_effects`, `test_manual_pure_chains_drop_side_effect_free_spreads`).

Test-only; no behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants