52### Error Handling
53- Non-null assertions (`!` in TS) -> `.unwrap()` or similar panic
54▶- `CompilerError.invariant()`, `CompilerError.throwTodo()`, `throw` -> `Result<_, CompilerDiagnostic>` with `Err(...)`
55- `pushDiagnostic()` with invariant errors -> `return Err(...)`
56- `env.recordError()` or non-invariant `pushDiagnostic()` -> accumulate on `Environment` (keep as-is)
1033. Check for `Impure`, `Render`, `Capture` effects on instructions
1044. The pass ordering in `Pipeline.ts` shows when effects are populated vs validated
105▶5. Todo errors indicate unsupported but known patterns; Invariant errors indicate unexpected states
106
107## Important Reminders
91...
92AnalyseFunctions: partial (1700/1717)
93▶InferMutationAliasingEffects: todo
94...
95
· · ·
112- `complete (N/N)` — all tests passing through this pass
113- `partial (passed/total)` — some test failures remain
114▶- `todo` — not yet ported
115
116Update the Status section after every test run to reflect the latest results.
13 - `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering
14 - `src/__tests__/fixtures/compiler/` - Test fixtures
15▶ - `error.todo-*.js` - Unsupported feature, correctly throws Todo error (graceful bailout)
16 - `error.bug-*.js` - Known bug, throws wrong error type or incorrect behavior
17 - `*.expect.md` - Expected output for each fixture
· · ·
268
269**Error categories:**
270▶- `CompilerError.throwTodo()` — Unsupported but known pattern. Graceful bailout. Can be caught by `tryRecord()`.
271- `CompilerError.invariant()` — Truly unexpected/invalid state. Always throws immediately, never caught by `tryRecord()`.
272- Non-`CompilerError` exceptions — Always re-thrown.
1▶# Rust port: e2e parity TODO
2
3Status snapshot (after the current stack lands):
· · ·
36 local or context"). Rust handles the same code without tripping the
37 invariant.
38▶- `error.todo-jsx-intrinsic-tag-matches-local-binding.js` — SWC pipeline
39 emits a Todo bailout (`[hoisting] EnterSSA: Expected identifier to be
40 defined before being used`) that the Babel path does not.
· · ·
39▶ emits a Todo bailout (`[hoisting] EnterSSA: Expected identifier to be
40 defined before being used`) that the Babel path does not.
41- `error.todo-repro-named-function-with-shadowed-local-same-name.js` —
· · ·
41▶- `error.todo-repro-named-function-with-shadowed-local-same-name.js` —
42 Babel errors; SWC compiles.
43- `new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js`
· · ·
43▶- `new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js`
44 — same as above with the new mutation-aliasing model enabled.
45- `error.todo-rust-as-expression-assignment-target.tsx` — Babel errors;
+ 10 more matches in this file
5Create a testing infrastructure that validates the Rust port produces identical results to the TypeScript compiler at every stage of the pipeline. The port proceeds incrementally — one pass at a time — so the test infrastructure must support running the pipeline up to any specified pass and comparing the intermediate state between TS and Rust.
6
7▶**Current status**: M1, M2, M3 implemented. All Rust tests expected to fail (todo!() stubs). Next step: port lower() (M4).
8
9**Known issues — resolved:**
· · ·
422
423- `CompilerError.invariant()` — truly unexpected state
424▶- `CompilerError.throwTodo()` — unsupported but known pattern
425- `CompilerError.throw*()` — other throwing methods
426
· · ·
507### M3: Rust Test Binary Scaffold
508
509▶**Goal**: Scaffold the Rust binary and a `todo!`-only stub for `lower()` so the end-to-end test loop works immediately — even though every test will fail. This validates the full test infrastructure (fixture discovery, Rust binary invocation, diff output) before any real porting begins.
510
5111. **Create the Rust compiler crate** — `compiler/crates/react_compiler/` with the binary target `test-rust-port`. Depends on `react_compiler_ast` for input types.
· · ·
512
513▶2. **Stub `lower()`** — Create a `lower()` function with the correct signature that immediately calls `todo!("lower not yet implemented")`. This means the Rust binary will panic for every fixture, producing a non-zero exit code. The test script treats this as a test failure (expected at this stage).
514
5153. **Stub pipeline** — The `run_pipeline()` function calls the stubbed `lower()` and has placeholder match arms for all other pass names. Every pass beyond `lower()` also hits `todo!()`.
· · ·
515▶3. **Stub pipeline** — The `run_pipeline()` function calls the stubbed `lower()` and has placeholder match arms for all other pass names. Every pass beyond `lower()` also hits `todo!()`.
516
5174. **Implement `debug_hir()`** — Rust debug printer matching the TS format exactly. This won't be exercised until `lower()` is real, but having it in place means the first real pass port immediately produces diffable output.
+ 5 more matches in this file
7The Rust port should be structurally as close to the TypeScript as possible: viewing the TS and Rust side by side, the logic should look, read, and feel similar while working naturally in Rust.
8
9▶**Current status**: M1-M13 fully implemented. All statement types, expression types, destructuring, function expressions, JSX, switch/try-catch, for-of/in, optional chaining, and recursive lowering are complete. No `todo!()` stubs remain. `cargo check` passes. Remaining work: test against fixtures and fix divergences from TypeScript output.
10
11**Known issues to fix:**
· · ·
232Following the port notes:
233- `CompilerError.invariant(cond, ...)` → `if !cond { panic!(...) }` or dedicated `compiler_invariant!` macro
234▶- `CompilerError.throwTodo(...)` → `return Err(CompilerDiagnostic::todo(...))`
235- `builder.recordError(...)` → `builder.record_error(...)` (accumulates on Environment)
236- Non-null assertions (`!`) → `.unwrap()` or `.expect("...")`
· · ·
238The `lower()` function returns `Result<HirFunction, CompilerError>` for invariant/thrown errors, while accumulated errors go to `env.errors`.
239
240▶### 6. `todo!()` Strategy for Incremental Implementation
241
242BuildHIR is too large (4555 lines) for a single implementation pass. Use Rust's `todo!()` macro to stub unimplemented branches:
· · ·
242▶BuildHIR is too large (4555 lines) for a single implementation pass. Use Rust's `todo!()` macro to stub unimplemented branches:
243
244```rust
· · ·
249 ast::Statement::BlockStatement(s) => lower_block_statement(builder, s),
250 // Stubbed — will be filled in later milestones
251▶ ast::Statement::ForStatement(_) => todo!("lower ForStatement"),
252 ast::Statement::WhileStatement(_) => todo!("lower WhileStatement"),
253 ast::Statement::SwitchStatement(_) => todo!("lower SwitchStatement"),
+ 12 more matches in this file
90|---|---|
91| Non-null assertion (`!`) | `.unwrap()` (panic) |
92▶| `CompilerError.invariant()`, `CompilerError.throwTodo()`, `throw ...` | Return `Err(CompilerDiagnostic)` via `Result` |
93| `env.recordError()` or `pushDiagnostic()` with an invariant error | Return `Err(CompilerDiagnostic)` |
94| `env.recordError()` or `pushDiagnostic()` with a NON invariant error | Keep as-is — accumulate on `Environment` |
68### 14. `known_incompatible` not checked for legacy signatures without aliasing config
69- **TS**: `Inference/InferMutationAliasingEffects.ts:2351-2370`
70▶- **Rust**: `infer_mutation_aliasing_effects.rs:2099-2100` — TODO comment, only checked in the `Apply` path with aliasing configs
71- If any legacy signatures (without aliasing configs) have `known_incompatible` set, Rust silently continues.
72
60 * Example: `throw ...` (latent bugs, should have been `invariant`)
61 * Example: `CompilerError.invariant()`
62▶ * Example: `CompilerError.throwTodo()`
63 * Example: `CompilerError.throw*` (other "throw-" methods)
64 * Approach: Make the function return a `Result<_, CompilerDiagnostic>`, and return `Err(...)` with the appropriate compiler error value.
73
74Fixed 4 issues causing the Rust compiler to report 27 errors vs TS's 22 on the
75▶error.todo-missing-source-locations fixture: (1) Don't record the root function node as
76important (TS func.traverse visits descendants only). (2) Use make_var_declarator for
77hoisted scope declarations to reconstruct VariableDeclarator source locations. (3) Pass
· · ·
87traverses both the original Babel AST function and the generated CodegenFunction output,
88comparing source locations for important node types. Code comparison now 1724/1724 (was
89▶1723/1724) since both TS and Rust correctly error on error.todo-missing-source-locations.
90
91## 20260331-210000 Fix function name inference to match TS parent-checking behavior
· · ·
981723/1723 passing.
99
100▶## 20260331-200000 Fix CompilerDiagnostic::todo() to produce ErrorDetail variant
101
102Removed the flat-loc serialization hack from log_error, compiler_error_to_info, and
· · ·
103log_errors_as_events. Instead fixed the root cause: the From<CompilerDiagnostic> for
104▶CompilerError impl now converts Todo-category diagnostics to CompilerErrorOrDiagnostic::ErrorDetail
105(matching TS's CompilerError.throwTodo() → CompilerErrorDetail). Invariant-category
106diagnostics remain as CompilerErrorOrDiagnostic::Diagnostic with sub-details (matching TS).
· · ·
105▶(matching TS's CompilerError.throwTodo() → CompilerErrorDetail). Invariant-category
106diagnostics remain as CompilerErrorOrDiagnostic::Diagnostic with sub-details (matching TS).
1071723/1723 passing.
+ 10 more matches in this file
335 ```
336
337▶3. **Handle `kind: 'ast'`** — keep the TODO error for now (codegen is deferred)
338
3394. **ID normalization** — the existing `normalizeIds` function handles `bb\d+`, `@\d+`, `Identifier(\d+)`, `Type(\d+)`, `\w+\$\d+`, `mutableRange` patterns. Should work for reactive output. Verify after BuildReactiveFunction is ported; may need additional patterns for scope-specific fields in the verbose format.
68**Input/output format**: Define a Rust representation of the Babel AST format using serde with custom serialization/deserialization (ensuring the `"type"` field is always produced, even outside of enum positions). Include full information from Babel, including source locations. A `Scope` type encodes the tree of scope information mapping to Babel's scope tree. The main public API is `compile(BabelAst, Scope) -> Option<BabelAst>`, returning `None` if no changes.
69
70▶**Error handling**: Two categories — errors that would have thrown in TypeScript (invariants, todo errors, short-circuiting) return `Err(CompilerDiagnostic)` via `Result`, while non-throwing accumulated diagnostics are recorded directly on `Environment`. TypeScript non-null assertions become `.unwrap()` panics.
71
72**Note on InferMutationAliasingEffects**: Previously categorized as "significant redesign" due to maps using JS reference identity with `InstructionValue` keys. An upstream refactor ([PR #33650](https://github.com/facebook/react/pull/33650)) replaces `InstructionValue` with interned `AliasingEffect` as allocation-site keys, eliminating synthetic InstructionValues and the `effectInstructionValueCache`. Since effects are already interned by content hash, they map directly to a copyable `EffectId` index in Rust. Additionally, `AliasingEffect` variants share `Place` references with `InstructionValue` fields — in Rust, Places are cloned cheaply (with arena-based `IdentifierId`). The `CreateFunction` variant's `FunctionExpression` reference is replaced with a `FunctionId` referencing the function arena on `Environment`. See [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership) for the full analysis. This is "moderate refactoring" — no algorithmic redesign needed.
· · ·
911|---|---|---|
912| Non-null assertions (`!`) | `value!.field` | Panic via `.unwrap()` or similar |
913▶| Throwing expressions | `throw ...`, `CompilerError.invariant()`, `CompilerError.throwTodo()`, `CompilerError.throw*()` | Make the function return `Result<_, CompilerDiagnostic>`, return `Err(...)` |
914| Non-throwing (invariant) | Local `error` + `error.pushDiagnostic()` where the error IS an invariant | Make the function return `Result<_, CompilerDiagnostic>`, change `pushDiagnostic()` to `return Err(...)` |
915| Non-throwing (non-invariant) | Local `error` + `error.pushDiagnostic()`, `env.recordError()` | Keep as-is — accumulate on environment |
37 - Patterns (object/array): create temporary Place, then emit destructuring assignments
38 - Rest elements: wrap in SpreadPattern
39▶ - Unsupported: emit Todo error
40
413. **Body Processing**:
· · ·
1088. **Unsupported Syntax**: `var` declarations, `with` statements, inline `class` declarations, `eval` - emit appropriate errors
109
110▶## TODOs
111- `returnTypeAnnotation: null, // TODO: extract the actual return type node if present`
112- `TODO(gsn): In the future, we could only pass in the context identifiers that are actually used by this function and its nested functions`
· · ·
111▶- `returnTypeAnnotation: null, // TODO: extract the actual return type node if present`
112- `TODO(gsn): In the future, we could only pass in the context identifiers that are actually used by this function and its nested functions`
113- Multiple `// TODO remove type cast` in destructuring pattern handling
· · ·
112▶- `TODO(gsn): In the future, we could only pass in the context identifiers that are actually used by this function and its nested functions`
113- Multiple `// TODO remove type cast` in destructuring pattern handling
114- `// TODO: should JSX namespaced names be handled here as well?`
· · ·
113▶- Multiple `// TODO remove type cast` in destructuring pattern handling
114- `// TODO: should JSX namespaced names be handled here as well?`
115
+ 1 more matches in this file
65- **Nested functions**: Function expressions and object methods are processed recursively with a temporary predecessor edge linking them to the enclosing block
66
67▶## TODOs
68- `[hoisting] EnterSSA: Expected identifier to be defined before being used` - Handles cases where hoisting causes an identifier to be used before definition (throws a Todo error for graceful bailout)
69
· · ·
68▶- `[hoisting] EnterSSA: Expected identifier to be defined before being used` - Handles cases where hoisting causes an identifier to be used before definition (throws a Todo error for graceful bailout)
69
70## Example
71- `StoreContext` with `Const` kind does propagate the rvalue type to enable ref inference through context variables
72
73▶## TODOs
741. **Hook vs Function type ambiguity**:
75 > "TODO: callee could be a hook or a function, so this type equation isn't correct. We should change Hook to a subtype of Function or change unifier logic."
· · ·
75▶ > "TODO: callee could be a hook or a function, so this type equation isn't correct. We should change Hook to a subtype of Function or change unifier logic."
76
772. **PropertyStore rvalue inference**:
· · ·
78▶ > "TODO: consider using the rvalue type here" - Currently uses a dummy type for PropertyStore to avoid inferring rvalue types from lvalue assignments.
79
80## Example
45- **Immutable captures**: `ImmutableCapture`, `Freeze`, `Create`, `Impure`, `Render` effects do not contribute to marking context variables as `Capture`
46
47▶## TODOs
48- No TODO comments in the pass itself
49
· · ·
48▶- No TODO comments in the pass itself
49
50## Example
1016. **Array.push and Similar**: Uses legacy signature system with `Store` effect on receiver and `Capture` of arguments.
102
103▶## TODOs
104- `// TODO: using InstructionValue as a bit of a hack, but it's pragmatic` - context variable initialization
105- `// TODO: call applyEffect() instead` - try-catch aliasing
· · ·
104▶- `// TODO: using InstructionValue as a bit of a hack, but it's pragmatic` - context variable initialization
105- `// TODO: call applyEffect() instead` - try-catch aliasing
106- `// TODO: make sure we're also validating against global mutations somewhere` - global mutation validation for effects/event handlers
· · ·
105▶- `// TODO: call applyEffect() instead` - try-catch aliasing
106- `// TODO: make sure we're also validating against global mutations somewhere` - global mutation validation for effects/event handlers
107- `// TODO; include "render" here?` - whether to track Render effects in function hasTrackedSideEffects
· · ·
106▶- `// TODO: make sure we're also validating against global mutations somewhere` - global mutation validation for effects/event handlers
107- `// TODO; include "render" here?` - whether to track Render effects in function hasTrackedSideEffects
108- `// TODO: consider using persistent data structures to make clone cheaper` - performance optimization for state cloning
· · ·
107▶- `// TODO; include "render" here?` - whether to track Render effects in function hasTrackedSideEffects
108- `// TODO: consider using persistent data structures to make clone cheaper` - performance optimization for state cloning
109- `// TODO check this` and `// TODO: what kind here???` - DeclareLocal value kinds
+ 2 more matches in this file
113When a phi's predecessor block is controlled by a reactive condition, the phi becomes reactive even if its operands are all non-reactive constants.
114
115▶## TODOs
116No explicit TODO comments are present in the source file. However, comments note:
117
· · ·
116▶No explicit TODO comments are present in the source file. However, comments note:
117
118- **ComputedLoads not handled for stability**: Only PropertyLoad propagates stability from containers, not ComputedLoad. The comment notes this is safe because stable containers have differently-typed elements, but ComputedLoad handling could be added.
114When enabled, phi operands are unconditionally unioned with the phi result (even without mutation after the phi).
115
116▶## TODOs
1171. `// TODO: improve handling of module-scoped variables and globals` - The current approach excludes globals entirely, but a more nuanced handling could be beneficial.
118
· · ·
117▶1. `// TODO: improve handling of module-scoped variables and globals` - The current approach excludes globals entirely, but a more nuanced handling could be beneficial.
118
1192. Known issue with aliasing and mutable lifetimes (from header comments):
72
73### Value Blocks with DCE
74▶There's a TODO for handling reassignment in value blocks where the original declaration was removed by DCE.
75
76### Parameters and Context Variables
· · ·
80`++x` and `x--` always mark the variable as `Let`, even if used inline.
81
82▶## TODOs
83```typescript
84CompilerError.invariant(block.kind !== 'value', {
· · ·
85▶ reason: `TODO: Handle reassignment in a value block where the original
86 declaration was removed by dead code elimination (DCE)`,
87 ...