compiler/docs/rust-port/rust-port-architecture.md MARKDOWN 156 lines View on github.com → Search inside
1# Rust Port: Architecture Guide23Reference for key data structures, patterns, and constraints in the Rust compiler port. See `rust-port-research.md` for detailed per-pass analysis and `rust-port-notes.md` for the original design decisions.45## Arenas and ID Types67All shared mutable data is stored in arenas on `Environment`, referenced by copyable ID types. This replaces JavaScript's shared object references.89| Arena | ID Type | Stored On | Replaces |10|-------|---------|-----------|----------|11| `identifiers: Vec<Identifier>` | `IdentifierId` | `Environment` | Shared `Identifier` object references across `Place` values |12| `scopes: Vec<ReactiveScope>` | `ScopeId` | `Environment` | Shared `ReactiveScope` references across identifiers |13| `functions: Vec<HIRFunction>` | `FunctionId` | `Environment` | Inline `HIRFunction` on `FunctionExpression`/`ObjectMethod` |14| `types: Vec<Type>` | `TypeId` | `Environment` | Inline `Type` on `Identifier` |1516All ID types are `Copy + Clone + Hash + Eq + PartialEq` newtypes wrapping `u32`.1718## Instructions and EvaluationOrder1920- `HirFunction.instructions: Vec<Instruction>`  flat instruction table21- `BasicBlock.instructions: Vec<InstructionId>`  indices into the table above22- The old TypeScript `InstructionId` is renamed to `EvaluationOrder`  it represents evaluation order and appears on both instructions and terminals23- The new `InstructionId` is an index into `HirFunction.instructions`, giving passes a single copyable ID to reference any instruction2425## Place is Clone, MutableRange is on Identifier/Scope2627`Place` stores an `IdentifierId` (not a shared reference), making it small and cheap to clone. Mutation of `mutable_range` goes through the identifier arena:2829```rust30env.identifiers[place.identifier].mutable_range.end = new_end;31```3233After `InferReactiveScopeVariables`, an identifier's effective mutable range is its scope's range. Downstream passes access this through the scope arena:3435```rust36let range = match env.identifiers[id].scope {37    Some(scope_id) => env.scopes[scope_id].range,38    None => env.identifiers[id].mutable_range,39};40```4142## Function Arena and FunctionId4344`FunctionExpression` and `ObjectMethod` instruction values store a `FunctionId` instead of an inline `HIRFunction`. Inner functions are accessed via the arena:4546```rust47let inner = &env.functions[function_id];       // read48let inner = &mut env.functions[function_id];    // write49```5051This makes `CreateFunction` aliasing effects store `FunctionId`, and function signature caches key by `FunctionId`.5253## AliasingEffect5455Effects own cloned `Place` values (cheap since `Place` contains `IdentifierId`). Key variants:5657- `Apply`  clones the args `Vec<PlaceOrSpreadOrHole>` from the instruction value58- `CreateFunction`  stores `FunctionId` (not the `FunctionExpression` itself), plus cloned `captures: Vec<Place>`5960Effect interning uses content hashing. The interned `EffectId` serves as both dedup key and allocation-site identity for abstract interpretation in `InferMutationAliasingEffects`.6162## Environment: Separate from HirFunction6364`HirFunction` does not store `env`. Passes receive `env: &mut Environment` as a separate parameter. Fields are flat (no sub-structs) to allow precise sliced borrows:6566```rust67// Simultaneous borrow of different fields is fine:68let id = &env.identifiers[some_id];69let scope = &env.scopes[some_scope_id];70```7172## Ordered Maps7374Use `IndexMap`/`IndexSet` (from the `indexmap` crate) wherever the TypeScript uses `Map`/`Set` and iteration order matters. The primary case is `HIR.blocks: IndexMap<BlockId, BasicBlock>` which maintains reverse postorder.7576## Side Maps7778Side maps fall into four categories:79801. **ID-only maps**  `HashMap<IdType, T>` / `HashSet<IdType>`. No borrow issues. Most passes use this.812. **Reference-identity maps**  TypeScript `Map<Identifier, T>` becomes `HashMap<IdentifierId, T>`. Similarly `DisjointSet<Identifier>` becomes `DisjointSet<IdentifierId>`, `DisjointSet<ReactiveScope>` becomes `DisjointSet<ScopeId>`.823. **Instruction/value reference maps**  Store `InstructionId` or `FunctionId` instead of references. Access the actual data through the instruction table or function arena when needed.834. **Scope reference sets with mutation**  Store `ScopeId` in sets. Mutate through the arena: `env.scopes[scope_id].range.start = new_start`.8485When a pass needs to both iterate over data and mutate the HIR, use two-phase collect/apply: collect IDs or updates into a `Vec`, then apply mutations in a second loop.8687## Error Handling8889| TypeScript Pattern | Rust Approach |90|---|---|91| Non-null assertion (`!`) | `.unwrap()` (panic) |92| `CompilerError.invariant()`, `CompilerError.throwTodo()`, `throw ...` | Return `Err(CompilerDiagnostic)` via `Result` |93| `env.recordError()` or `pushDiagnostic()` with an invariant error | Return `Err(CompilerDiagnostic)` |94| `env.recordError()` or `pushDiagnostic()` with a NON invariant error | Keep as-is  accumulate on `Environment` |9596Preserve full error details: reason, description, location, suggestions, category. 9798## JSRust Boundary99100The JS side serializes the Babel AST and Babel's scope information (scope tree, bindings, reference-to-binding map) to Rust. Keep this serialization thin: only send the core data structures that Babel already computed during parsing. Any derived analysis — identifier source locations, JSX classification, captured variables, etc. — should be computed on the Rust side by walking the AST. See `scope.ts`.101102## Pipeline and Pass Structure103104```rust105fn compile(ast: Ast, scope: Scope, env: &mut Environment)106    -> Result<CompileResult, CompilerDiagnostic>107{108    let mut hir = lower(ast, scope, env)?;109    some_pass(&mut hir, env)?;110    // ...111    let ast = codegen(...)?;112113    if env.has_errors() {114        Ok(CompileResult::Failure(env.take_errors()))115    } else {116        Ok(CompileResult::Success(ast))117    }118}119```120121Pass signatures follow these patterns:122123```rust124// Most passes: mutable HIR + mutable environment125fn pass(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic>;126127// Passes that don't need env128fn pass(func: &mut HirFunction);129130// Validation passes: read-only HIR, env for error recording131fn validate(func: &HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic>;132```133134Use `?` to propagate errors that would have thrown or short-circuited in TypeScript. Non-fatal errors are accumulated on `env` and checked at the end via `env.has_errors()`.135136## Structural Similarity to TypeScript137138Target ~85-95% structural correspondence. A developer should be able to view TypeScript and Rust side-by-side and trace the logic. The ported code should preserve:139140- **Same high-level data flow** through the code. Only deviate where strictly necessary due to data model differences (arenas, borrow checker workarounds, etc.).141- **Same grouping of types, functions, and "classes" (structs with methods) into files.** A TypeScript file maps to a Rust file with the same logical contents.142- **Similar filenames, type names, and identifier names**, adjusted for Rust naming conventions (`camelCase` -> `snake_case` for functions/variables, `PascalCase` preserved for types).143- **Crate structure**: The monolithic `babel-plugin-react-compiler` package is split into crates, roughly 1:1 by top-level folder (e.g., `src/HIR/` -> a crate, `src/Inference/` -> a crate, etc.). We split the lowering logic (BuildHIR and HIRBuilder) into react_compiler_lowering bc of its complexity.144145Key mechanical translations:146147| TypeScript | Rust |148|---|---|149| `switch (value.kind)` | `match &value` (exhaustive) |150| `Map<Identifier, T>` | `HashMap<IdentifierId, T>` |151| `for...of` with `Set.delete()` | `set.retain(\|x\| ...)` |152| `instr.value = { kind: 'X', ... }` | `std::mem::replace` + reconstruct |153| `{ ...place, effect: Effect.Read }` | `Place { effect: Effect::Read, ..place.clone() }` |154| `array.filter(x => ...)` | `vec.retain(\|x\| ...)` |155| `identifier.mutableRange.end = x` | `env.identifiers[id].mutable_range.end = x` |156| Builder closures setting outer variables | Return values from closures |

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.