feat(rolldown): inline optional-chain enum access#9379
Merged
Conversation
✅ Deploy Preview for rolldown-rs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Merging this PR will not alter performance
Comparing Footnotes
|
hyf0
approved these changes
May 12, 2026
a67b701 to
99b875f
Compare
`try_inline_enum_access` only matched `StaticMemberExpression` /
`ComputedMemberExpression`. Optional-chain enum accesses (`E?.x`,
`E?.['x']`) wrap their inner member in a `ChainExpression`, so the
finalizer never inlined them — the access survived in the output as a
runtime member lookup.
Extend the matcher to also walk through `ChainExpression`, treating
`E?.x` as equivalent to `E.x`: enum bindings are always defined (the
IIFE initializes them to `{}` — never null/undefined), so `?.` cannot
short-circuit. Done before the main `visit_expression` dispatch so we
can pass `expr` to `try_inline_enum_access` without conflicting with
the `ChainExpression` arm's mutable destructure of the inner chain.
With the finalizer now able to inline optional access, drop the
`!prop.optional` guard from the tree-shaker bypass in
`include_statements.rs` (added in #9229 to keep the declaration alive
while the access wasn't inlinable). The `!is_write` guard stays —
writes still aren't inlined.
The `ts_enum_cross_module_inlining_access` fixture moves the optional
cases into the `inlined` group, and the snapshot now folds all 8
inlinable accesses to their literals while preserving the `e_num` /
`e_str` declarations referenced as bare identifiers.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
99b875f to
89201fe
Compare
Merged
shulaoda
added a commit
that referenced
this pull request
May 13, 2026
## [1.0.1] - 2026-05-13 ### 🚀 Features - experimental/lazy-barrel: advice on oversized barrel modules (#9236) by @shulaoda - rolldown: inline optional-chain enum access (#9379) by @Dunqing - chunk-optimization: dedupe already-loaded dynamic deps (#9305) by @IWANABETHATGUY - binding: call moduleParsed hook in ParallelJsPlugin (#9318) by @jaehafe ### 🐛 Bug Fixes - transform: enable `enum_eval` for `transformSync` and vite TS transform (#9325) by @Dunqing - error: remove severity prefix from diagnostic messages (#9262) by @Kyujenius - deps: pin pnpm to 10.23.0 to work around catalog mismatch on Netlify (#9364) by @shulaoda - ci: pin mimalloc-safe to 0.1.58 (#9361) by @shulaoda - dev/lazy: fix exports of lazy requests in lazy chunks (#9249) by @h-a-n-a - rolldown_plugin_vite_resolve: handle errors in `resolveSubpathImports` callback (#9355) by @sapphi-red - rolldown_plugin_lazy_compilation: use loadExports for fetched proxy to preserve original export names (#9132) by @h-a-n-a - common: include offending index in HybridIndexVec panic message (#9296) by @SAY-5 ### 🚜 Refactor - ecmascript: extract semantic_builder_for_transform helper (#9326) by @Dunqing - test: extract reusable static-import-cycle helper (#9332) by @IWANABETHATGUY ### 📚 Documentation - clarify scope of `topLevelVar` (#9380) by @IWANABETHATGUY - meta/design: add ast-mutation design doc (#9338) by @hyf0 - feat: add ai policy in contribution guide (#9315) by @mdong1909 ### ⚡ Performance - binding: enable mimalloc v3 to reduce idle memory (#9349) by @shulaoda ### 🧪 Testing - mcs: cover require() in `$initial` group (#9376) by @hyf0 - add regression for CJS facade chunk merge into entry (#9351) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - switch prepare-release to manual dispatch with version input (#9383) by @shulaoda - migrate `@rolldown/pluginutils` to `rolldown/plugins` (#9317) by @shulaoda - deps: pin libmimalloc-sys2 to 0.1.54 (#9372) by @shulaoda - replace `igorskyflyer/action-readfile` with `cat` (#9369) by @sapphi-red - deps: update test262 submodule for tests (#9371) by @rolldown-guard[bot] - use app token for test dep update PRs (#9368) by @sapphi-red - replace some actions with gh commands (#9367) by @sapphi-red - replace action-semantic-pull-request with inline regex (#9366) by @sapphi-red - remove pull_request_target workflows (#9188) by @Boshen - deps: upgrade oxc to 0.130.0 (#9360) by @shulaoda - deps: update github actions (major) (#9348) by @renovate[bot] - deps: update github actions (#9341) by @renovate[bot] - deps: update rust crates (#9344) by @renovate[bot] - deps: update crate-ci/typos action to v1.46.1 (#9357) by @renovate[bot] - deps: update npm packages (#9343) by @renovate[bot] - deps: update pnpm to v10.33.4 (#9347) by @renovate[bot] - deps: update dependency rolldown-plugin-dts to ^0.25.0 (#9346) by @renovate[bot] - .claude: add rolldown-repl encoder, rename decode skill (#9352) by @IWANABETHATGUY - deps: update crate-ci/typos action to v1.46.0 (#9345) by @renovate[bot] - deps: update napi to v3.8.6 (#9342) by @renovate[bot] - deps: update dependency vite-plus to v0.1.20 (#9340) by @renovate[bot] - enable rollup chunking-form test (#9335) by @IWANABETHATGUY - typo: fix typo in watcher options comment (#9324) by @thescripted ### ❤️ New Contributors * @Kyujenius made their first contribution in [#9262](#9262) * @SAY-5 made their first contribution in [#9296](#9296) * @thescripted made their first contribution in [#9324](#9324) Co-authored-by: shulaoda <[email protected]>
IWANABETHATGUY
pushed a commit
that referenced
this pull request
May 18, 2026
We already implemented single file optional chaining inline in Oxc oxc-project/oxc#21834 ## Summary Teaches the cross-module enum inliner to fold optional-chain access (`E?.x`, `E?.['x']`) to the same literal as `E.x` / `E['x']`. With that hole filled, the tree-shaker bypass added in #9229 no longer needs its `!prop.optional` guard. ## Why this is safe Enum bindings are always defined — the generated IIFE initializes them to `{}` (never null/undefined), and const enums are removed entirely after inlining. So `?.` cannot short-circuit on an enum object: `E?.x` is observationally equivalent to `E.x`. The TDZ assumption this relies on (an enum identifier reaching the finalizer is always bound to a real value) is the same one `E.x` inlining already makes — we just extend it to the optional form. ## What changed - `crates/rolldown/src/module_finalizers/mod.rs` — `try_inline_enum_access` now also matches `ChainExpression { StaticMemberExpression | ComputedMemberExpression }`. - `crates/rolldown/src/module_finalizers/impl_visit_mut.rs` — pre-match inline attempt for `ChainExpression`, done before the main `visit_expression` dispatch so `expr` can be passed to `try_inline_enum_access` without conflicting with the existing arm's mutable destructure of the inner chain. - `crates/rolldown/src/stages/link_stage/tree_shaking/include_statements.rs` — drops the `!prop.optional` guard from the enum-decl bypass. `!is_write` stays. - `crates/rolldown/tests/esbuild/ts/ts_enum_cross_module_inlining_access/` — moves the optional cases into the `inlined` group; snapshot now folds all 8 inlinable accesses to literals while keeping `e_num`/`e_str` (bare identifier references). ## Test plan - [x] `cargo test -p rolldown --test integration ts_enum_cross_module_inlining_access` - [x] `cargo test -p rolldown` (1703 passed, 0 failed) - [x] `cargo test -p rolldown --test integration enum` (24 passed) - [x] `cargo test -p rolldown --test integration chain` (25 passed) - [x] Spot-checked single-module `enum E { x = 1 }; console.log(E?.x)` via CLI — now folds to `console.log(1)` - [x] `just ued` — produced no additional snap-diff changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 <[email protected]>
IWANABETHATGUY
pushed a commit
that referenced
this pull request
May 18, 2026
## [1.0.1] - 2026-05-13 ### 🚀 Features - experimental/lazy-barrel: advice on oversized barrel modules (#9236) by @shulaoda - rolldown: inline optional-chain enum access (#9379) by @Dunqing - chunk-optimization: dedupe already-loaded dynamic deps (#9305) by @IWANABETHATGUY - binding: call moduleParsed hook in ParallelJsPlugin (#9318) by @jaehafe ### 🐛 Bug Fixes - transform: enable `enum_eval` for `transformSync` and vite TS transform (#9325) by @Dunqing - error: remove severity prefix from diagnostic messages (#9262) by @Kyujenius - deps: pin pnpm to 10.23.0 to work around catalog mismatch on Netlify (#9364) by @shulaoda - ci: pin mimalloc-safe to 0.1.58 (#9361) by @shulaoda - dev/lazy: fix exports of lazy requests in lazy chunks (#9249) by @h-a-n-a - rolldown_plugin_vite_resolve: handle errors in `resolveSubpathImports` callback (#9355) by @sapphi-red - rolldown_plugin_lazy_compilation: use loadExports for fetched proxy to preserve original export names (#9132) by @h-a-n-a - common: include offending index in HybridIndexVec panic message (#9296) by @SAY-5 ### 🚜 Refactor - ecmascript: extract semantic_builder_for_transform helper (#9326) by @Dunqing - test: extract reusable static-import-cycle helper (#9332) by @IWANABETHATGUY ### 📚 Documentation - clarify scope of `topLevelVar` (#9380) by @IWANABETHATGUY - meta/design: add ast-mutation design doc (#9338) by @hyf0 - feat: add ai policy in contribution guide (#9315) by @mdong1909 ### ⚡ Performance - binding: enable mimalloc v3 to reduce idle memory (#9349) by @shulaoda ### 🧪 Testing - mcs: cover require() in `$initial` group (#9376) by @hyf0 - add regression for CJS facade chunk merge into entry (#9351) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - switch prepare-release to manual dispatch with version input (#9383) by @shulaoda - migrate `@rolldown/pluginutils` to `rolldown/plugins` (#9317) by @shulaoda - deps: pin libmimalloc-sys2 to 0.1.54 (#9372) by @shulaoda - replace `igorskyflyer/action-readfile` with `cat` (#9369) by @sapphi-red - deps: update test262 submodule for tests (#9371) by @rolldown-guard[bot] - use app token for test dep update PRs (#9368) by @sapphi-red - replace some actions with gh commands (#9367) by @sapphi-red - replace action-semantic-pull-request with inline regex (#9366) by @sapphi-red - remove pull_request_target workflows (#9188) by @Boshen - deps: upgrade oxc to 0.130.0 (#9360) by @shulaoda - deps: update github actions (major) (#9348) by @renovate[bot] - deps: update github actions (#9341) by @renovate[bot] - deps: update rust crates (#9344) by @renovate[bot] - deps: update crate-ci/typos action to v1.46.1 (#9357) by @renovate[bot] - deps: update npm packages (#9343) by @renovate[bot] - deps: update pnpm to v10.33.4 (#9347) by @renovate[bot] - deps: update dependency rolldown-plugin-dts to ^0.25.0 (#9346) by @renovate[bot] - .claude: add rolldown-repl encoder, rename decode skill (#9352) by @IWANABETHATGUY - deps: update crate-ci/typos action to v1.46.0 (#9345) by @renovate[bot] - deps: update napi to v3.8.6 (#9342) by @renovate[bot] - deps: update dependency vite-plus to v0.1.20 (#9340) by @renovate[bot] - enable rollup chunking-form test (#9335) by @IWANABETHATGUY - typo: fix typo in watcher options comment (#9324) by @thescripted ### ❤️ New Contributors * @Kyujenius made their first contribution in [#9262](#9262) * @SAY-5 made their first contribution in [#9296](#9296) * @thescripted made their first contribution in [#9324](#9324) Co-authored-by: shulaoda <[email protected]>
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.
We already implemented single file optional chaining inline in Oxc oxc-project/oxc#21834
Summary
Teaches the cross-module enum inliner to fold optional-chain access (
E?.x,E?.['x']) to the same literal asE.x/E['x']. With that hole filled, the tree-shaker bypass added in #9229 no longer needs its!prop.optionalguard.Why this is safe
Enum bindings are always defined — the generated IIFE initializes them to
{}(never null/undefined), and const enums are removed entirely after inlining. So?.cannot short-circuit on an enum object:E?.xis observationally equivalent toE.x.The TDZ assumption this relies on (an enum identifier reaching the finalizer is always bound to a real value) is the same one
E.xinlining already makes — we just extend it to the optional form.What changed
crates/rolldown/src/module_finalizers/mod.rs—try_inline_enum_accessnow also matchesChainExpression { StaticMemberExpression | ComputedMemberExpression }.crates/rolldown/src/module_finalizers/impl_visit_mut.rs— pre-match inline attempt forChainExpression, done before the mainvisit_expressiondispatch soexprcan be passed totry_inline_enum_accesswithout conflicting with the existing arm's mutable destructure of the inner chain.crates/rolldown/src/stages/link_stage/tree_shaking/include_statements.rs— drops the!prop.optionalguard from the enum-decl bypass.!is_writestays.crates/rolldown/tests/esbuild/ts/ts_enum_cross_module_inlining_access/— moves the optional cases into theinlinedgroup; snapshot now folds all 8 inlinable accesses to literals while keepinge_num/e_str(bare identifier references).Test plan
cargo test -p rolldown --test integration ts_enum_cross_module_inlining_accesscargo test -p rolldown(1703 passed, 0 failed)cargo test -p rolldown --test integration enum(24 passed)cargo test -p rolldown --test integration chain(25 passed)enum E { x = 1 }; console.log(E?.x)via CLI — now folds toconsole.log(1)just ued— produced no additional snap-diff changes🤖 Generated with Claude Code