refactor: unify pre-scan multi-declarator split into one decision site#9982
Merged
Conversation
✅ Deploy Preview for rolldown-rs canceled.
|
Merging this PR will not alter performance
Comparing Footnotes
|
4fae640 to
c43aaca
Compare
Member
Author
How to use the Graphite Merge QueueAdd the label graphite: merge-when-ready to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
hyf0
approved these changes
Jun 26, 2026
Member
|
LEGIT! |
shulaoda
approved these changes
Jun 26, 2026
Member
Author
Merge activity
|
#9982) ## Why The previous pre-scan split was **fragile and error-prone by construction** — this refactor removes the mechanism, not just another instance of the bug. - The split *decision* was made deep in the tree (`visit_declaration`, `visit_export_named_declaration`), but the *application* — replacing one statement with several — happens at the statement-list level. The two were bridged by a deferred, address-keyed `statement_replace_map` + `statement_stack`. - That indirection let **two independent deciders write the same map key**. Because `visit_declaration` also fires for an export''s inner declaration, the bare-var and export deciders raced — silently dropping the `export` keyword on multi-declarator exports under `keepNames` (#9973). - #9974 refereed the race with a `next_declaration_is_exported` boolean — papering over the collision with shared mutable state rather than removing it. The same shape (two overlapping mutators coordinated by a flag) stays just as easy to break the next time a statement form is added. ## What Consolidate the decision into a single `split_multi_declarator(stmt)` helper, called once per statement from the three slots that own statement placement — `visit_program`, `visit_statements`, `visit_statement`. Each statement is inspected exactly once, with full context, so export-vs-bare is decided by matching the node type. No deferred map, no side-channel flag, and the collision becomes **structurally impossible** (one decider per slot). ## Removed - `statement_replace_map` + `statement_stack` (deferred, address-keyed replacement) - `next_declaration_is_exported` (the #9974 referee flag) - the `visit_declaration` and `visit_export_named_declaration` overrides ## Behavior Pure refactor — output is byte-identical. `cargo test -p rolldown --test integration` passes (1768 tests) with **zero snapshot changes**; clippy clean.
c43aaca to
efc535a
Compare
graphite-app Bot
pushed a commit
that referenced
this pull request
Jun 26, 2026
…efactor (#9983) Stacked on #9982. After #9982 removed `PreProcessor`''s `statement_stack` / `statement_replace_map`, the **Address Use** section of `internal-docs/ast-mutation/implementation.md` went stale — it cited those as *the* current `Address` example and said `Address` was required there. With that refactor, `Address` / `GetAddress` have **no** remaining consumer anywhere in `crates/**`. This **removes the section outright** — it documented a pattern with no remaining usage. The `NodeId` / `Span` taxonomy in the rest of the doc already covers cross-pass identity (`NodeId`, `(ModuleIdx, NodeId)`) vs source location (`Span`). Docs only — no code change.
Merged
shulaoda
added a commit
that referenced
this pull request
Jul 1, 2026
## [1.1.4] - 2026-07-01 ### 🚀 Features - disable `experimental.lazyBarrel` by default (#10071) by @shulaoda ### 🐛 Bug Fixes - dev: disable lazy barrel in dev mode (#10060) by @shulaoda - generate: keep full JSON interface under preserveModules namespa… (#10056) by @IWANABETHATGUY - check finalize_other_specifiers in its own Debug attribute (#10032) by @shulaoda - serialize the KeepAssign unused minify option as "keep_assign" (#10031) by @shulaoda - keep fragments after the newline fragment in MagicString::last_line (#10023) by @shulaoda - generate: undeclared JSON named exports under preserveModules (#10020) (#10027) by @IWANABETHATGUY - deconflict: rename CJS-wrapped locals that shadow chunk-root bindings (#9921) by @IWANABETHATGUY - rolldown: keep entry facade when a shared chunk holds another entry's module (#9997) by @hyf0 - treeshake: also bail JSON default split when the object escapes (#9996) by @IWANABETHATGUY - don't classify await in a strict-mode function as top-level await (#9987) by @shulaoda - avoid spurious leading newline in addon hooks (banner/footer/intro/outro) (#9989) by @shulaoda - handle JSON default mutation bailouts (#9972) by @TheAlexLichter - plugin: make lazy hook metadata enumerable (#9991) by @TheAlexLichter - dev: make init errors in lazy-compiled modules catchable (#9981) by @h-a-n-a - treeshake: keep computed-key side effects on namespace member access (#9986) by @shulaoda - binding: validate replace plugin delimiters length instead of panicking (#9984) by @shulaoda - reconstruct nested rest patterns in into_expression (#9980) by @IWANABETHATGUY - reconstruct rest patterns as spread in into_expression (#9976) by @shulaoda - preserve export keyword on multi-declarator exports under keepNames (#9974) by @shulaoda - deterministically keep the shortest name for deduplicated assets (#9948) by @x1024 - treeshake: apply @__NO_SIDE_EFFECTS__ to cross-chunk namespace calls (#9960) by @IWANABETHATGUY ### 🚜 Refactor - drop redundant program scope enter/leave in finalizer (#10049) by @shulaoda - deconflict: extract collect_chunk_scope_captured_names (#10006) by @IWANABETHATGUY - unify pre-scan multi-declarator split into one decision site (#9982) by @IWANABETHATGUY - common: return bool from SymbolRef::is_not_reassigned (#9962) by @IWANABETHATGUY ### 📚 Documentation - rolldown: remove outdated comment for removing parenthesized expression (#10062) by @Dunqing - use GitHub-flavored alert for Etiquette note in contribution guide (#10012) by @IWANABETHATGUY - replace: explain the delimiters left and right boundaries (#9985) by @shulaoda - ast-mutation: remove stale Address Use section after pre-scan refactor (#9983) by @IWANABETHATGUY - remove fathom (#9968) by @mdong1909 - contribution-guide: code-format main branch references (#9966) by @IWANABETHATGUY - contribution-guide: fix stale REPL note and tidy wording (#9957) by @hyf0 - contribution-guide: clarify when to discuss before opening a PR (#9955) by @hyf0 ### ⚡ Performance - disable preserve_parens across all parse paths (#10057) by @Dunqing - common: inline declared_symbols with SmallVec (#9920) by @IWANABETHATGUY - common: pack TaggedSymbolRef into 8 bytes (#9919) by @IWANABETHATGUY - sourcemap: skip newline scan on the no-sourcemap join fast path (#9936) by @Boshen ### 🧪 Testing - dev: error in lazy module should be catchable (#9975) by @sapphi-red - dev: reject unknown lazy compile modules (#9969) by @sapphi-red ### ⚙️ Miscellaneous Tasks - deps: update actions/cache action to v6 (#10001) by @renovate[bot] - trigger vite ecosystem-ci from PR comments (#10058) by @shulaoda - deps: update napi to v3.10.0 (#10063) by @renovate[bot] - remove unused From impl for RolldownLabelSpan (#10055) by @shulaoda - remove dead Diagnostic::with_kind method (#10054) by @shulaoda - remove unused StatementExt methods (#10053) by @shulaoda - remove unused ExpressionExt methods (#10052) by @shulaoda - remove commented-out re_export_all_names field (#10051) by @shulaoda - deps: update pnpm to v11.9.0 (#10047) by @renovate[bot] - remove the unused BindingGenerateHmrPatchReturn napi type (#10034) by @shulaoda - remove the dead inline_entry_chunk_wrapping scaffolding (#10037) by @shulaoda - deps: bump oxc_resolver to 11.22.0 (#10045) by @Boshen - remove never-constructed MatchImportKind::_Ignore variant (#10041) by @shulaoda - remove the unused ScheduledBuild napi struct (#10033) by @shulaoda - remove dead compute_hmr_update_single method (#10040) by @shulaoda - drop the redundant visited.insert in manual code splitting (#10038) by @shulaoda - remove the dead output_assets vector in render_chunk_to_assets (#10036) by @shulaoda - remove the unused From<String>/Display impls for BindingLogLevel (#10035) by @shulaoda - deps: upgrade oxc to 0.138.0 and migrate to per-type AST construction (#10018) by @shulaoda - deps: update rust crates (#9911) by @renovate[bot] - deps: update test262 submodule for tests (#10016) by @rolldown-guard[bot] - deps: update github actions (#9999) by @renovate[bot] - deps: update npm packages (#10000) by @renovate[bot] ###◀️ Revert - "fix(plugin): make lazy hook metadata enumerable (#9991)" (#10005) by @shulaoda ### ❤️ New Contributors * @x1024 made their first contribution in [#9948](#9948) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
The previous pre-scan split was fragile and error-prone by construction — this refactor removes the mechanism, not just another instance of the bug.
visit_declaration,visit_export_named_declaration), but the application — replacing one statement with several — happens at the statement-list level. The two were bridged by a deferred, address-keyedstatement_replace_map+statement_stack.visit_declarationalso fires for an export''s inner declaration, the bare-var and export deciders raced — silently dropping theexportkeyword on multi-declarator exports underkeepNames([Bug]:keepNamesdrops theexportkeyword on multi-declarator exports (export const a = 1, b = 2) #9973).next_declaration_is_exportedboolean — papering over the collision with shared mutable state rather than removing it. The same shape (two overlapping mutators coordinated by a flag) stays just as easy to break the next time a statement form is added.What
Consolidate the decision into a single
split_multi_declarator(stmt)helper, called once per statement from the three slots that own statement placement —visit_program,visit_statements,visit_statement. Each statement is inspected exactly once, with full context, so export-vs-bare is decided by matching the node type. No deferred map, no side-channel flag, and the collision becomes structurally impossible (one decider per slot).Removed
statement_replace_map+statement_stack(deferred, address-keyed replacement)next_declaration_is_exported(the fix: preserve export keyword on multi-declarator exports under keepNames #9974 referee flag)visit_declarationandvisit_export_named_declarationoverridesBehavior
Pure refactor — output is byte-identical.
cargo test -p rolldown --test integrationpasses (1768 tests) with zero snapshot changes; clippy clean.