feat(transformer/typescript): debug_assert that enum_eval ran in semantic (#22252)

Closes #21667.

Stacked on #22253 (which lands the prerequisite `with_enum_eval(true)` calls in `cargo allocs` and the four transformer benches that this assert would otherwise trip).

## Problem

The TS enum lowering reads pre-computed member values from `Scoping`. Those values are only populated when `SemanticBuilder::with_enum_eval(true)` is set; without it, every `get_enum_member_value` call returns `None`, so string-valued members are treated as opaque expressions and the transform emits incorrect reverse mappings.

Example from #21667:

```ts
enum Theme {
  Light = "Light",
  Dark = "Dark",
  Default = Theme.Light,
}
```

When the transformer runs on a `Scoping` built without `enum_eval`, it does not recognise `Theme.Light` as a string and produces:

```js
Theme[Theme["Default"] = Theme.Light] = "Default";
```

— i.e. a reverse-mapping assignment for a string member, which is wrong (and runtime-observable).

## Fix

Add a `debug_assert!` at the entry of `transform_ts_enum` (after the `decl.declare` early return) verifying that the enum's body scope is registered in `Scoping`.

The check uses `get_enum_body_scopes` as the signal: `add_enum_body_scope` has exactly one caller (`evaluate_enum_members`), which is gated entirely by `enum_eval`. No new state on `Scoping` / `EnumData`.

The internal `Compiler` (`crates/oxc/src/compiler.rs`) and the playground (`napi/playground/src/lib.rs`) already enable `enum_eval`. The benches and `cargo allocs` are fixed in the parent PR. This guard catches the misuse for any other external caller of `Transformer::build_with_scoping`.

## Test plan

- [x] `cargo test -p oxc_transformer` — new integration tests in `tests/integrations/enum_eval.rs` cover both the positive case and the `#[should_panic]` case (debug-only)
- [x] `cargo run -p oxc_transform_conformance` — no snapshot changes (the conformance runner goes through `CompilerInterface`, which already enables `enum_eval`)
- [x] `cargo clippy -p oxc_transformer --tests` — clean
This commit is contained in:
Dunqing committed 2026-05-10 09:36:16 +00:00
1 parent 31a5de7991
commit 66c9b01c83
6 files changed
+51 -8

No files matched your search

@@ -160,6 +160,17 @@ impl<'a> TypeScriptEnum {
return None;
}
// Enum lowering relies on pre-computed member values stored in `Scoping`
// by `evaluate_enum_members` (only run when the semantic builder is configured
// with `enum_eval`). Without it, string-valued members are not recognized and
// the transform emits incorrect reverse mappings (see oxc#21667).
debug_assert!(
ctx.scoping().get_enum_body_scopes(decl.id.symbol_id()).is_some(),
"Transformer requires `Scoping` produced with `SemanticBuilder::with_enum_eval(true)` \
to correctly transform `enum {}`.",
decl.id.name,
);
let ast = ctx.ast;
let is_export = export_span.is_some();
@@ -0,0 +1,31 @@
use std::path::Path;
use oxc_allocator::Allocator;
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use oxc_transformer::{TransformOptions, Transformer};
fn run(enum_eval: bool) {
let source_text = "enum E { A = 1 }";
let source_type = SourceType::ts();
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let mut program = ret.program;
let scoping =
SemanticBuilder::new().with_enum_eval(enum_eval).build(&program).semantic.into_scoping();
let _ret = Transformer::new(&allocator, Path::new(""), &TransformOptions::default())
.build_with_scoping(scoping, &mut program);
}
#[test]
fn transforms_enum_when_enum_eval_enabled() {
run(true);
}
#[test]
#[should_panic(expected = "SemanticBuilder::with_enum_eval(true)")]
#[cfg(debug_assertions)]
fn panics_when_enum_eval_disabled() {
run(false);
}
@@ -1,3 +1,4 @@
mod enum_eval;
mod es_target;
mod helper_call;
mod targets;
@@ -1,6 +1,6 @@
File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes
-------------------------------------------------------------------------------------------------------------------------------------------
checker.ts | 2.92 MB || 286 | 37 || 152712 | 28244
checker.ts | 2.92 MB || 286 | 37 || 152659 | 28244
cal.com.tsx | 1.06 MB || 20214 | 46 || 37153 | 4570
@@ -10,5 +10,5 @@ pdf.mjs | 567.30 kB || 4052 | 417 |
antd.js | 6.69 MB || 7702 | 1657 || 331684 | 69349
binder.ts | 193.08 kB || 66 | 23 || 7083 | 824
binder.ts | 193.08 kB || 66 | 23 || 7075 | 824
@@ -1,8 +1,8 @@
File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes
-------------------------------------------------------------------------------------------------------------------------------------------
checker.ts | 2.92 MB || 2281 | 18 || 0 | 0
checker.ts | 2.92 MB || 2309 | 18 || 0 | 0
cal.com.tsx | 1.06 MB || 14966 | 22 || 0 | 0
cal.com.tsx | 1.06 MB || 14978 | 22 || 0 | 0
RadixUIAdoptionSection.jsx | 2.52 kB || 10 | 3 || 0 | 0
@@ -10,5 +10,5 @@ pdf.mjs | 567.30 kB || 1401 | 208 |
antd.js | 6.69 MB || 2455 | 18 || 0 | 0
binder.ts | 193.08 kB || 190 | 11 || 0 | 0
binder.ts | 193.08 kB || 196 | 11 || 0 | 0
@@ -1,8 +1,8 @@
File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes
-------------------------------------------------------------------------------------------------------------------------------------------
checker.ts | 2.92 MB || 159 | 1 || 4765 | 64
checker.ts | 2.92 MB || 20 | 1 || 4400 | 64
cal.com.tsx | 1.06 MB || 37 | 3 || 38190 | 3515
cal.com.tsx | 1.06 MB || 26 | 3 || 38212 | 3515
RadixUIAdoptionSection.jsx | 2.52 kB || 22 | 3 || 259 | 24
@@ -10,5 +10,5 @@ pdf.mjs | 567.30 kB || 15 | 0 |
antd.js | 6.69 MB || 15 | 0 || 2 | 0
binder.ts | 193.08 kB || 30 | 1 || 367 | 6
binder.ts | 193.08 kB || 18 | 1 || 379 | 6