Skip to content

feat: const enum cross-module inlining support#8796

Merged
IWANABETHATGUY merged 1 commit intomainfrom
oxc/const-enum
Apr 15, 2026
Merged

feat: const enum cross-module inlining support#8796
IWANABETHATGUY merged 1 commit intomainfrom
oxc/const-enum

Conversation

@Dunqing
Copy link
Copy Markdown
Collaborator

@Dunqing Dunqing commented Mar 19, 2026

Summary

Enable cross-module enum inlining by consuming pre-computed enum member values from oxc_semantic.

Depends on: oxc-project/oxc#20652

closes #4342

What changed

  • Extract enum values from initial Scoping before the transformer converts enums to IIFEs/placeholders
  • Store on EcmaView as enum_member_value_map (name-keyed, survives transformer rewrites)
  • Inline enum member accesses in the scope hoisting finalizer:
    • Direction.Up0 (dot notation)
    • Direction["Up"]0 (bracket notation)
    • ns.Direction.Up0 (chained namespace access)
  • Tree-shake dead enum declarations when all references are inlined member accesses
  • Supports both const enum and regular enum when all members have statically known values
  • Enables oxc's optimize_const_enums and optimize_enums transformer options

Architecture

The enum inlining pipeline follows Rolldown's existing module processing stages:

┌─ Scan Stage (per module) ──────────────────────────────────────────┐
│                                                                     │
│  pre_process_ecma_ast.rs                                           │
│  ├─ Step 1: SemanticBuilder → Scoping                              │
│  ├─ Step 1.5: Extract enum_member_value_map from Scoping           │
│  │            (before transformer destroys enum declarations)       │
│  ├─ Step 3: Transformer (optimize_const_enums + optimize_enums)    │
│  │           const enums → removed, regular enums → @__PURE__ IIFE │
│  └─ Returns ParseToEcmaAstResult { enum_member_value_map, ... }    │
│                                                                     │
│  ecma_module_view_factory.rs                                        │
│  ├─ AstScanner: scans AST for imports/exports/statements           │
│  └─ enum_member_value_map stored directly on EcmaView              │
│     (bypasses AstScanner — no symbol-level processing needed)       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

┌─ Link Stage ───────────────────────────────────────────────────────┐
│                                                                     │
│  include_statements.rs (tree-shaking)                               │
│  ├─ Computes has_enum_inlining once for the whole bundle           │
│  └─ For member expr refs (e.g. B.member):                          │
│     if member exists in enum_member_value_map → skip including     │
│     the enum declaration (it will be inlined, declaration is dead)  │
│                                                                     │
│  has_enum_inlining stored on LinkStageOutput for generate stage    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

┌─ Generate Stage ───────────────────────────────────────────────────┐
│                                                                     │
│  ScopeHoistingFinalizer (impl_visit_mut.rs + mod.rs)               │
│  ├─ visit_expression: try_inline_enum_access() before other        │
│  │   member expr rewrites                                          │
│  ├─ After namespace rewrite (ns.E → E): retry enum inlining       │
│  └─ try_inline_enum_member_by_ref: canonical_ref → owner module   │
│     → enum_member_value_map → literal value                        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Comparison with other tools

Feature Rolldown (this PR) esbuild TypeScript (tsc)
Const enum inlining Yes Yes Yes (same-file only with --isolatedModules)
Regular enum inlining Yes Yes No
Cross-module const enum Yes Yes No (with --isolatedModules)
Optional chaining (E?.X) No No No
Computed access (E["X"]) Yes Yes Yes
Namespace chains (ns.E.X) Yes Yes N/A
Tree-shake dead enum decls Yes Yes No
Merged/sibling enums Yes Yes Yes

Performance

  • has_enum_inlining flag computed once during link stage, reused by generate stage
  • Fast-path skip in visitor hot loop for enum-free bundles

Test plan

  • ts_enum_cross_module_inlining_access — direct + computed member access inlined
  • ts_enum_cross_module_inlining_edge_cases — renamed re-exports, namespace re-exports, mixed value types, const vs regular
  • ts_enum_cross_module_inlining_definitions — enum definitions handled correctly
  • ts_enum_cross_module_inlining_re_export — chained ns.Enum.Member inlined
  • ts_enum_cross_module_tree_shaking — dead enum declarations removed
  • ts_enum_same_module_inlining_access — same-module accesses inlined
  • ts_const_enum_comments — const enum member accesses inlined
  • ts_sibling_enum — merged enum declarations resolved correctly
  • cross_module_constant_folding_* — number, string, computed property name folding
  • Full enum test suite: 15 passed, 0 failed

Known limitations

  • Optional chaining on enums (E?.X) not inlined (matches esbuild behavior)
  • Comment annotations (/* Direction.Up */) on inlined values not yet implemented

🤖 Generated with Claude Code

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 19, 2026

Deploy Preview for rolldown-rs canceled.

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

@Dunqing Dunqing marked this pull request as draft March 19, 2026 13:01
@Dunqing Dunqing force-pushed the oxc/const-enum branch 8 times, most recently from c9010aa to 5fee816 Compare March 23, 2026 04:57
@Boshen Boshen unassigned hyf0 Mar 31, 2026
@Dunqing Dunqing force-pushed the oxc/const-enum branch 2 times, most recently from 5f0725e to 20b5750 Compare April 3, 2026 01:30
@Dunqing
Copy link
Copy Markdown
Collaborator Author

Dunqing commented Apr 3, 2026

All tests passed locally; I think it is ready for review for this round. cc @IWANABETHATGUY

Comment thread crates/rolldown/src/stages/link_stage/tree_shaking/include_statements.rs Outdated
Copy link
Copy Markdown
Member

@hyf0 hyf0 left a comment

Choose a reason for hiding this comment

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

LGTM. Very good job!

Comment thread crates/rolldown/src/module_finalizers/finalizer_context.rs
graphite-app bot pushed a commit to oxc-project/oxc that referenced this pull request Apr 13, 2026
… enum inlining (#20539)

This is required in Rolldown to align with `esbuild`, which could inline a regular enum as long as all its enum members can be evaluated. See rolldown/rolldown#8796
## Summary

Depends on #20508.

- Add `optimize_enums` option to `TypeScriptOptions` that treats regular enums with all-evaluable members the same as const enums: inlines member accesses and removes non-exported declarations
- Regular enums are only removed when no runtime value references remain (e.g., `typeof Foo`, passing as argument)
- Includes 14 conformance test fixtures covering basic, string values, binary expressions, cross-member/cross-enum references, merged enums, exported (kept), non-evaluable (kept), template literals, unary expressions, typeof/value-usage/passed-as-argument (kept), and re-exported (kept)

## Test plan

- [x] 14 new conformance test fixtures under `optimize-enums/`
- [x] `cargo run -p oxc_transform_conformance` passes
- [x] `cargo test -p oxc_transformer` passes
@Dunqing Dunqing marked this pull request as ready for review April 15, 2026 08:41
@Dunqing Dunqing requested review from IWANABETHATGUY and hyf0 April 15, 2026 08:41
@Dunqing
Copy link
Copy Markdown
Collaborator Author

Dunqing commented Apr 15, 2026

READY FOR REVIEW NOW

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 15, 2026

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing oxc/const-enum (c682675) with main (ec42e53)

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.

Inline TypeScript const enum and regular enum member accesses across
module boundaries so `Direction.Up` becomes the literal value at the
call site, matching TypeScript's const enum semantics.

- Extract enum member values in `pre_process_ecma_ast` via
  `SemanticBuilder::with_enum_eval(true)`, before the transformer
  rewrites or removes enum declarations.
- Thread the name-keyed `enum_member_value_map` through
  `ParseToEcmaAstResult` → `EcmaView` → finalizer context.
- In `ScopeHoistingFinalizer`, inline `E.M`, `ns.E.M`, and `E["M"]`
  accesses by looking up the enum's owning module. A bundle-level
  `has_enum_inlining` flag (computed once in `include_statements`)
  keeps the visitor's hot path free of work when no enums exist.
- Skip including the enum declaration during tree-shaking when its
  only references are inlinable member accesses.
- Enable oxc's `optimize_const_enums` and `optimize_enums` so
  non-exported const enum declarations are stripped and regular
  enum IIFEs carry `@__PURE__` for dead-code elimination.
@IWANABETHATGUY IWANABETHATGUY merged commit a52d258 into main Apr 15, 2026
54 of 55 checks passed
@IWANABETHATGUY IWANABETHATGUY deleted the oxc/const-enum branch April 15, 2026 14:19
@github-actions github-actions bot mentioned this pull request Apr 16, 2026
shulaoda added a commit that referenced this pull request Apr 16, 2026
## [1.0.0-rc.16] - 2026-04-16

### 🚀 Features

- const enum cross-module inlining support (#8796) by @Dunqing
- implement module tagging system for code splitting (#9045) by @hyf0

### 🐛 Bug Fixes

- rolldown_plugin_vite_manifest: handle duplicate chunk names for CSS entries (#9059) by @sapphi-red
- improve error message for invalid return values in function options (#9125) by @shulaoda
- await async export-star init wrappers (#9101) by @thezzisu
- never panic during diagnostic emission (#9091) by @IWANABETHATGUY
- include array rest pattern in binding_identifiers (#9112) by @IWANABETHATGUY
- rolldown: set worker thread count with ROLLDOWN_WORKER_THREADS (#9086) by @fpotter
- rolldown_plugin_lazy_compilation: escape request ID in proxy modules (#9102) by @h-a-n-a
- treat namespace member access as side-effect-free (#9099) by @IWANABETHATGUY
- relax overly conservative side-effect leak check in chunk optimizer (#9085) by @IWANABETHATGUY
- runtime: release `cb` reference after `__commonJS` factory initialization (#9067) by @hyf0-agent
- `@__NO_SIDE_EFFECTS__` wrapper should not remove dynamic imports (#9075) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use POSIX path join/normalize for glob resolution (#9077) by @shulaoda
- emit REQUIRE_TLA error when require() loads a module with top-level await (#9071) by @jaehafe
- emit namespace declaration for empty modules in manual chunks (#8993) by @privatenumber
- rolldown_plugin_vite_import_glob: keep common base on path segment boundary (#9070) by @shulaoda
- prevent circular runtime helper imports during facade elimination (#8989) (#9057) by @IWANABETHATGUY
- correct circular dependency check in facade elimination (#9047) by @h-a-n-a
- docs: correct dead link in CodeSplittingGroup.tags JSDoc (#9051) by @hyf0
- emit DUPLICATE_SHEBANG warning when banner contains shebang (#9026) by @IWANABETHATGUY

### 🚜 Refactor

- use semantic reference flags for member write detection (#9060) by @Dunqing
- extract UsedSymbolRefs newtype wrapper (#9130) by @IWANABETHATGUY
- dedupe await wrapping in export-star init emit (#9119) by @IWANABETHATGUY
- calculate side-effect-free function symbols on demand (#9120) by @IWANABETHATGUY
- extract duplicated top-level await handling into shared helper (#9087) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use split_first for get_common_base (#9069) by @shulaoda
- simplify ESM init deduplication with idiomatic insert check (#9044) by @IWANABETHATGUY

### 📚 Documentation

- document runtime module placement strategy in code-splitting design (#9062) by @IWANABETHATGUY
- clarify `options` hook behavior difference with Rollup in watch mode (#9053) by @sapphi-red
- meta/design: introduce module tags (#9017) by @hyf0

### ⚡ Performance

- convert `generate_transitive_esm_init` to iterative (#9046) by @IWANABETHATGUY

### 🧪 Testing

- merge strict/non_strict test variants using configVariants (#9089) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- disable Renovate auto-updates for oxc packages (#9129) by @IWANABETHATGUY
- upgrade oxc@0.126.0 (#9127) by @Dunqing
- deps: update napi to v3.8.5 (#9126) by @renovate[bot]
- deps: update dependency @napi-rs/cli to v3.6.2 (#9123) by @renovate[bot]
- move lazy-compilation design doc (#9117) by @h-a-n-a
- deps: update dependency vite-plus to v0.1.18 (#9118) by @renovate[bot]
- deps: update dependency vite-plus to v0.1.17 (#9113) by @renovate[bot]
- deps: update oxc to v0.125.0 (#9094) by @renovate[bot]
- deps: update dependency follow-redirects to v1.16.0 [security] (#9103) by @renovate[bot]
- deps: update test262 submodule for tests (#9097) by @sapphi-red
- deps: update crate-ci/typos action to v1.45.1 (#9096) by @renovate[bot]
- deps: update rust crates (#9081) by @renovate[bot]
- deps: update npm packages (#9080) by @renovate[bot]
- remove outdated TODO in determine_module_exports_kind (#9072) by @jaehafe
- rust/test: support `extendedTests: false` shorthand in test config (#9050) by @hyf0
- ci: extract shared infra-changes anchor in path filters (#9054) by @hyf0
- add docs build check to catch dead links in PRs (#9052) by @hyf0

### ❤️ New Contributors

* @thezzisu made their first contribution in [#9101](#9101)
* @fpotter made their first contribution in [#9086](#9086)
* @jaehafe made their first contribution in [#9071](#9071)
* @privatenumber made their first contribution in [#8993](#8993)

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.

[Feature Request]: Support const enum

3 participants