Skip to content

fix: preserve optional chaining in namespace member expr rewrite#8712

Merged
graphite-app[bot] merged 1 commit intomainfrom
copilot/fix-optional-chaining-bug
Mar 16, 2026
Merged

fix: preserve optional chaining in namespace member expr rewrite#8712
graphite-app[bot] merged 1 commit intomainfrom
copilot/fix-optional-chaining-bug

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 15, 2026

Summary

  • Fixes namespace member expression rewriting to preserve optional chaining on unresolved runtime properties while still allowing full tree-shaking through nested namespace chains.
  • Introduces MemberExprProp struct to propagate each property's optional flag through the scanner, linker, and finalizer pipeline.
  • Wraps rewritten expressions in ChainExpression when the result contains optional member accesses.

Closes #8710

Test plan

  • optional_chain_namespace test: h.app?.user?.name preserves ?. after h.app is inlined
  • optional_chainning_namespace test: foo?.bar?.value through nested namespaces tree-shakes to console.log("used")
  • optional_chainning_namespace_2 test: mixed namespace + runtime optional chain
  • Full integration suite (1642 tests pass, 0 failures)
  • Clippy clean

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 15, 2026

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit 2a906e6
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69b764b3a2e94f0007873c3b

Copilot AI changed the title [WIP] Fix optional chaining stripping bug in namespace member expressions fix: preserve optional chaining when rewriting namespace member expressions Mar 15, 2026
Copilot AI requested a review from hyf0 March 15, 2026 14:48
Copilot finished work on behalf of hyf0 March 15, 2026 14:48
@hyf0 hyf0 assigned IWANABETHATGUY and unassigned hyf0 and Copilot Mar 15, 2026
@hyf0 hyf0 marked this pull request as ready for review March 15, 2026 14:54
Copilot AI review requested due to automatic review settings March 15, 2026 14:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a correctness bug where optional chaining (?.) could be stripped during namespace member-expression rewriting (e.g. ns.app?.user?.name), causing runtime TypeErrors. This change adjusts AST scanning so resolution stops before optional boundaries, preserving ?. semantics while still allowing safe namespace import resolution.

Changes:

  • Update try_extract_parent_static_member_expr_chain to stop collecting a member-expression chain once an optional member expression is encountered past the first property.
  • Add a regression test case under crates/rolldown/tests/rolldown/issues/optional_chain_namespace/ that asserts the bundle executes and preserves optional chaining after rewriting.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/rolldown/src/ast_scanner/mod.rs Stops member-chain extraction at optional member expressions (after the first prop) to avoid rewriting across ?. boundaries.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/state.js Test fixture exporting app with a null inner value to exercise optional chaining behavior.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/main.js Regression test asserting optional chaining on a namespace import remains safe after bundling.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/artifacts.snap Snapshot verifying generated output preserves ?. operators.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/_config.json Test configuration enabling execution verification on Node.

You can also share your feedback on Copilot code review. Take the survey.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 15, 2026

Benchmarks Rust

  • target: main(dff6613)
  • pr: copilot/fix-optional-chaining-bug(54c113e)
group                                                        pr                                     target
-----                                                        --                                     ------
bundle/bundle@multi-duplicated-top-level-symbol              1.00     65.0±2.21ms        ? ?/sec    1.00     64.8±2.87ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-sourcemap    1.01     72.9±1.88ms        ? ?/sec    1.00     71.9±1.52ms        ? ?/sec
bundle/bundle@rome_ts                                        1.01    139.3±7.72ms        ? ?/sec    1.00    137.9±2.75ms        ? ?/sec
bundle/bundle@rome_ts-sourcemap                              1.01    158.9±7.78ms        ? ?/sec    1.00    157.8±2.91ms        ? ?/sec
bundle/bundle@threejs                                        1.02     61.4±3.84ms        ? ?/sec    1.00     60.2±2.01ms        ? ?/sec
bundle/bundle@threejs-sourcemap                              1.03     71.8±3.96ms        ? ?/sec    1.00     69.9±1.13ms        ? ?/sec
bundle/bundle@threejs10x                                     1.01    703.4±5.81ms        ? ?/sec    1.00    699.3±9.32ms        ? ?/sec
bundle/bundle@threejs10x-sourcemap                           1.00    804.9±4.85ms        ? ?/sec    1.01    813.1±8.91ms        ? ?/sec

@IWANABETHATGUY IWANABETHATGUY force-pushed the copilot/fix-optional-chaining-bug branch from 7fcd1ce to 9ad21f7 Compare March 15, 2026 15:54
Copy link
Copy Markdown
Member


How to use the Graphite Merge Queue

Add 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.

@IWANABETHATGUY IWANABETHATGUY force-pushed the copilot/fix-optional-chaining-bug branch from 9ad21f7 to bbc9138 Compare March 15, 2026 16:02
@IWANABETHATGUY IWANABETHATGUY marked this pull request as draft March 15, 2026 16:04
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 15, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing copilot/fix-optional-chaining-bug (54c113e) with main (88acaad)2

Open in CodSpeed

Footnotes

  1. 8 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 (dff6613) during the generation of this report, so 88acaad was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@IWANABETHATGUY IWANABETHATGUY force-pushed the copilot/fix-optional-chaining-bug branch from 44cb93c to a44411e Compare March 15, 2026 16:34
@IWANABETHATGUY IWANABETHATGUY changed the title fix: preserve optional chaining when rewriting namespace member expressions fix: preserve optional chaining in namespace member expr rewrite Mar 15, 2026
@IWANABETHATGUY
Copy link
Copy Markdown
Member

The original implementation of Copilot is incorrect, so I rewrote it with CC

@IWANABETHATGUY IWANABETHATGUY marked this pull request as ready for review March 15, 2026 16:38
Copilot AI review requested due to automatic review settings March 15, 2026 16:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a correctness bug in rolldown’s namespace member-expression rewriting so optional chaining (?.) is preserved through the scan → link → finalize pipeline, preventing runtime TypeErrors while still allowing deep namespace-chain tree-shaking (closes #8710).

Changes:

  • Introduce MemberExprProp to carry { name, span, optional } for each property in a member-expression chain.
  • Update AST snippet generation and member-expr resolution to propagate optional flags into rewritten member expressions.
  • Ensure rewritten optional member expressions remain valid JS by keeping/wrapping them in ChainExpression when needed, and add regression tests/snapshots.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/rolldown_ecmascript_utils/src/ast_snippet.rs Build member-expression chains using MemberExprProp.optional instead of always non-optional accesses.
crates/rolldown_common/src/types/member_expr_ref_resolution.rs Store resolved chain props as Vec<MemberExprProp> (instead of (name, span) tuples).
crates/rolldown_common/src/types/member_expr_ref.rs Add MemberExprProp and migrate MemberExprRef to use it.
crates/rolldown_common/src/lib.rs Re-export MemberExprProp for downstream crates.
crates/rolldown/src/stages/link_stage/bind_imports_and_exports.rs Update member-expr resolution logic to use prop.name from MemberExprProp.
crates/rolldown/src/module_finalizers/impl_visit_mut.rs Preserve optional chaining validity by re-wrapping rewritten optional member accesses in ChainExpression.
crates/rolldown/src/ast_scanner/mod.rs Collect member-expression chains as Vec<MemberExprProp> including optional flags.
crates/rolldown/src/ast_scanner/impl_visit.rs Adjust downstream accesses to tuple→struct (prop[0].name, etc.).
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace_2/main.js New tree-shaking regression test covering mixed namespace + runtime optional chain.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace_2/foo.js Fixture for optional chaining tree-shaking test.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace_2/bar.js Fixture exports for tree-shaking verification.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace_2/artifacts.snap Snapshot asserting optional chaining is preserved post-rewrite.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace_2/_config.json Integration test config for the new fixture.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace/main.js New tree-shaking regression test covering nested namespaces with optional chaining.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace/foo.js Fixture re-exporting namespace for tree-shaking case.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace/bar.js Fixture exports for tree-shaking verification.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace/artifacts.snap Snapshot asserting full tree-shaking to the used value.
crates/rolldown/tests/rolldown/tree_shaking/optional_chainning_namespace/_config.json Integration test config for the new fixture.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/state.js New issue repro fixture module exporting an object with null leaf.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/main.js Regression asserting optional chaining is preserved for both static and computed property access.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/artifacts.snap Snapshot verifying rewritten output keeps ?. semantics.
crates/rolldown/tests/rolldown/issues/optional_chain_namespace/_config.json Integration config (node platform + executed expectation) for issue repro.

Copy link
Copy Markdown
Member

IWANABETHATGUY commented Mar 16, 2026

Merge activity

  • Mar 16, 1:58 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.
  • Mar 16, 2:02 AM UTC: IWANABETHATGUY added this pull request to the Graphite merge queue.
  • Mar 16, 2:06 AM UTC: Merged by the Graphite merge queue.

## Summary

- Fixes namespace member expression rewriting to preserve optional chaining on unresolved runtime properties while still allowing full tree-shaking through nested namespace chains.
- Introduces MemberExprProp struct to propagate each property's optional flag through the scanner, linker, and finalizer pipeline.
- Wraps rewritten expressions in ChainExpression when the result contains optional member accesses.

Closes #8710

## Test plan

- [x] optional_chain_namespace test: h.app?.user?.name preserves ?. after h.app is inlined
- [x] optional_chainning_namespace test: foo?.bar?.value through nested namespaces tree-shakes to console.log("used")
- [x] optional_chainning_namespace_2 test: mixed namespace + runtime optional chain
- [x] Full integration suite (1642 tests pass, 0 failures)
- [x] Clippy clean
@graphite-app graphite-app bot force-pushed the copilot/fix-optional-chaining-bug branch from b3fbb28 to 2a906e6 Compare March 16, 2026 02:02
@graphite-app graphite-app bot merged commit 2a906e6 into main Mar 16, 2026
32 checks passed
@IWANABETHATGUY IWANABETHATGUY deleted the copilot/fix-optional-chaining-bug branch March 16, 2026 02:09
This was referenced Mar 18, 2026
shulaoda added a commit that referenced this pull request Mar 18, 2026
## [1.0.0-rc.10] - 2026-03-18

### 🚀 Features

- add indentExclusionRanges property to MagicString (#8746) by @IWANABETHATGUY
- expose `oxcRuntimePlugin` (#8654) by @sapphi-red
- rust: make bundler generic over FileSystem for in-memory benchmarks (#8652) by @Boshen

### 🐛 Bug Fixes

- rolldown_plugin_vite_dynamic_import_vars: align dynamic import fast check with Vite (#8760) by @shulaoda
- renamer: handle existing bindings in nested scopes when finding unique names (#8741) by @drewolson
- pass `yarn_pnp` option where needed (#8736) by @sapphi-red
- preserve optional chaining in namespace member expr rewrite (#8712) by @Copilot
- correct UTF-16 index handling in native MagicString (#8693) by @IWANABETHATGUY
- mark failing doctests as ignore (#8700) by @Boshen
- prevent may_partial_namespace from leaking through include_module (#8682) by @IWANABETHATGUY
- ci: bump native-build cache key to invalidate stale napi-rs artifacts (#8678) by @Boshen
- `comments.annotation: false` breaking tree-shaking (#8657) by @IWANABETHATGUY
- validate filenames for NUL bytes from chunkFileNames/entryFileNames (#8644) by @IWANABETHATGUY
- dce-only minify should not set NODE_ENV to production (#8651) by @IWANABETHATGUY

### 🚜 Refactor

- rust: remove dead `CrossModuleOptimizationConfig::side_effects_free_function_optimization` (#8673) by @Dunqing
- rust: simplify `cross_module_optimization` by removing redundant scope tracking (#8672) by @Dunqing
- simplify string repeat in guess_indentor (#8753) by @IWANABETHATGUY
- consolidate custom magic-string tests into one file (#8696) by @IWANABETHATGUY
- extract CJS bailout checks from include_symbol (#8683) by @IWANABETHATGUY
- rust: remove `BindingIdentifierExt` to use `BindingIdentifier::symbol_id()` instead (#8667) by @Dunqing
- bench: add bench_preset helper and inline presets (#8658) by @Boshen
- rust: filter external modules from entries instead of mapping bit positions (#8637) by @Dunqing

### 📚 Documentation

- clarify watch mode behavior and its limitations (#8751) by @sapphi-red
- add external link icon to GitHub button in Hero section (#8731) by @thisisnkc
- guide: clarify that `inject` option is only conceptually similar to esbuild's one (#8743) by @sapphi-red
- meta/design: add `devtools.md` (#8663) by @hyf0
- add viteplus alpha announcement banner (#8668) by @shulaoda

### ⚡ Performance

- rolldown: some minor perf optimization found by autoresearch (#8730) by @Brooooooklyn
- replace Vec allocation with lazy iterator in find_hash_placeholders (#8703) by @Boshen
- replace TypedDashMap with TypedMap in CustomField (#8708) by @Boshen
- bench: remove scan benchmark binary to halve LTO link time (#8694) by @Boshen

### 🧪 Testing

- watch: increase timeout for error output (#8766) by @sapphi-red
- vite-tests: remove JS plugin tests (#8767) by @sapphi-red
- watch: add CLI exit code test (#8752) by @sapphi-red
- normalize paths on Windows even if `resolve.symlinks` is false (#8483) by @sapphi-red

### ⚙️ Miscellaneous Tasks

- correct comment in bundle-analyzer-plugin.ts (#8770) by @origami-z
- upgrade oxc to 0.120.0 (#8764) by @Boshen
- enable all test for `reset` category in MagicString.test.ts (#8749) by @IWANABETHATGUY
- deps: update test262 submodule for tests (#8742) by @sapphi-red
- deps: update oxc apps (#8734) by @renovate[bot]
- deps: update softprops/action-gh-release action to v2.6.1 (#8724) by @renovate[bot]
- deps: update npm packages (major) (#8722) by @renovate[bot]
- deps: update github-actions (major) (#8721) by @renovate[bot]
- deps: update softprops/action-gh-release action to v2.6.0 (#8720) by @renovate[bot]
- deps: update npm packages (#8718) by @renovate[bot]
- deps: update rust crates (#8717) by @renovate[bot]
- deps: update github-actions (#8716) by @renovate[bot]
- deps: update dependency oxlint-tsgolint to v0.17.0 (#8713) by @renovate[bot]
- deps: bump cargo-shear to v1.11.2 (#8711) by @Boshen
- use org level `CODE_OF_CONDUCT.md` (#8706) by @sapphi-red
- fix cache key mismatch and remove redundant cache saves (#8695) by @Boshen
- deps: update oxc apps (#8692) by @renovate[bot]
- deps: update oxc apps (#8649) by @renovate[bot]
- should do matrix out side of reusable workflows 2 (#8691) by @hyf0
- should do matrix out side of reusable workflows (#8690) by @hyf0
- deps: update dependency rolldown-plugin-dts to v0.22.5 (#8689) by @renovate[bot]
- upgrade oxc to 0.119.0 and oxc_resolver to 11.19.1 (#8686) by @Boshen
- correct if condition of `type-check` job (#8677) by @hyf0
- Gate CI type-check job on node changes (#8669) by @Copilot
- benchmark: improve codspeed build (#8665) by @Boshen
- deps: update oxc to v0.118.0 (#8650) by @renovate[bot]
- deps: update crate-ci/typos action to v1.44.0 (#8647) by @renovate[bot]
- deps: update oxc resolver to v11.19.1 (#8646) by @renovate[bot]
- deps: update dependency rust to v1.94.0 (#8648) by @renovate[bot]
- deps: update dependency rolldown-plugin-dts to v0.22.4 (#8645) by @renovate[bot]

### ◀️ Revert

- Revert "ci: Gate CI type-check job on node changes" (#8674) by @hyf0
- "chore(deps): update dependency rust to v1.94.0 (#8648)" (#8660) by @shulaoda

### ❤️ New Contributors

* @origami-z made their first contribution in [#8770](#8770)
* @drewolson made their first contribution in [#8741](#8741)
* @thisisnkc made their first contribution in [#8731](#8731)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
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.

[Bug]: Optional chaining (?.) stripped when rewriting namespace member expressions

5 participants