compiler/docs/rust-port/rust-port-research.md MARKDOWN 1,461 lines View on github.com → Search inside
1# React Compiler: Rust Port Feasibility Research23## Table of Contents451. [Executive Summary](#executive-summary)62. [Key Data Structures](#key-data-structures)73. [The Shared Mutable Reference Problem](#the-shared-mutable-reference-problem)84. [Environment as Shared Mutable State](#environment-as-shared-mutable-state)95. [Side Maps: Passes Storing HIR References](#side-maps-passes-storing-hir-references)106. [AliasingEffect: Shared References and Rust Ownership](#aliasingeffect-shared-references-and-rust-ownership)117. [Recommended Rust Architecture](#recommended-rust-architecture)128. [Input/Output Format](#inputoutput-format)139. [Error Handling](#error-handling)1410. [Structural Similarity: TypeScript  Rust Alignment](#structural-similarity-typescript--rust-alignment)1511. [Pipeline Overview](#pipeline-overview)1612. [Pass-by-Pass Analysis](#pass-by-pass-analysis)17   - [Phase 1: Lowering (AST to HIR)](#phase-1-lowering)18   - [Phase 2: Normalization](#phase-2-normalization)19   - [Phase 3: SSA Construction](#phase-3-ssa-construction)20   - [Phase 4: Optimization (Pre-Inference)](#phase-4-optimization-pre-inference)21   - [Phase 5: Type and Effect Inference](#phase-5-type-and-effect-inference)22   - [Phase 6: Mutation/Aliasing Analysis](#phase-6-mutationaliasing-analysis)23   - [Phase 7: Optimization (Post-Inference)](#phase-7-optimization-post-inference)24   - [Phase 8: Reactivity Inference](#phase-8-reactivity-inference)25   - [Phase 9: Scope Construction](#phase-9-scope-construction)26   - [Phase 10: Scope Alignment and Merging](#phase-10-scope-alignment-and-merging)27   - [Phase 11: Scope Terminal Construction](#phase-11-scope-terminal-construction)28   - [Phase 12: Scope Dependency Propagation](#phase-12-scope-dependency-propagation)29   - [Phase 13: Reactive Function Construction](#phase-13-reactive-function-construction)30   - [Phase 14: Reactive Function Transforms](#phase-14-reactive-function-transforms)31   - [Phase 15: Codegen](#phase-15-codegen)32   - [Validation Passes](#validation-passes)3313. [External Dependencies](#external-dependencies)3414. [Risk Assessment](#risk-assessment)3515. [Recommended Migration Strategy](#recommended-migration-strategy)3637---3839## Executive Summary4041Porting the React Compiler from TypeScript to Rust is **feasible and the Rust code can remain structurally very close to the TypeScript**. The compiler's algorithms are well-suited to Rust. The TypeScript implementation relies on patterns that conflict with Rust's ownership model, but all have clean, well-understood solutions using arenas and indirect references:42431. **Shared Identifier references**: Multiple `Place` objects reference the same `Identifier` object. **Solution**: Arena-allocated identifiers on `Environment`, referenced by copyable `IdentifierId` index.44452. **Shared ReactiveScope references**: Multiple identifiers share the same `ReactiveScope` object (including its mutable range). **Solution**: Arena-allocated scopes on `Environment`, referenced by `ScopeId`.46473. **Inner function storage**: `FunctionExpression`/`ObjectMethod` instructions store inner `HIRFunction` values inline. **Solution**: Arena-allocated functions on `Environment`, referenced by `FunctionId`.48494. **Type storage**: Types stored inline on identifiers. **Solution**: Arena-allocated types on `Environment`, referenced by `TypeId`.50515. **Instructions stored inline in blocks**: `BasicBlock.instructions` stores `Instruction` objects directly. **Solution**: Flat instruction table on `HIRFunction`, referenced by `InstructionId`. The existing `InstructionId` (evaluation order counter) is renamed to `EvaluationOrder` since it applies to both instructions and terminals.52536. **Environment as shared mutable singleton**: The `Environment` object is threaded through the entire compilation via `fn.env` and mutated by many passes. **Solution**: Remove `HIRFunction.env` and pass `env: &mut Environment` separately. Maintain existing fields (no sub-struct grouping) to allow precise sliced borrows via direct field access.5455**Key finding on structural similarity**: After deep analysis of every pass, the vast majority of compiler passes can be ported to Rust with **~85-95% structural correspondence**  meaning you could view the TypeScript and Rust side-by-side and easily trace the logic. The main mechanical differences are:56- `match` instead of `switch` (exhaustive by default in Rust)57- `HashMap<IdentifierId, T>` instead of `Map<Identifier, T>` (reference identity  value identity)58- `Vec::retain()` instead of delete-during-Set-iteration59- `std::mem::replace` / `std::mem::take` for in-place enum variant swaps60- Two-phase collect/apply instead of mutate-through-stored-references6162**Complexity breakdown** (revised after deep per-pass analysis):63- ~25 passes are straightforward to port (simple traversal, local mutation, ID-only side maps)64- ~13 passes require moderate refactoring (stored references  IDs, iteration order changes)65- ~4 passes require significant redesign (InferMutationAliasingRanges, BuildHIR, CodegenReactiveFunction, AnalyseFunctions)66- Input/output boundaries use JSON AST interchange via serde, with a Rust Babel AST type6768**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.6970**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.7172**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.7374---7576## Key Data Structures7778### HIRFunction79```80HIRFunction {81  body: HIR {82    entry: BlockId,83    blocks: Map<BlockId, BasicBlock>    // ordered map, reverse postorder84  },85  instructions: Vec<Instruction>,        // flat instruction table, indexed by InstructionId86  params: Array<Place | SpreadPattern>,87  returns: Place,88  context: Array<Place>,                 // captured variables from outer scope89  aliasingEffects: Array<AliasingEffect> | null,90}91```9293**Note**: `env` is removed from `HIRFunction` and passed separately as `env: &mut Environment`. Inner functions are stored in the function arena on `Environment` (see Recommended Rust Architecture](#recommended-rust-architecture)).9495### BasicBlock96```97BasicBlock {98  id: BlockId,99  kind: 'block' | 'value' | 'loop' | 'sequence' | 'catch',100  instructions: Vec<InstructionId>,      // indices into HIRFunction.instructions101  terminal: Terminal,                    // control flow (goto, if, for, return, etc.)102  preds: Set<BlockId>,103  phis: Set<Phi>,                        // SSA join points104}105```106107### Instruction108```109Instruction {110  order: EvaluationOrder,                // evaluation order (renamed from InstructionId)111  lvalue: Place,                         // destination112  value: InstructionValue,               // discriminated union (~40 variants)113  effects: Array<AliasingEffect> | null, // populated by InferMutationAliasingEffects114  loc: SourceLocation,115}116```117118**Note**: The previous `InstructionId` type is renamed to `EvaluationOrder` because it represents evaluation order and is not instruction-specific (terminals also carry it). A new `InstructionId` type is introduced as an index into the `HIRFunction.instructions` table, allowing passes to reference instructions by a single copyable ID rather than `(BlockId, usize)`.119120### Place (CRITICAL for Rust port)121```122Place {123  kind: 'Identifier',124  identifier: IdentifierId,  // ← index into Identifier arena on Environment (shared reference in TS)125  effect: Effect,             // Read, Mutate, Capture, Freeze, etc.126  reactive: boolean,          // set by InferReactivePlaces127  loc: SourceLocation,128}129```130131### Identifier (CRITICAL for Rust port)132```133Identifier {134  id: IdentifierId,           // unique after SSA (opaque number)135  declarationId: DeclarationId,136  name: IdentifierName | null, // null for temporaries, mutated by RenameVariables137  mutableRange: MutableRange,  // { start, end } — mutated by InferMutationAliasingRanges138  scope: ScopeId | null,       // index into scope arena — mutated by InferReactiveScopeVariables139  type: TypeId,                // index into type arena — mutated by InferTypes140  loc: SourceLocation,141}142```143144### FunctionExpression / ObjectMethod145```146FunctionExpression {147  loweredFunc: FunctionId,     // index into function arena on Environment148  ...                          // other fields remain inline149}150```151152**Note**: Inner `HIRFunction` values are stored in a function arena on `Environment`, referenced by `FunctionId`. This replaces inline storage and provides a stable, copyable reference for passes that need to cache or access inner functions.153154### ReactiveScope155```156ReactiveScope {157  id: ScopeId,158  range: MutableRange,                              // mutated by alignment passes159  dependencies: Set<ReactiveScopeDependency>,        // populated by PropagateScopeDependencies160  declarations: Map<IdentifierId, ReactiveScopeDeclaration>,161  reassignments: Set<IdentifierId>,162  earlyReturnValue: { value: IdentifierId, loc, label } | null,163  merged: Set<ScopeId>,164}165```166167### MutableRange168```169MutableRange {170  start: EvaluationOrder,  // inclusive (renamed from InstructionId)171  end: EvaluationOrder,    // exclusive172}173```174175---176177## The Shared Mutable Reference Problem178179This is the **central challenge** for a Rust port. In TypeScript, the compiler relies on JavaScript's reference semantics in three pervasive patterns:180181### Pattern 1: Shared Identifier Mutation182```typescript183// Multiple Place objects share the SAME Identifier object184const place1: Place = { identifier: someIdentifier, ... };185const place2: Place = { identifier: someIdentifier, ... }; // same object!186187// A pass mutates the identifier through one place...188place1.identifier.mutableRange.end = 42;189190// ...and the change is visible through the other191console.log(place2.identifier.mutableRange.end); // 42192```193194Used by: InferMutationAliasingRanges, InferReactiveScopeVariables, InferTypes, InferReactivePlaces, RenameVariables, PromoteUsedTemporaries, EnterSSA, EliminateRedundantPhi, AnalyseFunctions, and many more.195196### Pattern 2: Shared ReactiveScope References197```typescript198// Multiple Identifiers share the same ReactiveScope AND MutableRange199identifier.mutableRange = scope.range;  // line 132 of InferReactiveScopeVariables200201// Now identifier.mutableRange IS scope.range (same JS object)202// A pass expands the scope range...203scope.range.end = 100;204205// ...visible through the identifier206console.log(identifier.mutableRange.end); // 100207```208209This is explicitly noted in AnalyseFunctions.ts (line 30-34): "NOTE: inferReactiveScopeVariables makes identifiers in the scope point to the *same* mutableRange instance."210211Used by: AlignMethodCallScopes, AlignObjectMethodScopes, AlignReactiveScopesToBlockScopesHIR, MergeOverlappingReactiveScopesHIR, MemoizeFbtAndMacroOperandsInSameScope.212213### Pattern 3: Iterate-and-Mutate / Side Map References214```typescript215// Store a reference to an HIR object in a side map216const nodes: Map<Identifier, Node> = new Map();217nodes.set(identifier, { id: identifier, ... });218219// Later, mutate the object through the stored reference220node.id.mutableRange.end = 42; // mutates HIR through map reference221```222223Used by: InferMutationAliasingRanges (AliasingState.nodes), EnterSSA (SSABuilder.#states.defs), InferMutationAliasingEffects (Context caches  see note below about upstream simplification), DropManualMemoization (sidemap.manualMemos), InlineIIFEs (functions map), AlignReactiveScopesToBlockScopesHIR (activeScopes), and others.224225---226227## Environment as Shared Mutable State228229### Complete Environment Analysis230231Environment is created once per top-level function compilation and stored on `HIRFunction.env`. It is shared via reference across the entire compilation, including nested functions.232233#### Mutable State (mutated by passes)234| Field | Mutated by | Pattern |235|-------|-----------|---------|236| `#nextIdentifer: number` | BuildHIR, EnterSSA, OutlineJSX, InferMutationAliasingEffects (via `createTemporaryPlace`) | Auto-increment counter |237| `#nextBlock: number` | BuildHIR, InlineIIFEs | Auto-increment counter |238| `#nextScope: number` | InferReactiveScopeVariables | Auto-increment counter |239| `#errors: CompilerError` | All validation passes, DropManualMemoization, InferMutationAliasingRanges, CodegenReactiveFunction | Append-only accumulator |240| `#outlinedFunctions: Array` | OutlineJSX, OutlineFunctions | Append-only list |241| `#moduleTypes: Map` | `getGlobalDeclaration` (lazy cache fill) | One-time lazy initialization |242243#### Read-Only State (accessed but never mutated)244| Field | Accessed by |245|-------|------------|246| `config: EnvironmentConfig` | Pipeline.ts (feature flags), InferMutationAliasingEffects, DropManualMemoization, MemoizeFbtAndMacroOperandsInSameScope, InferReactiveScopeVariables |247| `fnType: ReactFunctionType` | Pipeline.ts |248| `outputMode: CompilerOutputMode` | Pipeline.ts, DeadCodeElimination |249| `#globals: GlobalRegistry` | InferTypes (via `getGlobalDeclaration`), DropManualMemoization |250| `#shapes: ShapeRegistry` | InferTypes (via `getPropertyType`, `getFunctionSignature`), InferMutationAliasingEffects, InferReactivePlaces, FlattenScopesWithHooksOrUseHIR, NameAnonymousFunctions |251| `logger` | Pipeline.ts, AnalyseFunctions |252| `programContext` | BuildHIR, CodegenReactiveFunction, OutlineJSX |253254#### How Environment is Shared with Nested Functions255256Parent and nested functions share the **exact same Environment instance**. When `lower()` is called for a nested function expression, it receives the same `env`. This means:257- ID counters are globally unique across the entire function tree258- Errors from inner function compilation are visible to the parent259- Outlined functions from inner compilations accumulate on the shared list260- Configuration is shared (same feature flags everywhere)261262This sharing is sequential, not concurrent: `AnalyseFunctions` processes each child function synchronously before returning to the parent.263264### Recommended Rust Representation265266Remove `HIRFunction.env` and pass `env: &mut Environment` as a separate parameter to passes. Maintain the existing fields and types of the `Environment` struct  do not group them into sub-structs. Use direct field access (rather than methods) to allow precise sliced borrows of portions of the environment.267268```rust269struct Environment {270    // Configuration (read-only after construction)271    config: EnvironmentConfig,272    fn_type: ReactFunctionType,273    output_mode: CompilerOutputMode,274275    // Type registries (read-only after lazy init)276    globals: GlobalRegistry,277    shapes: ShapeRegistry,278    module_types: HashMap<String, Option<Global>>,279280    // Mutable counters281    next_identifier: IdentifierId,282    next_block: BlockId,283    next_scope: ScopeId,284285    // Arenas286    identifiers: Vec<Identifier>,         // indexed by IdentifierId287    scopes: Vec<ReactiveScope>,           // indexed by ScopeId288    functions: Vec<HIRFunction>,          // indexed by FunctionId289    types: Vec<Type>,                     // indexed by TypeId290291    // Accumulated state292    errors: Vec<CompilerDiagnostic>,293    outlined_functions: Vec<OutlinedFunction>,294295    // Other296    logger: Option<Logger>,297    program_context: ProgramContext,298}299```300301**Why no sub-structs**: Keeping all fields flat on `Environment` allows Rust's borrow checker to reason about independent field borrows. For example, a pass can simultaneously borrow `env.identifiers` and `env.config` without conflict, because the borrow checker can see they are distinct fields. Grouping fields into sub-structs would require borrowing the entire sub-struct even when only one field is needed.302303**Pass signatures** return `Result` for errors that would have thrown in TypeScript:304305```rust306// Most passes: need mutable HIR + mutable environment307fn enter_ssa(func: &mut HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... }308309// Validation passes:310fn validate_hooks_usage(func: &HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... }311312// Passes that don't use env at all (many!):313fn merge_consecutive_blocks(func: &mut HIRFunction) { ... }314fn constant_propagation(func: &mut HIRFunction) { ... }315```316317**Key insight from per-pass analysis**: The majority of passes (PruneMaybeThrows, MergeConsecutiveBlocks, ConstantPropagation, EliminateRedundantPhi, OptimizePropsMethodCalls, DeadCodeElimination, RewriteInstructionKinds, PruneUnusedLabelsHIR, FlattenReactiveLoopsHIR, and all reactive function transforms) do NOT use Environment at all. Only ~12 passes need `env`, and most only read config flags or call `getHookKind()`.318319For the `AnalyseFunctions` recursive pattern (where parent and child share the same Environment), `&mut Environment` works naturally because the recursive call completes before the parent continues  there is only one `&mut` active at a time.320321---322323## Side Maps: Passes Storing HIR References324325### The Core Problem326327Many passes store references to HIR values (Places, Identifiers, Instructions, InstructionValues, ReactiveScopes) in "side maps" (HashMaps, Sets, arrays) while simultaneously mutating the HIR. In Rust, this creates borrow conflicts because you cannot hold an immutable reference (in the map) while mutating through a different path.328329### Classification of Side Map Patterns330331After analyzing every pass, side map patterns fall into four categories:332333#### Category 1: ID-Only Maps (No Borrow Issues)334Maps keyed and valued by opaque IDs (`IdentifierId`, `BlockId`, `ScopeId`, `InstructionId`, `DeclarationId`). These are `Copy` types with no aliasing concerns.335336**Passes**: PruneMaybeThrows, MergeConsecutiveBlocks, ConstantPropagation, DeadCodeElimination, RewriteInstructionKinds, InferReactivePlaces (reactive set), PruneUnusedLabelsHIR, FlattenReactiveLoopsHIR, FlattenScopesWithHooksOrUseHIR, StabilizeBlockIds, and most reactive function transforms.337338**Rust approach**: Direct `HashMap<IdType, T>` / `HashSet<IdType>`. No changes needed.339340#### Category 2: Reference-Identity Maps (Replace Keys with IDs)341Maps using JavaScript object identity (`===`) as the key, typically `Map<Identifier, T>` or `Map<BasicBlock, T>` or `DisjointSet<Identifier>` / `DisjointSet<ReactiveScope>`.342343**Passes**: EnterSSA (`Map<BasicBlock, State>`, `Map<Identifier, Identifier>`), EliminateRedundantPhi (`Map<Identifier, Identifier>`), InferMutationAliasingRanges (`Map<Identifier, Node>`), InferReactiveScopeVariables (`DisjointSet<Identifier>`), InferReactivePlaces (`DisjointSet<Identifier>`), AlignMethodCallScopes (`DisjointSet<ReactiveScope>`), AlignObjectMethodScopes (`Set<Identifier>`, `DisjointSet<ReactiveScope>`), MergeOverlappingReactiveScopes (`DisjointSet<ReactiveScope>`).344345**Rust approach**: Replace with `HashMap<IdentifierId, T>`, `HashMap<BlockId, T>`, `DisjointSet<IdentifierId>`, `DisjointSet<ScopeId>`. This is **always simpler and more correct** than the TypeScript  it eliminates an entire class of bugs where cloned objects silently fail identity checks.346347#### Category 3: Instruction/Value Reference Maps (Store Indices Instead)348Maps that store references to actual `Instruction`, `FunctionExpression`, or `InstructionValue` objects, then later access fields on those objects or mutate them.349350**Passes**: InferMutationAliasingEffects (`Map<Instruction, InstructionSignature>`, `Map<FunctionExpression, AliasingSignature>`), DropManualMemoization (`Map<IdentifierId, TInstruction<FunctionExpression>>`, `ManualMemoCallee.loadInstr`), InlineIIFEs (`Map<IdentifierId, FunctionExpression>`), NameAnonymousFunctions (`Node.fn: FunctionExpression`).351352**Note**: InferMutationAliasingEffects currently uses `Map<InstructionValue, AbstractValue>` and `Map<IdentifierId, Set<InstructionValue>>` with `InstructionValue` objects as allocation-site identity tokens (JS reference identity), including both real InstructionValues from the HIR (for `CreateFunction`) and synthetic objects fabricated as allocation-site markers. An upstream refactor ([PR #33650](https://github.com/facebook/react/pull/33650)) replaces all `InstructionValue` keys with interned `AliasingEffect` objects, eliminating the synthetic InstructionValues and `effectInstructionValueCache` entirely. Since effects are already interned by content hash, reference identity equals content identity — exactly what's needed for Rust. In Rust, the `EffectId` (index into the interning table) serves as the allocation-site key directly. See [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership) for the full analysis.353354**Rust approach**: Store only what is actually needed:355- If the map is for existence checking: use `HashSet<IdentifierId>`356- If specific fields are needed later: extract and store those fields (e.g., store `InstructionId` to reference the instruction table)357- Instructions are stored in a flat table on `HIRFunction`, referenced by `InstructionId`  passes can reference any instruction by a single copyable ID358- `FunctionExpression`/`ObjectMethod` inner functions are accessed via `FunctionId` referencing the function arena on `Environment`359- For InferMutationAliasingEffects: use `InstructionId` for instruction signature cache, `EffectId` (interning table index) for value-identity maps, `FunctionId` for function signature caches360361#### Category 4: Scope Reference Sets with In-Place Mutation (Arena Access)362Sets or maps of `ReactiveScope` references where the scope's `range` fields are mutated while the scope is in the collection.363364**Passes**: AlignReactiveScopesToBlockScopesHIR (`Set<ReactiveScope>` iterated while mutating `scope.range`), AlignMethodCallScopes (DisjointSet forEach with range mutation), AlignObjectMethodScopes (same pattern), MergeOverlappingReactiveScopesHIR (DisjointSet with range mutation), MemoizeFbtAndMacroOperandsInSameScope (scope range mutation).365366**Rust approach**: Store `ScopeId` in sets/DisjointSets. Mutate through arena: `env.scopes[scope_id].range.start = ...`. The set holds copyable IDs, and the mutation goes through the arena  completely disjoint borrows.367368### Critical Insight: The Shared MutableRange Aliasing369370The most architecturally significant side map pattern is in `InferReactiveScopeVariables` (line 132):371```typescript372identifier.mutableRange = scope.range;373```374375This makes ALL identifiers in a scope share the SAME `MutableRange` object as the scope. Every subsequent scope-alignment pass relies on this: mutating `scope.range.start` automatically updates all identifiers' `mutableRange`.376377**Recommended Rust approach**: Identifiers store `scope: Option<ScopeId>`. The "effective mutable range" is always accessed through the scope arena:378```rust379fn effective_mutable_range(id: &Identifier, scopes: &[ReactiveScope]) -> MutableRange {380    match id.scope {381        Some(scope_id) => scopes[scope_id.index()].range,382        None => id.mutable_range, // pre-scope original range383    }384}385```386387All downstream passes that read `identifier.mutableRange` (like `isMutable()`, `inRange()`) would need access to `env.scopes`. This is a mechanical refactor  every call site accesses the scope arena via `Environment`.388389---390391## AliasingEffect: Shared References and Rust Ownership392393### Overview394395`AliasingEffect` is a discriminated union (17 variants) that describes data flow, mutation, and other side effects of instructions and terminals. Effects are **created** by `InferMutationAliasingEffects`, stored on `Instruction.effects` and `Terminal.effects`, and **consumed** by `InferMutationAliasingRanges`, `AnalyseFunctions`, validation passes, and `PrintHIR`. This section analyzes the shared references between `AliasingEffect` variants, `Instruction`, and `InstructionValue`, and how they map to Rust ownership.396397### Shared Reference Inventory398399Every `AliasingEffect` variant contains `Place` objects. In the TypeScript implementation, these are the **same JS object references** as the Places in the `InstructionValue` and `Instruction.lvalue`  not copies. This creates a web of shared references:400401#### Category A: Place Sharing (Instruction/InstructionValue  Effect)402403Nearly every instruction kind in `computeSignatureForInstruction` creates effects that directly reference Places from the instruction:404405| InstructionValue Kind | Effect Created | Shared Place Fields |406|---|---|---|407| `ArrayExpression` | `Create into:lvalue`, `Capture from:element into:lvalue` | `lvalue`, each `element` from `value.elements` |408| `ObjectExpression` | `Create into:lvalue`, `Capture from:property.place into:lvalue` | `lvalue`, each `property.place` from `value.properties` |409| `PropertyStore/ComputedStore` | `Mutate value:object`, `Capture from:value into:object` | `value.object`, `value.value`, `lvalue` |410| `PropertyLoad/ComputedLoad` | `CreateFrom from:object into:lvalue` | `value.object`, `lvalue` |411| `PropertyDelete/ComputedDelete` | `Mutate value:object` | `value.object`, `lvalue` |412| `Destructure` | `CreateFrom from:value.value into:place` per pattern item | `value.value`, each pattern item place |413| `JsxExpression` | `Freeze value:operand`, `Capture`, `Render place:tag/child` | `lvalue`, `value.tag`, each child, each prop place |414| `GetIterator` | `Alias/Capture from:collection into:lvalue` | `value.collection`, `lvalue` |415| `IteratorNext` | `MutateConditionally value:iterator`, `CreateFrom from:collection` | `value.iterator`, `value.collection`, `lvalue` |416| `StoreLocal` | `Assign from:value.value into:value.lvalue.place` | `value.value`, `value.lvalue.place`, `lvalue` |417| `LoadLocal` | `Assign from:value.place into:lvalue` | `value.place`, `lvalue` |418| `Await` | `MutateTransitiveConditionally value:value.value`, `Capture` | `value.value`, `lvalue` |419420#### Category B: Call Instructions  Deep Sharing via Apply421422For `CallExpression`, `MethodCall`, and `NewExpression`, a single `Apply` effect is created that shares **multiple fields** including the args array itself:423424```typescript425// From computeSignatureForInstruction (line 1832-1841)426effects.push({427  kind: 'Apply',428  receiver,              // same Place as value.receiver or value.callee429  function: callee,      // same Place as value.callee or value.property430  mutatesFunction: ...,431  args: value.args,      // THE SAME ARRAY REFERENCE from InstructionValue432  into: lvalue,          // same Place as instruction.lvalue433  signature,             // shared FunctionSignature from type registry434  loc: value.loc,435});436```437438The `args` field is the **exact same array object** as the InstructionValue's `args`. In Rust, this must be either cloned or accessed via the instruction.439440#### Category C: FunctionExpression  The Deepest Sharing441442The `CreateFunction` variant holds a direct reference to the `FunctionExpression` or `ObjectMethod` InstructionValue:443444```typescript445// From computeSignatureForInstruction (line 1946-1953)446effects.push({447  kind: 'CreateFunction',448  into: lvalue,449  function: value,  // THE SAME FunctionExpression/ObjectMethod InstructionValue450  captures: value.loweredFunc.func.context.filter(451    operand => operand.effect === Effect.Capture,452  ),453});454```455456This is the most architecturally significant sharing because `effect.function` is used in three distinct ways:4574581. **As an allocation-site token** in abstract interpretation (reference identity):459   - `state.initialize(effect.function, {...})`  `#values.set(value, kind)`  FunctionExpression as map key460   - `state.define(effect.into, effect.function)`  `#variables.set(id, new Set([value]))`  FunctionExpression as set value4614622. **For deep structural access**:463   - `effect.function.loweredFunc.func.aliasingEffects`  reads the nested function's inferred effects464   - `effect.function.loweredFunc.func.context`  iterates captured variables4654663. **For mutation** of the nested function's context:467   - `operand.effect = Effect.Read` (line 838)  mutates `Place.effect` on the nested function's context variables468469**Rust approach**: `CreateFunction` stores a `FunctionId` referencing the function arena on `Environment`. Allocation-site identity uses `EffectId` (from effect interning), deep structural access uses `env.functions[function_id]`, and context mutation uses `&mut env.functions[function_id].context`.470471### Allocation-Site Identity: InstructionValue  AliasingEffect (PR #33650)472473The abstract interpretation in `InferenceState` tracks the abstract kind (Mutable, Frozen, Primitive, etc.) of each "allocation site" and which allocation sites each identifier points to. Currently this uses `InstructionValue` objects as allocation-site identity tokens via JS reference identity:474475```476#values: Map<InstructionValue, AbstractValue>   // InstructionValue as KEY (reference identity)477#variables: Map<IdentifierId, Set<InstructionValue>>  // InstructionValue as SET VALUE478```479480Allocation sites are created from:481- **Params/context variables**: Synthetic `{kind: 'Primitive'}` or `{kind: 'ObjectExpression'}` objects482- **`Create`/`CreateFrom` effects**: Synthetic InstructionValues via `effectInstructionValueCache` (maps interned effect  synthetic InstructionValue)483- **`CreateFunction` effects**: The actual `FunctionExpression` InstructionValue from the HIR484485**Upstream simplification** ([facebook/react#33650](https://github.com/facebook/react/pull/33650)): This PR replaces `InstructionValue` with the interned `AliasingEffect` itself as the allocation-site key:486487```488#values: Map<AliasingEffect, AbstractValue>     // interned AliasingEffect as KEY489#variables: Map<IdentifierId, Set<AliasingEffect>>490```491492The changes:4931. **Params/context**: Synthetic `InstructionValue` objects are replaced with `AliasingEffect` objects (e.g., `{kind: 'Create', into: place, value: ValueKind.Context, reason: ValueReason.Other}`)4942. **`Create`/`CreateFrom` effects**: `effectInstructionValueCache` is eliminated entirely. `state.initialize(effect, ...)` and `state.define(place, effect)` use the interned effect directly as the key/value4953. **`CreateFunction` effects**: `state.initialize(effect.function, ...)`  `state.initialize(effect, ...)`  the CreateFunction effect itself is the key, not the FunctionExpression4964. **`state.values()` return type**: Changes from `Array<InstructionValue>` to `Array<AliasingEffect>`. Code that checks function values now uses `values[0].kind === 'CreateFunction'` and accesses `values[0].function` for the FunctionExpression4975. **`freezeValue` method**: Checks `value.kind === 'CreateFunction'` and accesses `value.function.loweredFunc.func.context` instead of `value.kind === 'FunctionExpression'`498499Since effects are already interned by content hash (via `context.internEffect()`), reference identity equals content identity. This means the interned `AliasingEffect` maps directly to a copyable `EffectId` index in Rust  no separate `AllocationSiteId` type is needed.500501**Key insight for CreateFunction**: After PR #33650, the `CreateFunction` effect's `function` field (the FunctionExpression/ObjectMethod reference) is **no longer used as a map key** for allocation-site tracking. It is only used for:5021. **Deep structural access**: `effect.function.loweredFunc.func.context` and `.aliasingEffects`5032. **As a key in `functionSignatureCache`**: `Map<FunctionExpression, AliasingSignature>` (the one remaining reference-identity map using FunctionExpression)5043. **Mutation**: `operand.effect = Effect.Read` on context variables505506In Rust, `CreateFunction` stores a `FunctionId` referencing the function arena on `Environment`. The function's context and aliasing effects are accessed via `env.functions[function_id]`. The allocation-site identity is the `EffectId` of the interned CreateFunction effect. The `functionSignatureCache` keys by `FunctionId` instead of FunctionExpression reference.507508### Effect Interning509510Effects are interned by content hash in `Context.internEffect()`:511512```typescript513internEffect(effect: AliasingEffect): AliasingEffect {514  const hash = hashEffect(effect);           // hash based on identifier IDs, not Place references515  let interned = this.internedEffects.get(hash);516  if (interned == null) {517    this.internedEffects.set(hash, effect);518    interned = effect;519  }520  return interned;521}522```523524The hash uses `place.identifier.id` (a number) rather than Place reference identity. The interned effect retains the Place references from whichever instruction first created that hash. In the fixpoint loop, re-processing an instruction may produce an effect with the same hash but different Place objects; interning returns the **original** effect with its original Place references. This is safe in TypeScript (both Places point to the same shared Identifier), but in Rust it means the interned effect's Places may not be the "current" instruction's Places  they are equivalent by ID but different allocations.525526With PR #33650, the interned effect is also the allocation-site key. Since interning guarantees that the same `EffectId` is returned for structurally identical effects, the fixpoint loop correctly converges  the same allocation site is used across iterations.527528### Consumers: How Effects Are Read529530#### InferMutationAliasingRanges (primary consumer)531532Iterates `instr.effects` for every instruction and reads Place fields:533- `effect.into.identifier`  used as key in `AliasingState.nodes` and to call `state.create()`534- `effect.from.identifier`  used in `state.assign()`, `state.capture()`, `state.maybeAlias()`535- `effect.value.identifier`  stored in `mutations` array, passed to `state.mutate()`536- `effect.function.loweredFunc.func`  used in `state.create()` for Function nodes537- `effect.place.identifier`  stored in `renders` array for Render effects538- `effect.error`  for MutateFrozen/MutateGlobal/Impure, recorded on Environment539540Also reads terminal effects: `block.terminal.effects` for Alias and Freeze effects on maybe-throw/return terminals.541542Also reads effects a second time (Part 2, lines 359-421) to compute legacy per-operand `Effect` enum values. This pass accesses `effect.*.identifier.id` and `effect.*.identifier.mutableRange.end` through effect Places.543544**Key observation**: InferMutationAliasingRanges reads `identifier.id`, `identifier` (for the reference-identity map key), and `identifier.mutableRange` from effect Places. It never mutates them through the effect's Places (mutations go through the graph nodes). With arena-based identifiers, `place.identifier` is an `IdentifierId` (`Copy`), and `mutableRange` is accessed via the identifier arena. No Place reference comparison is done — all passes access identifiers through their IDs, never by comparing Place object references.545546#### AnalyseFunctions547548Reads `fn.aliasingEffects` (the function-level effects from `InferMutationAliasingRanges`) to populate context variable effect annotations:549- `effect.from.identifier.id`  for Assign/Alias/Capture/CreateFrom/MaybeAlias variants550- `effect.value.identifier.id`  for Mutate/MutateConditionally/MutateTransitive/MutateTransitiveConditionally551552Only reads identifier IDs. Does not access Places beyond `.identifier.id`.553554#### ValidateNoFreezingKnownMutableFunctions555556Reads `fn.aliasingEffects` on nested `FunctionExpression` values:557- Stores `Mutate`/`MutateTransitive` effects in `Map<IdentifierId, AliasingEffect>`558- Reads `effect.value.identifier.id`, `effect.value.identifier.name`, `effect.value.loc`559560Accesses Identifier fields (name, loc) beyond just the ID, but these are read-only.561562#### Other Passes (do NOT read AliasingEffects)563564`ValidateLocalsNotReassignedAfterRender`, `ValidateNoImpureFunctionsInRender`, and `PruneNonEscapingScopes` import from AliasingEffects.ts or InferMutationAliasingEffects.ts but only use `getFunctionCallSignature` or the legacy `Effect` enum on Places  they do not read `instr.effects` or `fn.aliasingEffects`.565566#### PrintHIR567568Reads all effect fields for debug output. Read-only.569570### Recommended Rust Representation571572#### AliasingEffect Enum573574With arena-based identifiers, `Place` becomes a small `Copy`/`Clone` struct. Effects can own cloned Places:575576```rust577#[derive(Clone)]578enum AliasingEffect {579    Freeze { value: Place, reason: ValueReason },580    Mutate { value: Place, reason: Option<MutationReason> },581    MutateConditionally { value: Place },582    MutateTransitive { value: Place },583    MutateTransitiveConditionally { value: Place },584    Capture { from: Place, into: Place },585    Alias { from: Place, into: Place },586    MaybeAlias { from: Place, into: Place },587    Assign { from: Place, into: Place },588    Create { into: Place, value: ValueKind, reason: ValueReason },589    CreateFrom { from: Place, into: Place },590    ImmutableCapture { from: Place, into: Place },591    Render { place: Place },592593    Apply {594        receiver: Place,595        function: Place,596        mutates_function: bool,597        args: Vec<PlaceOrSpreadOrHole>,    // cloned from InstructionValue598        into: Place,599        signature: Option<FunctionSignature>,600        loc: SourceLocation,601    },602    CreateFunction {603        into: Place,604        /// Index into function arena on Environment.605        /// Used to access context variables, aliasing effects, etc.606        function: FunctionId,607        captures: Vec<Place>,              // cloned from context, filtered608    },609610    MutateFrozen { place: Place, error: CompilerDiagnostic },611    MutateGlobal { place: Place, error: CompilerDiagnostic },612    Impure { place: Place, error: CompilerDiagnostic },613}614```615616Key design decisions:617- **Place is cloned, not shared**: Since `Place` stores `IdentifierId` (a `Copy` type) + `Effect` + `bool` + `SourceLocation`, it is small enough to clone cheaply. No shared references needed.618- **`CreateFunction.function`** stores a `FunctionId` referencing the function arena on `Environment`. Code that needs `func.context` or `func.aliasingEffects` accesses `env.functions[function_id]` directly (see [Accessing Functions from CreateFunction](#accessing-functions-from-createfunction) below).619- **`Apply.args`** is a cloned `Vec`, not a shared reference to the InstructionValue's args. This is a shallow clone of `Place`/`SpreadPattern`/`Hole` values (all small, copyable types with arena IDs).620621#### EffectId as Allocation-Site Identity622623With PR #33650, the interned `AliasingEffect` replaces `InstructionValue` as the allocation-site key. In Rust, the `EffectId` (index into the interning table) serves directly as the allocation-site identity  no separate `AllocationSiteId` is needed:624625```rust626struct InferenceState {627    /// The kind of each value, keyed by the EffectId of its creation effect628    values: HashMap<EffectId, AbstractValue>,629    /// The set of allocation sites pointed to by each identifier630    variables: HashMap<IdentifierId, SmallVec<[EffectId; 2]>>,631}632633impl InferenceState {634    /// Initialize a value at the given allocation site635    fn initialize(&mut self, effect_id: EffectId, kind: AbstractValue) {636        self.values.insert(effect_id, kind);637    }638639    /// Define a variable to point at an allocation site640    fn define(&mut self, place: &Place, effect_id: EffectId) {641        self.variables.insert(place.identifier, smallvec![effect_id]);642    }643644    /// Look up which allocation sites a place points to645    fn values(&self, place: &Place) -> &[EffectId] {646        self.variables.get(&place.identifier).expect("uninitialized").as_slice()647    }648}649```650651Each call to `state.initialize(effect, kind)` / `state.define(place, effect)` in TypeScript becomes `state.initialize(effect_id, kind)` / `state.define(place, effect_id)` in Rust, where `effect_id` is the `EffectId` returned by the effect interner. This applies uniformly to all creation effects:652- **`Create`/`CreateFrom`**: The interned effect's `EffectId` is both the interning key and the allocation-site key653- **`CreateFunction`**: Same  the interned CreateFunction effect's `EffectId` is the allocation-site key (the `FunctionExpression` reference is no longer used as a key)654- **Params/context**: Synthetic `AliasingEffect::Create` values are interned and their `EffectId` serves as the allocation site655656The `effectInstructionValueCache` is eliminated entirely (PR #33650 removes it). The `functionSignatureCache: Map<FunctionExpression, AliasingSignature>` becomes `HashMap<FunctionId, AliasingSignature>`  keyed by the `FunctionId` rather than the FunctionExpression reference.657658#### Effect Interning659660```rust661struct EffectInterner {662    effects: Vec<AliasingEffect>,        // indexed by EffectId663    by_hash: HashMap<String, EffectId>,  // dedup by content hash664}665666#[derive(Copy, Clone, Hash, Eq, PartialEq)]667struct EffectId(u32);668669impl EffectInterner {670    fn intern(&mut self, effect: AliasingEffect) -> EffectId {671        let hash = hash_effect(&effect);672        *self.by_hash.entry(hash).or_insert_with(|| {673            let id = EffectId(self.effects.len() as u32);674            self.effects.push(effect);675            id676        })677    }678}679```680681Since the interned effect IS the allocation-site key, there is no additional cache or mapping needed. The `EffectId` serves as interning dedup key, allocation-site identity, and cache key for `applySignatureCache`. The `functionSignatureCache` is keyed by `FunctionId`.682683#### Accessing Functions from CreateFunction684685In Rust, `CreateFunction` stores `function: FunctionId`, so the inner function is accessed directly from the function arena on `Environment`:686687```rust688// Read access:689let inner_func = &env.functions[effect.function];690691// Mutable access:692let inner_func = &mut env.functions[effect.function];693```694695No instruction lookup or index is needed  the `FunctionId` provides direct O(1) access to the inner function's context variables, aliasing effects, and other data.696697#### Context Variable Mutation698699The mutation `operand.effect = Effect.Read` (in `applyEffect` for `CreateFunction`) modifies Places on the nested function's context. In Rust:700701```rust702// During CreateFunction processing, after determining abstract kinds:703let inner_func = &mut env.functions[effect.function];704for operand in &mut inner_func.context {705    if operand.effect == Effect::Capture {706        let kind = state.kind(operand).kind;707        if matches!(kind, ValueKind::Primitive | ValueKind::Frozen | ValueKind::Global) {708            operand.effect = Effect::Read;709        }710    }711}712```713714Since inner functions live in the function arena on `Environment` (not inline in the instruction), the borrow to `env.functions[function_id]` is completely disjoint from the outer `HIRFunction` being processed. No collect-then-apply workaround is needed.715716### Summary of Rust Approach for AliasingEffect717718| TypeScript Pattern | Rust Equivalent | Complexity |719|---|---|---|720| Effect Places share InstructionValue Places | Clone Places (cheap with `IdentifierId`) | Trivial |721| `Apply.args` shares InstructionValue's args array | Clone the `Vec<PlaceOrSpreadOrHole>` | Trivial |722| `CreateFunction.function` = the FunctionExpression | Store `FunctionId`, direct arena access | Trivial |723| `InstructionValue` as allocation-site key (→ `AliasingEffect` after #33650) | `EffectId` from interning table | Trivial |724| `effectInstructionValueCache` (eliminated by #33650) | Not needed  `EffectId` is the allocation site directly | N/A |725| `functionSignatureCache` (FunctionExpr  Signature) | `HashMap<FunctionId, AliasingSignature>` | Trivial |726| Effect interning by content hash | `EffectInterner` with `Vec` + `HashMap` | Low |727| `operand.effect = Effect.Read` mutation | `&mut env.functions[function_id].context`  disjoint borrow | Trivial |728| `applySignatureCache` (Signature × Apply  Effects) | `HashMap<(EffectId, EffectId), Vec<AliasingEffect>>` | Low |729| `state.values(place)` returning `AliasingEffect[]` | Returns `&[EffectId]` | Trivial |730731**Overall assessment**: AliasingEffect translates cleanly to Rust. With PR #33650, the interned `EffectId` serves as both the dedup key and allocation-site identity, eliminating the need for a separate `AllocationSiteId`. Place sharing is resolved by cloning (cheap with arena-based identifiers), and inner function access uses `FunctionId` into the function arena on `Environment`. No fundamental algorithmic redesign is needed. The fixpoint loop, effect interning, and abstract interpretation structure remain structurally identical.732733---734735## Recommended Rust Architecture736737### Arena-Based Identifier Storage738739Stored as `identifiers: Vec<Identifier>` directly on `Environment`.740741```rust742#[derive(Copy, Clone, Hash, Eq, PartialEq)]743struct IdentifierId(u32);744745#[derive(Clone)]746struct Place {747    identifier: IdentifierId,  // index into Environment.identifiers748    effect: Effect,749    reactive: bool,750    loc: SourceLocation,751}752753struct Identifier {754    id: IdentifierId,755    declaration_id: DeclarationId,756    name: Option<IdentifierName>,757    mutable_range: MutableRange,758    scope: Option<ScopeId>,759    ty: TypeId,                 // index into Environment.types760    loc: SourceLocation,761}762```763764### Arena-Based Scope Storage765766Stored as `scopes: Vec<ReactiveScope>` directly on `Environment`.767768```rust769#[derive(Copy, Clone, Hash, Eq, PartialEq)]770struct ScopeId(u32);771```772773### Arena-Based Function Storage774775Stored as `functions: Vec<HIRFunction>` directly on `Environment`. `FunctionExpression` and `ObjectMethod` instruction values store a `FunctionId` instead of inline function data.776777```rust778#[derive(Copy, Clone, Hash, Eq, PartialEq)]779struct FunctionId(u32);780```781782### Arena-Based Type Storage783784Stored as `types: Vec<Type>` directly on `Environment`. `Identifier.ty` stores a `TypeId` instead of an inline `Type` value.785786```rust787#[derive(Copy, Clone, Hash, Eq, PartialEq)]788struct TypeId(u32);789```790791### Instructions Table792793Instructions are stored in a flat table on `HIRFunction` (`instructions: Vec<Instruction>`), indexed by `InstructionId`. `BasicBlock.instructions` becomes `Vec<InstructionId>`, referencing into this table. The existing `InstructionId` type is renamed to `EvaluationOrder` since it represents evaluation order and is present on both instructions and terminals.794795```rust796#[derive(Copy, Clone, Hash, Eq, PartialEq)]797struct InstructionId(u32);798799#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]800struct EvaluationOrder(u32);801```802803This allows passes to cache or reference an instruction's location via a single copyable ID, avoiding `(BlockId, usize)` tuples.804805### CFG Representation806807```rust808/// Use IndexMap for insertion-order iteration (matching JS Map semantics)809struct HIR {810    entry: BlockId,811    blocks: IndexMap<BlockId, BasicBlock>,812}813```814815### Pass Signature Patterns816817Passes return `Result` for errors that would have thrown in TypeScript.818819```rust820/// Most passes: mutable HIR + mutable environment821fn enter_ssa(func: &mut HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... }822823/// Validation passes824fn validate_hooks_usage(func: &HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... }825826/// Passes that don't need env at all (many!)827fn merge_consecutive_blocks(func: &mut HIRFunction) { ... }828fn constant_propagation(func: &mut HIRFunction) { ... }829```830831### Key Rust Patterns for Common TypeScript Idioms832833#### Pattern A: InstructionValue Variant Swap (`std::mem::replace`)834```rust835// TypeScript: instr.value = { kind: 'CallExpression', callee: instr.value.property, ... }836// Rust: take ownership, destructure, construct new variant837let old = std::mem::replace(&mut instr.value, InstructionValue::Tombstone);838if let InstructionValue::MethodCall { property, args, loc, .. } = old {839    instr.value = InstructionValue::CallExpression { callee: property, args, loc };840} else {841    instr.value = old;842}843```844845#### Pattern B: Place Cloning via Spread (`{...place}`)846```rust847// TypeScript: const newPlace = { ...place, effect: Effect.Read }848// Rust: Place is Clone (or Copy if small enough)849let new_place = Place { effect: Effect::Read, ..place.clone() };850```851852#### Pattern C: Delete-During-Set-Iteration (`retain`)853```rust854// TypeScript: for (const phi of block.phis) { if (dead) block.phis.delete(phi); }855// Rust: retain is the idiomatic equivalent856block.phis.retain(|phi| !is_dead(phi));857```858859#### Pattern D: Map Iteration with Block Deletion860```rust861// TypeScript: for (const [, block] of fn.body.blocks) { fn.body.blocks.delete(id); }862// Rust: collect keys first, then remove + get_mut863let block_ids: Vec<BlockId> = blocks.keys().copied().collect();864for block_id in block_ids {865    if should_merge(block_id) {866        let removed = blocks.remove(&block_id).unwrap();867        let pred = blocks.get_mut(&pred_id).unwrap();868        pred.instructions.extend(removed.instructions);869    }870}871```872873#### Pattern E: Closure Variables Set Inside Builder Callbacks874```rust875// TypeScript: let callee = null; builder.enter(() => { callee = ...; return terminal; });876// Rust: closure returns the value, or use Option<T> initialized before877let (block_id, callee) = builder.enter(|b| {878    let callee = /* compute */;879    let terminal = /* build */;880    (terminal, callee)  // return both881});882```883884---885886## Input/Output Format887888Define a Rust representation of the Babel AST format using serde with custom serialization/deserialization in order to ensure that the `"type"` field is always produced, even outside of enum positions. Include full information from Babel, including source locations. Define a `Scope` type that encodes the tree of scope information, mapping to the information that Babel represents in its own scope tree.889890The main public API is roughly:891892```rust893/// Returns None if the function doesn't need changes, Some with the compiled output otherwise.894fn compile(ast: BabelAst, scope: Scope) -> Option<BabelAst>895```896897This replaces the current Babel-plugin integration pattern where the compiler receives NodePath objects. The JSON AST interchange decouples the Rust compiler from any specific JS parser or AST format at the implementation level while maintaining Babel compatibility at the serialization boundary.898899---900901## Error Handling902903In general there are two categories of errors:904- Anything that would have thrown, or would have short-circuited, should return an `Err(...)` with the single diagnostic905- Otherwise, accumulate errors directly onto the environment906- Error handling must preserve the full details of the errors: reason, description, location, details, suggestions, category, etc907908### Specific Error Patterns and Approaches909910| TypeScript Pattern | Example | Rust Approach |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 |916917### Pass and Pipeline Structure918919```rust920// pipeline.rs921fn compile(922    ast: Ast,923    scope: Scope,924    env: &mut Environment,925) -> Result<CompileResult, CompilerDiagnostic> {926    // "?" to handle cases that would have thrown or produced an invariant927    let mut hir = lower(ast, scope, env)?;928    some_compiler_pass(&mut hir, env)?;929    // ...930    let ast = codegen(...)?;931932    if env.has_errors() {933        Ok(CompileResult::Failure(env.take_errors()))934    } else {935        Ok(CompileResult::Success(ast))936    }937}938939// <compiler_pass>.rs940fn pass_name(941    func: &mut HirFunction,942    env: &mut Environment,943) -> Result<(), CompilerDiagnostic>;944```945946---947948## Structural Similarity: TypeScript  Rust Alignment949950### Design Goal951952The Rust code should be visually and structurally aligned with the original TypeScript. A developer should be able to have the TypeScript on the left side of the screen and the Rust on the right, scroll them together, and easily see how the logic corresponds.953954### What Looks Nearly Identical (~95% match)955956Most passes consist of these patterns that translate almost line-for-line:957958| TypeScript Pattern | Rust Equivalent |959|---|---|960| `switch (value.kind) { case 'X': ... }` | `match &value { InstructionValue::X { .. } => ... }` |961| `for (const [, block] of fn.body.blocks)` | `for block in func.body.blocks.values()` |962| `for (const instr of block.instructions)` | `for instr in &block.instructions` |963| `const map = new Map<K, V>()` | `let mut map: HashMap<K, V> = HashMap::new()` |964| `map.get(key) ?? defaultValue` | `map.get(&key).copied().unwrap_or(default)` |965| `if (x === null) { ... }` | `if x.is_none() { ... }` or `let Some(x) = x else { ... }` |966| `CompilerError.invariant(cond, ...)` | `assert!(cond, "...")` or `panic!("...")` |967| `do { ... } while (changed)` | `loop { ... if !changed { break; } }` |968| `array.push(item)` | `vec.push(item)` |969| `set.has(item)` | `set.contains(&item)` |970971### What Looks Slightly Different (~80% match)972973| TypeScript Pattern | Rust Equivalent | Reason |974|---|---|---|975| `Map<Identifier, T>` (reference keys) | `HashMap<IdentifierId, T>` | Reference identity  value identity |976| `DisjointSet<ReactiveScope>` | `DisjointSet<ScopeId>` | Same reason |977| `place.identifier.mutableRange.end = x` | `env.identifiers[place.identifier].mutable_range.end = x` | Arena indirection |978| `identifier.scope = sharedScope` | `identifier.scope = Some(scope_id)` | Reference  ID |979| `for...of` with `Set.delete()` | `set.retain(|x| ...)` | Different idiom, same semantics |980| `instr.value = { kind: 'X', ... }` | `instr.value = InstructionValue::X { ... }` (with `mem::replace`) | Ownership swap |981982### What Looks Substantially Different (~60% match)983984| TypeScript Pattern | Rust Equivalent | Reason |985|---|---|---|986| Storing `&Instruction` in side map | Store `InstructionId`, access via instruction table | Cannot hold references during mutation |987| Builder closures capturing outer `&mut` | Return values from closures, or split borrows | Borrow checker |988| `node.id.mutableRange.end = x` (graph node  HIR mutation) | Collect updates, apply to `env.identifiers` after traversal | Cannot mutate HIR through graph references |989| `identifier.mutableRange = scope.range` (shared object aliasing) | `identifier.scope = Some(scope_id)` + lookup via arena | Fundamental ownership model difference |990991### Passes Ranked by Structural Similarity to Rust992993**Nearly identical (95%+)**: PruneMaybeThrows, OptimizePropsMethodCalls, FlattenReactiveLoopsHIR, FlattenScopesWithHooksOrUseHIR, MergeConsecutiveBlocks, DeadCodeElimination, PruneUnusedLabelsHIR, RewriteInstructionKindsBasedOnReassignment, EliminateRedundantPhi, all validation passes, PruneUnusedLabels, PruneUnusedScopes, PruneNonReactiveDependencies, PruneAlwaysInvalidatingScopes, StabilizeBlockIds, PruneHoistedContexts994995**Very similar (85-95%)**: ConstantPropagation, EnterSSA, InferTypes, InferReactivePlaces, DropManualMemoization, InlineIIFEs, MemoizeFbtAndMacroOperandsInSameScope, AlignMethodCallScopes, AlignObjectMethodScopes, OutlineFunctions, NameAnonymousFunctions, BuildReactiveScopeTerminalsHIR, PropagateScopeDependenciesHIR, PropagateEarlyReturns, MergeReactiveScopesThatInvalidateTogether, PromoteUsedTemporaries, RenameVariables, ExtractScopeDeclarationsFromDestructuring996997**Moderately similar (70-85%)**: AnalyseFunctions, InferReactiveScopeVariables, AlignReactiveScopesToBlockScopesHIR, MergeOverlappingReactiveScopesHIR, OutlineJSX, BuildReactiveFunction, PruneNonEscapingScopes, OptimizeForSSR, PruneUnusedLValues998999**Moderately similar (70-85%)** *(additional)*: InferMutationAliasingEffects (after [PR #33650](https://github.com/facebook/react/pull/33650): allocation-site keys → `EffectId` via interning, Place sharing → Clone, CreateFunction → FunctionId arena access — see [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership))10001001**Requires redesign (50-70%)**: InferMutationAliasingRanges (graph-through-HIR mutation), BuildHIR (Babel AST coupling), CodegenReactiveFunction (Babel AST output)10021003---10041005## Pipeline Overview10061007```1008Babel AST1009    1010    1011┌─────────────────────────────────────────────┐1012 Phase 1: Lowering                           1013   BuildHIR (lower)                          1014└─────────────────────────────────────────────┘1015    1016    1017┌─────────────────────────────────────────────┐1018 Phase 2-3: Normalization + SSA              1019   PruneMaybeThrows                          1020   DropManualMemoization                     1021   InlineIIFEs                               1022   MergeConsecutiveBlocks                    1023   EnterSSA                                  1024   EliminateRedundantPhi                     1025└─────────────────────────────────────────────┘1026    1027    1028┌─────────────────────────────────────────────┐1029 Phase 4-5: Optimization + Type Inference    1030   ConstantPropagation                       1031   InferTypes                                1032   OptimizePropsMethodCalls                  1033└─────────────────────────────────────────────┘1034    1035    1036┌─────────────────────────────────────────────┐1037 Phase 6: Mutation/Aliasing Analysis         1038   AnalyseFunctions                          1039   InferMutationAliasingEffects              1040   DeadCodeElimination                       1041   InferMutationAliasingRanges               1042└─────────────────────────────────────────────┘1043    1044    1045┌─────────────────────────────────────────────┐1046 Phase 7-8: Post-Inference + Reactivity      1047   InferReactivePlaces                       1048   RewriteInstructionKindsBasedOnReassignment1049└─────────────────────────────────────────────┘1050    1051    1052┌─────────────────────────────────────────────┐1053 Phase 9-12: Scope Construction + Alignment  1054   InferReactiveScopeVariables               1055   MemoizeFbtAndMacroOperandsInSameScope     1056   OutlineJSX / OutlineFunctions             1057   AlignMethodCallScopes                     1058   AlignObjectMethodScopes                   1059   AlignReactiveScopesToBlockScopesHIR        1060   MergeOverlappingReactiveScopesHIR          1061   BuildReactiveScopeTerminalsHIR             1062   FlattenReactiveLoopsHIR                    1063   FlattenScopesWithHooksOrUseHIR             1064   PropagateScopeDependenciesHIR              1065└─────────────────────────────────────────────┘1066    1067    1068┌─────────────────────────────────────────────┐1069 Phase 13-14: Reactive Function              1070   BuildReactiveFunction (CFG  tree)        1071   PruneUnusedLabels                         1072   PruneNonEscapingScopes                    1073   PruneNonReactiveDependencies              1074   PruneUnusedScopes                         1075   MergeReactiveScopesThatInvalidateTogether 1076   PruneAlwaysInvalidatingScopes             1077   PropagateEarlyReturns                     1078   PruneUnusedLValues                        1079   PromoteUsedTemporaries                    1080   ExtractScopeDeclarationsFromDestructuring 1081   StabilizeBlockIds                         1082   RenameVariables                           1083   PruneHoistedContexts                      1084└─────────────────────────────────────────────┘1085    1086    1087┌─────────────────────────────────────────────┐1088 Phase 15: Codegen                           1089   CodegenReactiveFunction (tree  Babel AST)│1090└─────────────────────────────────────────────┘1091    1092    1093Babel AST (with memoization)1094```10951096---10971098## Pass-by-Pass Analysis10991100### Phase 1: Lowering11011102#### BuildHIR (`lower`)1103**What it does**: Converts Babel AST to HIR by traversing the AST and building a control-flow graph with BasicBlocks, Instructions, and Terminals.11041105**Environment usage**: Heavy. Uses `env.nextIdentifierId`, `env.nextBlockId` for all ID allocation. Uses `env.recordError()` for fault-tolerant error handling. Uses `env.parentFunction.scope` for Babel scope analysis. Uses `env.isContextIdentifier()` and `env.programContext`. Environment is shared with nested function lowering via recursive `lower()` calls.11061107**Side maps**:1108- `#bindings: Map<string, {node, identifier}>`  caches Identifier objects by name, using Babel node reference equality to distinguish same-named variables in different scopes1109- `#context: Map<t.Identifier, SourceLocation>`  Babel node keys (reference identity)1110- `#completed: Map<BlockId, BasicBlock>`  ID-keyed (safe)1111- `followups: Array<{place, path}>`  temporary Place storage during destructuring11121113**Structural similarity**: ~65%. The HIRBuilder class maps to a Rust struct with `&mut self` methods. The `enter()/loop()/label()` closure patterns translate to methods taking `impl FnOnce(&mut Self) -> Terminal`. However, several patterns require restructuring:1114- Variables assigned inside closures and read outside (e.g., `let callee = null; builder.enter(() => { callee = ...; })`) must return values from the closure instead1115- `resolveBinding()` uses Babel node reference equality (`mapping.node === node`)  needs parser-specific node IDs1116- Recursive `lower()` for nested functions needs `std::mem::take` to extract child function data1117- The Babel AST input arrives as JSON (deserialized via serde), replacing direct Babel NodePath traversal11181119**Unexpected issues**: Babel bug workarounds (lines 413-418, 4488-4498) would not be needed with a different parser. The `promoteTemporary()` pattern is straightforward in Rust. The `fbtDepth` counter is trivial.11201121---11221123### Phase 2: Normalization11241125#### PruneMaybeThrows1126**Env usage**: None. **Side maps**: `Map<BlockId, BlockId>` (IDs only). **Similarity**: ~95%.1127Simple terminal mutation (`handler = null`), phi rewiring, and CFG cleanup. The phi operand mutation-during-iteration needs `drain().collect()` in Rust. Block iteration order must be RPO for chain resolution.11281129#### DropManualMemoization1130**Env usage**: `getGlobalDeclaration`, `getHookKindForType`, `recordError`, `createTemporaryPlace`, config flags. **Side maps**: `IdentifierSidemap` with 6 collections  `functions` stores `TInstruction` references (use `HashSet<IdentifierId>` instead), `manualMemos.loadInstr` stores instruction reference (store `InstructionId` instead), others are ID-keyed. **Similarity**: ~85%.1131Two-phase collect+rewrite. In Rust, the `functions` map needs only existence checking (not the actual instruction reference). `manualMemos.loadInstr` only needs `.id`  store the ID directly.11321133#### InlineImmediatelyInvokedFunctionExpressions1134**Env usage**: `env.nextBlockId`, `env.nextIdentifierId` (via `createTemporaryPlace`). **Side maps**: `functions: Map<IdentifierId, FunctionExpression>` stores instruction value references. **Similarity**: ~80%.1135The `functions` map stores `FunctionExpression` references  in Rust, store `FunctionId` for the inner function. The queue-while-iterating pattern needs index-based loop (`while i < queue.len()`). Block ownership transfer uses `blocks.remove()` + `blocks.insert()`.11361137#### MergeConsecutiveBlocks1138**Env usage**: None. **Side maps**: `MergedBlocks` (ID-only map), `fallthroughBlocks` (ID-only set). **Similarity**: ~90%.1139Main Rust challenge: iteration + deletion. Collect block IDs first, then `remove()` + `get_mut()`. Phi operand rewriting needs collect-then-apply.11401141---11421143### Phase 3: SSA Construction11441145#### EnterSSA1146**Env usage**: `env.nextIdentifierId` for fresh SSA identifiers. **Side maps**: `#states: Map<BasicBlock, State>` with `defs: Map<Identifier, Identifier>` (both reference-identity keyed), `unsealedPreds: Map<BasicBlock, number>`, `#unknown/#context: Set<Identifier>`. **Similarity**: ~85%.1147All reference-identity maps become ID-keyed: `Vec<State>` indexed by BlockId, `HashMap<IdentifierId, IdentifierId>` for defs. The recursive `getIdAt()` works cleanly because `IdentifierId` is `Copy`  no borrows held across recursive calls. The `enter()` closure for nested functions is just save/restore of `self.current`. `makeType()` global counter must become per-compilation.11481149#### EliminateRedundantPhi1150**Env usage**: None. **Side maps**: `rewrites: Map<Identifier, Identifier>` (reference keys). **Similarity**: ~95%.1151Becomes `HashMap<IdentifierId, IdentifierId>`. `rewritePlace` becomes `place.identifier_id = new_id`. Phi deletion during iteration becomes `block.phis.retain(|phi| ...)`. The fixpoint loop and labeled `continue` translate directly.11521153---11541155### Phase 4: Optimization (Pre-Inference)11561157#### ConstantPropagation1158**Env usage**: None. **Side maps**: `constants: Map<IdentifierId, Constant>` (ID-keyed, safe). **Similarity**: ~90%.1159The fixpoint loop, `evaluateInstruction()` switch, and terminal rewriting all map directly. Constants map stores cloned `Primitive`/`LoadGlobal` values (small, cheap to clone). The CFG cleanup cascade after branch elimination needs shared infrastructure. The `block.kind === 'sequence'` guard translates to an enum check.11601161#### OptimizePropsMethodCalls1162**Env usage**: None. **Side maps**: None. **Similarity**: ~98%.1163The simplest pass in the compiler. A single linear scan with one `match` arm and `std::mem::replace` for the value swap. ~20 lines of Rust.11641165---11661167### Phase 5: Type and Effect Inference11681169#### InferTypes1170**Env usage**: `getGlobalDeclaration`, `getPropertyType`, `getFallthroughPropertyType`, config flags. **Side maps**: `Unifier.substitutions: Map<TypeId, Type>` (ID-keyed), `names: Map<IdentifierId, string>` (ID-keyed). **Similarity**: ~90%.1171Unification-based type inference is very natural in Rust. The `Type` enum needs `Box<Type>` for recursive variants (`Function.return`, `Property.objectType`). The TypeScript generator pattern for constraint generation can be replaced with direct `unifier.unify()` calls during the walk. The `apply()` phase is straightforward mutable traversal. `makeType()` global counter needs per-compilation scope.11721173---11741175### Phase 6: Mutation/Aliasing Analysis11761177#### AnalyseFunctions1178**Env usage**: Shares Environment between parent and child via `fn.env`. Uses logger. **Side maps**: None (operates entirely through in-place HIR mutation). **Similarity**: ~85%.1179The recursive `lowerWithMutationAliasing` pattern works with `&mut` because it is sequential. Inner functions are stored in the function arena on `Environment` and accessed via `FunctionId`, so no extraction/replacement is needed. The mutableRange reset (`identifier.mutableRange = {start: 0, end: 0}`) is a simple value write in Rust (no aliasing to break because Rust uses values, not shared objects).11801181#### InferMutationAliasingEffects1182**Env usage**: `env.config` (3 reads), `env.getFunctionSignature`, `env.enableValidations`, `createTemporaryPlace`. InferenceState stores `env` as read-only reference. **Side maps**: `statesByBlock/queuedStates` (BlockId-keyed), Context class with caches (`Map<Instruction, InstructionSignature>`, `Map<FunctionExpression, AliasingSignature>`, `Map<AliasingSignature, Map<AliasingEffect, ...>>`), InferenceState with `#values: Map<InstructionValue, AbstractValue>` and `#variables: Map<IdentifierId, Set<InstructionValue>>`. **Similarity**: ~80%.11831184**Shared references in AliasingEffect** (see AliasingEffect: Shared References and Rust Ownership](#aliasingeffect-shared-references-and-rust-ownership) for full analysis): `computeSignatureForInstruction` creates effects that share Place objects with the Instruction's `lvalue` and `InstructionValue` fields. The `Apply` effect shares the args array reference. The `CreateFunction` effect stores the actual `FunctionExpression`/`ObjectMethod` InstructionValue. In Rust, Places are cloned (cheap with `IdentifierId`) and `CreateFunction` stores a `FunctionId` for function arena access.11851186**Allocation-site identity**: Currently uses `InstructionValue` as reference-identity keys. PR [#33650](https://github.com/facebook/react/pull/33650) replaces this with interned `AliasingEffect` objects — since effects are already interned by content hash, the interned effect IS the allocation-site key. In Rust, this maps to `EffectId` (index into the interning table). No separate `AllocationSiteId` is needed.11871188**Reference-identity maps and their Rust equivalents** (after PR #33650):1189- `instructionSignatureCache: Map<Instruction, ...>`  `HashMap<InstructionId, InstructionSignature>`1190- `#values: Map<AliasingEffect, AbstractValue>`  `HashMap<EffectId, AbstractValue>` (EffectId = interning index = allocation-site ID)1191- `#variables: Map<IdentifierId, Set<AliasingEffect>>`  `HashMap<IdentifierId, SmallVec<[EffectId; 2]>>`1192- `effectInstructionValueCache`  eliminated by PR #336501193- `functionSignatureCache: Map<FunctionExpression, ...>`  `HashMap<FunctionId, AliasingSignature>` (key by FunctionId from arena)1194- `applySignatureCache: Map<AliasingSignature, Map<AliasingEffect, ...>>`  `HashMap<EffectId, HashMap<EffectId, ...>>`1195- `internedEffects: Map<string, AliasingEffect>`  `EffectInterner { effects: Vec<AliasingEffect>, by_hash: HashMap<String, EffectId> }`11961197All keys become `Copy` types (`InstructionId`, `EffectId`, `IdentifierId`), trivially `Hash + Eq`, with no reference identity needed.11981199The overall structure (fixpoint loop, InferenceState clone/merge, applyEffect recursion, Context caching) can remain nearly identical. The `applyEffect` recursive method works with `&mut InferenceState` + `&mut Context` parameters  Rust's reborrowing handles the recursion naturally.12001201**Context variable mutation**: During `CreateFunction` processing, `operand.effect = Effect.Read` mutates Places on the nested function's context. In Rust, the inner function is accessed via `&mut env.functions[function_id]`, which is completely disjoint from the outer `HIRFunction` being processed.12021203#### DeadCodeElimination1204**Env usage**: `env.outputMode` (one read for SSR hook pruning). **Side maps**: `State.identifiers: Set<IdentifierId>`, `State.named: Set<string>` (both value-keyed, safe). **Similarity**: ~95%.1205Two-phase mark-and-sweep is perfectly natural in Rust. `Vec::retain` replaces `retainWhere`. Destructuring pattern rewrites use `iter_mut()` + `truncate()`.12061207#### InferMutationAliasingRanges (HIGH COMPLEXITY)1208**Env usage**: `env.enableValidations` (one read), `env.recordError` (error recording). **Side maps**: `AliasingState.nodes: Map<Identifier, Node>` (reference-identity keys), each Node containing `createdFrom/captures/aliases/maybeAliases: Map<Identifier, number>` and `edges: Array<{node: Identifier, ...}>`. Also `mutations/renders` arrays storing Place references. **Similarity**: ~75%.12091210**Effect consumption**: Iterates `instr.effects` for every instruction, reading Place fields (`effect.into`, `effect.from`, `effect.value`, `effect.place`). For `CreateFunction` effects, accesses `effect.function.loweredFunc.func` to create Function graph nodes. In Rust, `CreateFunction` stores `FunctionId`; the function is accessed via `env.functions[function_id]` (see AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership)). All other effect Place accesses only need `place.identifier` (an `IdentifierId` in Rust), with no shared reference concerns.12111212**All Identifier-keyed maps become `HashMap<IdentifierId, T>`**. The critical `node.id.mutableRange.end = ...` pattern (mutating HIR through graph node references) needs restructuring: either store computed range updates on the Node and apply after traversal (recommended), or use arena-based identifiers. The BFS in `mutate()` collects edge targets into temporary `Vec<IdentifierId>` before pushing to queue, resolving borrow conflicts. The two-part structure (build graph  apply ranges) maps well to Rust's two-phase pattern. The temporal `index` counter and edge ordering translate directly.12131214**Potential latent issue**: The `edges` array uses `break` (line 763) assuming monotonic insertion order, but pending phi edges from back-edges could break this ordering. The Rust port should consider using `continue` instead of `break` for safety.12151216---12171218### Phase 7: Optimization (Post-Inference)12191220#### OptimizeForSSR1221**Env usage**: None directly (conditional on pipeline `outputMode` check). **Side maps**: `inlinedState: Map<IdentifierId, InstructionValue>` (ID-keyed). **Similarity**: ~90%.1222Stores cloned InstructionValue objects. The two-pass pattern translates directly.12231224---12251226### Phase 8: Reactivity Inference12271228#### InferReactivePlaces1229**Env usage**: `getHookKind(fn.env, ...)` for hook detection. **Side maps**: `ReactivityMap.reactive: Set<IdentifierId>` (safe), `ReactivityMap.aliasedIdentifiers: DisjointSet<Identifier>` (reference-identity), `StableSidemap.map: Map<IdentifierId, {isStable}>` (ID-keyed). **Similarity**: ~85%.1230DisjointSet becomes `DisjointSet<IdentifierId>`. The `isReactive()` side-effect pattern (sets `place.reactive = true` during reads) works in Rust as `fn is_reactive(&self, place: &mut Place) -> bool`  the ReactivityMap holds only IDs while `place` is mutably borrowed from the HIR, so borrows are disjoint. The fixpoint loop translates directly.12311232#### RewriteInstructionKindsBasedOnReassignment1233**Env usage**: None. **Side maps**: `declarations: Map<DeclarationId, LValue | LValuePattern>` stores references to lvalue objects for retroactive `.kind` mutation. **Similarity**: ~85%.1234The aliased-mutation-through-map pattern is best handled with a two-pass approach: Pass 1 collects `HashSet<DeclarationId>` of reassigned variables, Pass 2 assigns `InstructionKind` values. Or use `HashMap<DeclarationId, InstructionKind>` and apply in a final pass.12351236---12371238### Phase 9: Scope Construction12391240#### InferReactiveScopeVariables1241**Env usage**: `env.nextScopeId`, `env.config.enableForest`, `env.logger`. **Side maps**: `scopeIdentifiers: DisjointSet<Identifier>` (reference-identity), `declarations: Map<DeclarationId, Identifier>` (stores Identifier references), `scopes: Map<Identifier, ReactiveScope>` (reference keys). **Similarity**: ~75%.12421243**THE CRITICAL ALIASING PASS**: Line 132 `identifier.mutableRange = scope.range` creates the shared-MutableRange aliasing that all downstream scope passes depend on. In Rust with arenas: identifiers store `scope: Option<ScopeId>`. The "effective mutable range" is accessed via scope lookup. All downstream passes that read `mutableRange` access the scope arena via `env.scopes`. DisjointSet becomes `DisjointSet<IdentifierId>`, scopes map becomes `HashMap<IdentifierId, ScopeId>`.12441245#### MemoizeFbtAndMacroOperandsInSameScope1246**Env usage**: `fn.env.config.customMacros` (one read). **Side maps**: `macroKinds: Map<string, MacroDefinition>` (string keys), `macroTags: Map<IdentifierId, MacroDefinition>` (ID keys), `macroValues: Set<IdentifierId>` (IDs). **Similarity**: ~90%.1247All ID-keyed. The scope mutation (`operand.identifier.scope = scope`, `expandFbtScopeRange`) becomes `identifier.scope = Some(scope_id)` + `env.scopes[scope_id].range.start = min(...)`. The cyclic `MacroDefinition` structure can use arena indices or hardcoded match logic.12481249---12501251### Phase 10: Scope Alignment and Merging12521253#### AlignMethodCallScopes1254**Env usage**: None. **Side maps**: `scopeMapping: Map<IdentifierId, ReactiveScope | null>` (ID keys), `mergedScopes: DisjointSet<ReactiveScope>` (reference-identity). **Similarity**: ~90%.1255DisjointSet becomes `DisjointSet<ScopeId>`. Range merging through arena: `env.scopes[root_id].range.start = min(...)`. Scope rewriting: `identifier.scope = Some(root_id)`.12561257#### AlignObjectMethodScopes1258**Env usage**: None. **Side maps**: `objectMethodDecls: Set<Identifier>` (reference-identity), `DisjointSet<ReactiveScope>`. **Similarity**: ~88%.1259Same patterns as AlignMethodCallScopes. `Set<Identifier>` becomes `HashSet<IdentifierId>`. **Porting hazard**: The lvalue-only scope repointing (Phase 2b) relies on shared Identifier references. With arena-based identifiers where each Place has its own copy, repointing must cover ALL occurrences, not just lvalues. If using a central identifier arena (recommended), lvalue-only repointing is fine.12601261#### AlignReactiveScopesToBlockScopesHIR1262**Env usage**: None. **Side maps**: `activeScopes: Set<ReactiveScope>` (reference-identity, iterated while mutating `scope.range`), `seen: Set<ReactiveScope>`, `placeScopes: Map<Place, ReactiveScope>` (**dead code  never read**), `valueBlockNodes: Map<BlockId, ValueBlockNode>`. **Similarity**: ~85%.1263`activeScopes` becomes `HashSet<ScopeId>`. Scope mutation through arena: `for &scope_id in &active_scopes { env.scopes[scope_id].range.start = min(...); }`  perfectly clean borrows (HashSet is immutable, arena is mutable). The `placeScopes` map can be omitted entirely.12641265#### MergeOverlappingReactiveScopesHIR1266**Env usage**: None. **Side maps**: `joinedScopes: DisjointSet<ReactiveScope>` (reference-identity), `placeScopes: Map<Place, ReactiveScope>` (Place reference keys). **Similarity**: ~85%.1267DisjointSet becomes `DisjointSet<ScopeId>`. Same arena-based range merging pattern. Place-keyed maps become unnecessary with identifier-arena approach.12681269---12701271### Phase 11: Scope Terminal Construction12721273#### BuildReactiveScopeTerminalsHIR1274**Env usage**: None. **Side maps**: `rewrittenFinalBlocks: Map<BlockId, BlockId>` (IDs), `nextBlocks: Map<BlockId, BasicBlock>` (block storage), `queuedRewrites`. **Similarity**: ~85%.1275Complete blocks map replacement (`fn.body.blocks = nextBlocks`). Block splitting creates new blocks from instruction slices. Phi rewriting across old/new blocks. All structurally translatable.12761277#### FlattenReactiveLoopsHIR1278**Env usage**: None. **Side maps**: `activeLoops: Array<BlockId>` (IDs only). **Similarity**: ~98%.1279Simple terminal variant replacement (`scope`  `pruned-scope`). Uses `Vec::retain` for the active loops stack. ~40 lines of Rust logic. The terminal swap uses `std::mem::replace` or shared inner data struct.12801281#### FlattenScopesWithHooksOrUseHIR1282**Env usage**: `getHookKind(fn.env, ...)` (one hook resolution call). **Side maps**: `activeScopes: Array<{block, fallthrough}>`, `prune: Array<BlockId>` (both ID-only). **Similarity**: ~95%.1283Two-phase detect/rewrite. Stack-based scope tracking with `Vec::retain`. Terminal variant conversion. Very clean Rust translation.12841285---12861287### Phase 12: Scope Dependency Propagation12881289#### PropagateScopeDependenciesHIR1290**Env usage**: None directly. **Side maps**: `temporaries: Map<IdentifierId, ReactiveScopeDependency>` (ID-keyed, but `ReactiveScopeDependency` contains `identifier: Identifier` reference), `DependencyCollectionContext` with `#declarations: Map<DeclarationId, Decl>`, `#reassignments: Map<Identifier, Decl>` (reference keys), `deps: Map<ReactiveScope, Array<...>>` (reference keys). **Similarity**: ~80%.1291Reference-keyed maps become ID-keyed. `deps` becomes `HashMap<ScopeId, Vec<ReactiveScopeDependency>>`. The PropertyPathRegistry tree with parent pointers needs arena allocation. Scope mutation (`scope.declarations.set(...)`, `scope.dependencies.add(...)`) through arena.12921293---12941295### Phase 13: Reactive Function Construction12961297#### BuildReactiveFunction1298**Env usage**: Copies `fn.env` to reactive function. **Side maps**: Scheduling/traversal state during CFG-to-tree conversion. **Similarity**: ~80%.1299Major structural transformation (CFG  tree). The builder pattern works with `&mut` state. Deep recursion for value blocks is bounded by CFG depth. Shared Places/scopes/identifiers use arena indices in the new tree structure.13001301---13021303### Phase 14: Reactive Function Transforms13041305All reactive function transforms use the `ReactiveFunctionVisitor` / `ReactiveFunctionTransform` pattern.13061307**ReactiveFunctionVisitor/Transform pattern  Rust traits**:1308```rust1309trait ReactiveFunctionTransform {1310    type State;1311    fn transform_terminal(&mut self, stmt: &mut ReactiveTerminalStatement, state: &mut Self::State)1312        -> Transformed<ReactiveStatement> { Transformed::Keep }1313    fn transform_instruction(&mut self, stmt: &mut ReactiveInstructionStatement, state: &mut Self::State)1314        -> Transformed<ReactiveStatement> { Transformed::Keep }1315    // ... default implementations for traversal ...1316}13171318enum Transformed<T> {1319    Keep,1320    Remove,1321    Replace(T),1322    ReplaceMany(Vec<T>),1323}1324```13251326The `traverseBlock` method handles `ReplaceMany` by lazily building a new `Vec` (only allocating on first mutation). This maps to Rust's `Option<Vec<T>>` pattern.13271328Individual passes:13291330| Pass | Env | Side Maps | Similarity |1331|------|-----|-----------|------------|1332| PruneUnusedLabels | None | `Set<BlockId>` | ~95% |1333| PruneNonEscapingScopes | None | Dependency graph with cycle detection | ~85% |1334| PruneNonReactiveDependencies | None | None significant | ~95% |1335| PruneUnusedScopes | None | None significant | ~95% |1336| MergeReactiveScopesThatInvalidateTogether | None | Scope metadata comparison | ~85% |1337| PruneAlwaysInvalidatingScopes | None | None significant | ~95% |1338| PropagateEarlyReturns | None | Early return tracking state | ~85% |1339| PruneUnusedLValues | None | Lvalue usage tracking | ~90% |1340| PromoteUsedTemporaries | None | Identifier name mutation | ~90% |1341| ExtractScopeDeclarationsFromDestructuring | None | None significant | ~90% |1342| StabilizeBlockIds | None | `Map<BlockId, BlockId>` remapping | ~95% |1343| RenameVariables | None | Name collision tracking | ~90% |1344| PruneHoistedContexts | None | Context declaration tracking | ~95% |13451346---13471348### Phase 15: Codegen13491350#### CodegenReactiveFunction1351**Env usage**: `env.programContext` (imports, bindings), `env.getOutlinedFunctions()`, `env.recordErrors()`, `env.config`. **Side maps**: Context class with cache slot management, scope metadata tracking. **Similarity**: ~60%.13521353**The most significantly different pass** due to AST output generation. 1000+ lines of `t.*()` Babel API calls are replaced with constructing Rust Babel AST types that serialize to JSON via serde. Core scope logic (cache slot allocation, dependency checking, memoization code structure) can look structurally similar.13541355The `uniqueIdentifiers` and `fbtOperands` parameters translate directly.13561357---13581359### Validation Passes13601361~15 validation passes share a common pattern: read-only HIR/ReactiveFunction traversal + error reporting via `env.recordError()`. They are the **easiest passes to port**. Common structure:13621363```rust1364fn validate_hooks_usage(func: &HIRFunction, env: &mut Environment) -> Result<(), ()> {1365    for block in func.body.blocks.values() {1366        for instr in &block.instructions {1367            match &instr.value {1368                // check for violations, record errors1369            }1370        }1371    }1372    Ok(())1373}1374```13751376All use `HashMap<IdentifierId, T>` for state tracking (ID-keyed, safe). Some return `CompilerError` directly instead of recording. The `tryRecord()` wrapping pattern maps to `Result` in Rust.13771378---13791380## External Dependencies13811382### Input/Output: JSON AST Interchange13831384The Rust compiler defines its own representation of the Babel AST format using serde with custom serialization/deserialization, ensuring the `"type"` field is always produced (even outside of enum positions). Input ASTs are deserialized from JSON, and output ASTs are serialized back to JSON for consumption by the Babel plugin. A `Scope` type encodes the scope tree information that Babel provides. The main public API is `compile(BabelAst, Scope) -> Option<BabelAst>`, returning `None` if no changes are needed.13851386This approach decouples the Rust compiler from any specific JS parser  the JSON boundary handles the translation. The `resolveBinding()` pattern in BuildHIR (which uses Babel node reference equality in TypeScript) maps to scope-tree lookups via the `Scope` type.13871388---13891390## Risk Assessment13911392### Low Risk (straightforward port)1393- All validation passes1394- Simple transformation passes (PruneMaybeThrows, PruneUnusedLabelsHIR, FlattenReactiveLoopsHIR, FlattenScopesWithHooksOrUseHIR, StabilizeBlockIds, RewriteInstructionKindsBasedOnReassignment, OptimizePropsMethodCalls, MergeConsecutiveBlocks)1395- Reactive pruning passes (PruneUnusedLabels, PruneUnusedScopes, PruneAlwaysInvalidatingScopes, PruneNonReactiveDependencies)13961397### Medium Risk (requires systematic refactoring)1398- SSA passes (EnterSSA, EliminateRedundantPhi)  reference-identity maps  ID maps1399- Scope construction passes  centralized scope arena with ID-based references1400- Type inference (InferTypes)  arena-based Type storage, TypeId generation1401- Constant propagation  separated constants map, CFG cleanup infrastructure1402- Dead code elimination  two-phase collect/apply1403- Scope alignment passes  DisjointSet<ScopeId>, arena-based range mutation1404- Reactive function transforms  Visitor/MutVisitor trait design with Transformed enum14051406### Medium Risk *(additional)*1407- **InferMutationAliasingEffects**: After [PR #33650](https://github.com/facebook/react/pull/33650), allocation-site identity uses interned `AliasingEffect` (→ `EffectId`), eliminating `InstructionValue` keys and `effectInstructionValueCache`. Remaining reference-identity maps use Instructions (→ `InstructionId`) and FunctionExpressions (→ `FunctionId`). All become copyable ID-keyed maps. Place sharing between effects and instructions is resolved by cloning (cheap with arena-based identifiers). `CreateFunction`'s FunctionExpression reference becomes a `FunctionId` referencing the function arena. Fixpoint loop and abstract interpretation structure port directly. See [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership) for full analysis.14081409### High Risk (significant redesign)1410- **BuildHIR**: JSON AST deserialization, scope tree integration, closure-heavy builder patterns1411- **InferMutationAliasingRanges**: Graph-through-HIR mutation, temporal reasoning, deferred range updates1412- **CodegenReactiveFunction**: JSON AST output construction via serde, 1000+ lines of AST building1413- **AnalyseFunctions**: Recursive nested function processing via function arena, shared mutableRange semantics14141415### Critical Architectural Decisions (must be designed upfront)14161. **Arena-based storage on Environment**: Identifiers, scopes, functions, and types are stored as flat `Vec` fields on `Environment`, referenced by copyable ID types (`IdentifierId`, `ScopeId`, `FunctionId`, `TypeId`). Affects every pass.14172. **Instructions table**: Instructions stored in flat `Vec<Instruction>` on `HIRFunction`, referenced by `InstructionId`. Old `InstructionId` renamed to `EvaluationOrder`.14183. **Scope-based mutableRange access**: After InferReactiveScopeVariables, effective mutable range = scope's range. All downstream `isMutable()`/`inRange()` calls access the scope arena via `env.scopes`.14194. **JSON AST interchange**: Input/output via serde-serialized Babel AST types and a `Scope` type for scope tree information.14205. **Environment as single `&mut`**: No sub-struct grouping  flat fields allow precise sliced borrows. Passed separately from `HIRFunction`.14216. **Error handling**: `Result<_, CompilerDiagnostic>` for thrown errors, accumulated errors on `Environment`.14221423---14241425## Recommended Migration Strategy14261427### Phase 1: Foundation14281. Define Rust data model (flat `Environment` with arena fields for Identifiers/Scopes/Functions/Types, all ID newtypes)14292. Define HIR types as Rust enums/structs (InstructionValue ~40 variants, Terminal ~20 variants)14303. Define flat `Environment` struct with arena fields, counters, config, and accumulated state14314. Implement shared infrastructure: `DisjointSet<T: Copy>`, `IndexMap` wrappers, visitor utilities14325. Define Babel AST types with serde serialization/deserialization for JSON AST interchange14336. Build JSON serialization for HIR (enables testing against TypeScript implementation)14341435### Phase 2: Core Pipeline14361. Port BuildHIR (highest effort, most value  requires JSON AST deserialization and Scope type integration)14372. Port normalization passes (PruneMaybeThrows, MergeConsecutiveBlocks  simple, builds confidence)14383. Port SSA (EnterSSA, EliminateRedundantPhi  establishes arena patterns)14394. Port ConstantPropagation, InferTypes14405. Validate output matches TypeScript via JSON comparison at each stage14411442### Phase 3: Analysis Engine14431. Port AnalyseFunctions (establishes recursive compilation pattern)14442. Port InferMutationAliasingEffects (establish EffectId interning table  EffectId serves as allocation-site identity, FunctionId-based function arena access for CreateFunction)14453. Port DeadCodeElimination14464. Port InferMutationAliasingRanges (establish deferred-range-update pattern)14475. Port InferReactivePlaces14481449### Phase 4: Scope System14501. Port InferReactiveScopeVariables (establishes ScopeId  mutableRange indirection)14512. Port scope alignment passes (Align*, Merge*  establish DisjointSet<ScopeId> pattern)14523. Port BuildReactiveScopeTerminalsHIR14534. Port PropagateScopeDependenciesHIR14541455### Phase 5: Output14561. Port BuildReactiveFunction (establishes reactive tree representation)14572. Port reactive function transforms (Prune*, Promote*, Rename*  use trait-based visitor)14583. Port CodegenReactiveFunction with JSON AST output14594. Port validation passes (easiest, can be done in parallel)14605. End-to-end integration testing

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.