| 1 | # Rust Port: ReactiveFunction and Reactive Passes |
| 2 | |
| 3 | Current status: **Phase 2 In Progress** — Reactive types, crate skeleton, TS/Rust debug printers, and BuildReactiveFunction are implemented. 1458/1717 fixtures pass (85%). Remaining failures are mostly earlier-pass error propagation differences and a few loop scheduling edge cases. |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | This document covers porting the reactive function representation and all passes from `BuildReactiveFunction` through `CodegenReactiveFunction` from TypeScript to Rust. |
| 8 | |
| 9 | The reactive function is a tree-structured IR derived from the HIR CFG. `BuildReactiveFunction` converts the flat CFG into a nested tree where control flow constructs (if/switch/loops/try) and reactive scopes are represented as nested blocks rather than block references. Subsequent passes prune, merge, and transform scopes, then codegen converts the tree to output AST. |
| 10 | |
| 11 | ## 1. Rust Type Representation |
| 12 | |
| 13 | **Location**: New file `compiler/crates/react_compiler_hir/src/reactive.rs`, re-exported from `lib.rs` |
| 14 | |
| 15 | All types derive `Debug, Clone`. |
| 16 | |
| 17 | ### ReactiveFunction |
| 18 | |
| 19 | ```rust |
| 20 | /// Tree representation of a compiled function, converted from the CFG-based HIR. |
| 21 | /// TS: ReactiveFunction in HIR.ts |
| 22 | pub struct ReactiveFunction { |
| 23 | pub loc: Option<SourceLocation>, |
| 24 | pub id: Option<String>, |
| 25 | pub name_hint: Option<String>, |
| 26 | pub params: Vec<ParamPattern>, |
| 27 | pub generator: bool, |
| 28 | pub is_async: bool, |
| 29 | pub body: ReactiveBlock, |
| 30 | pub directives: Vec<String>, |
| 31 | // No env field — passed separately per established Rust convention |
| 32 | } |
| 33 | ``` |
| 34 | |
| 35 | ### ReactiveBlock and ReactiveStatement |
| 36 | |
| 37 | ```rust |
| 38 | /// TS: ReactiveBlock = Array<ReactiveStatement> |
| 39 | pub type ReactiveBlock = Vec<ReactiveStatement>; |
| 40 | |
| 41 | /// TS: ReactiveStatement (discriminated union with 'kind' field) |
| 42 | pub enum ReactiveStatement { |
| 43 | Instruction(ReactiveInstruction), |
| 44 | Terminal(ReactiveTerminalStatement), |
| 45 | Scope(ReactiveScopeBlock), |
| 46 | PrunedScope(PrunedReactiveScopeBlock), |
| 47 | } |
| 48 | ``` |
| 49 | |
| 50 | ### ReactiveInstruction and ReactiveValue |
| 51 | |
| 52 | ```rust |
| 53 | /// TS: ReactiveInstruction |
| 54 | pub struct ReactiveInstruction { |
| 55 | pub id: EvaluationOrder, // TS InstructionId = Rust EvaluationOrder |
| 56 | pub lvalue: Option<Place>, |
| 57 | pub value: ReactiveValue, |
| 58 | pub effects: Option<Vec<AliasingEffect>>, |
| 59 | pub loc: Option<SourceLocation>, |
| 60 | } |
| 61 | |
| 62 | /// Extends InstructionValue with compound expression types that were |
| 63 | /// separate blocks+terminals in HIR but become nested expressions here. |
| 64 | /// TS: ReactiveValue = InstructionValue | ReactiveLogicalValue | ... |
| 65 | pub enum ReactiveValue { |
| 66 | /// All ~35 base instruction value kinds |
| 67 | Instruction(InstructionValue), |
| 68 | |
| 69 | /// TS: ReactiveLogicalValue |
| 70 | LogicalExpression { |
| 71 | operator: LogicalOperator, |
| 72 | left: Box<ReactiveValue>, |
| 73 | right: Box<ReactiveValue>, |
| 74 | loc: Option<SourceLocation>, |
| 75 | }, |
| 76 | |
| 77 | /// TS: ReactiveTernaryValue |
| 78 | ConditionalExpression { |
| 79 | test: Box<ReactiveValue>, |
| 80 | consequent: Box<ReactiveValue>, |
| 81 | alternate: Box<ReactiveValue>, |
| 82 | loc: Option<SourceLocation>, |
| 83 | }, |
| 84 | |
| 85 | /// TS: ReactiveSequenceValue |
| 86 | SequenceExpression { |
| 87 | instructions: Vec<ReactiveInstruction>, |
| 88 | id: EvaluationOrder, |
| 89 | value: Box<ReactiveValue>, |
| 90 | loc: Option<SourceLocation>, |
| 91 | }, |
| 92 | |
| 93 | /// TS: ReactiveOptionalCallValue |
| 94 | OptionalExpression { |
| 95 | id: EvaluationOrder, |
| 96 | value: Box<ReactiveValue>, |
| 97 | optional: bool, |
| 98 | loc: Option<SourceLocation>, |
| 99 | }, |
| 100 | } |
| 101 | ``` |
| 102 | |
| 103 | ### Terminals |
| 104 | |
| 105 | ```rust |
| 106 | pub struct ReactiveTerminalStatement { |
| 107 | pub terminal: ReactiveTerminal, |
| 108 | pub label: Option<ReactiveLabel>, |
| 109 | } |
| 110 | |
| 111 | pub struct ReactiveLabel { |
| 112 | pub id: BlockId, |
| 113 | pub implicit: bool, |
| 114 | } |
| 115 | |
| 116 | pub enum ReactiveTerminalTargetKind { |
| 117 | Implicit, |
| 118 | Labeled, |
| 119 | Unlabeled, |
| 120 | } |
| 121 | |
| 122 | pub enum ReactiveTerminal { |
| 123 | Break { |
| 124 | target: BlockId, |
| 125 | id: EvaluationOrder, |
| 126 | target_kind: ReactiveTerminalTargetKind, |
| 127 | loc: Option<SourceLocation>, |
| 128 | }, |
| 129 | Continue { |
| 130 | target: BlockId, |
| 131 | id: EvaluationOrder, |
| 132 | target_kind: ReactiveTerminalTargetKind, |
| 133 | loc: Option<SourceLocation>, |
| 134 | }, |
| 135 | Return { |
| 136 | value: Place, |
| 137 | id: EvaluationOrder, |
| 138 | loc: Option<SourceLocation>, |
| 139 | }, |
| 140 | Throw { |
| 141 | value: Place, |
| 142 | id: EvaluationOrder, |
| 143 | loc: Option<SourceLocation>, |
| 144 | }, |
| 145 | Switch { |
| 146 | test: Place, |
| 147 | cases: Vec<ReactiveSwitchCase>, |
| 148 | id: EvaluationOrder, |
| 149 | loc: Option<SourceLocation>, |
| 150 | }, |
| 151 | DoWhile { |
| 152 | loop_block: ReactiveBlock, // "loop" is a Rust keyword |
| 153 | test: ReactiveValue, |
| 154 | id: EvaluationOrder, |
| 155 | loc: Option<SourceLocation>, |
| 156 | }, |
| 157 | While { |
| 158 | test: ReactiveValue, |
| 159 | loop_block: ReactiveBlock, |
| 160 | id: EvaluationOrder, |
| 161 | loc: Option<SourceLocation>, |
| 162 | }, |
| 163 | For { |
| 164 | init: ReactiveValue, |
| 165 | test: ReactiveValue, |
| 166 | update: Option<ReactiveValue>, |
| 167 | loop_block: ReactiveBlock, |
| 168 | id: EvaluationOrder, |
| 169 | loc: Option<SourceLocation>, |
| 170 | }, |
| 171 | ForOf { |
| 172 | init: ReactiveValue, |
| 173 | test: ReactiveValue, |
| 174 | loop_block: ReactiveBlock, |
| 175 | id: EvaluationOrder, |
| 176 | loc: Option<SourceLocation>, |
| 177 | }, |
| 178 | ForIn { |
| 179 | init: ReactiveValue, |
| 180 | loop_block: ReactiveBlock, |
| 181 | id: EvaluationOrder, |
| 182 | loc: Option<SourceLocation>, |
| 183 | }, |
| 184 | If { |
| 185 | test: Place, |
| 186 | consequent: ReactiveBlock, |
| 187 | alternate: Option<ReactiveBlock>, |
| 188 | id: EvaluationOrder, |
| 189 | loc: Option<SourceLocation>, |
| 190 | }, |
| 191 | Label { |
| 192 | block: ReactiveBlock, |
| 193 | id: EvaluationOrder, |
| 194 | loc: Option<SourceLocation>, |
| 195 | }, |
| 196 | Try { |
| 197 | block: ReactiveBlock, |
| 198 | handler_binding: Option<Place>, |
| 199 | handler: ReactiveBlock, |
| 200 | id: EvaluationOrder, |
| 201 | loc: Option<SourceLocation>, |
| 202 | }, |
| 203 | } |
| 204 | |
| 205 | pub struct ReactiveSwitchCase { |
| 206 | pub test: Option<Place>, |
| 207 | pub block: Option<ReactiveBlock>, // TS: ReactiveBlock | void |
| 208 | } |
| 209 | ``` |
| 210 | |
| 211 | ### Scope Blocks |
| 212 | |
| 213 | ```rust |
| 214 | pub struct ReactiveScopeBlock { |
| 215 | pub scope: ScopeId, // Arena pattern: scope data in Environment |
| 216 | pub instructions: ReactiveBlock, |
| 217 | } |
| 218 | |
| 219 | pub struct PrunedReactiveScopeBlock { |
| 220 | pub scope: ScopeId, |
| 221 | pub instructions: ReactiveBlock, |
| 222 | } |
| 223 | ``` |
| 224 | |
| 225 | ### Reused Existing Types |
| 226 | |
| 227 | All of these are already defined in `react_compiler_hir`: |
| 228 | - `Place`, `InstructionValue`, `AliasingEffect`, `LogicalOperator`, `ParamPattern` |
| 229 | - `BlockId`, `ScopeId`, `IdentifierId`, `EvaluationOrder`, `TypeId`, `FunctionId` |
| 230 | - `SourceLocation` (from `react_compiler_diagnostics`) |
| 231 | - `ReactiveScope`, `ReactiveScopeDependency`, `ReactiveScopeDeclaration`, `ReactiveScopeEarlyReturn` |
| 232 | |
| 233 | ### Key Design Decisions |
| 234 | |
| 235 | 1. **ReactiveValue wraps InstructionValue**: `ReactiveValue::Instruction(InstructionValue)` wraps the existing ~35-variant enum. Passes that match specific kinds use `ReactiveValue::Instruction(InstructionValue::FunctionExpression { .. })`. |
| 236 | |
| 237 | 2. **Box for recursive types**: `ReactiveValue` fields use `Box<ReactiveValue>` for recursion. `ReactiveBlock` (Vec) naturally heap-allocates, breaking the size cycle for terminals. |
| 238 | |
| 239 | 3. **ScopeId, not cloned scope**: `ReactiveScopeBlock` stores `ScopeId`. Scope data lives in `env.scopes[scope_id]`. Passes that read/write scope data access it through the environment. |
| 240 | |
| 241 | 4. **No Environment on ReactiveFunction**: Passes take `env: &Environment` or `env: &mut Environment` as a separate parameter, following the established Rust pattern. |
| 242 | |
| 243 | 5. **EvaluationOrder, not InstructionId**: The TS `InstructionId` (evaluation order counter) maps to Rust `EvaluationOrder`. Rust's `InstructionId` is the flat instruction table index (not used in reactive types). |
| 244 | |
| 245 | ## 2. New Crate: `react_compiler_reactive_scopes` |
| 246 | |
| 247 | ``` |
| 248 | compiler/crates/react_compiler_reactive_scopes/ |
| 249 | Cargo.toml |
| 250 | src/ |
| 251 | lib.rs |
| 252 | build_reactive_function.rs |
| 253 | print_reactive_function.rs |
| 254 | visitors.rs |
| 255 | assert_well_formed_break_targets.rs |
| 256 | assert_scope_instructions_within_scopes.rs |
| 257 | prune_unused_labels.rs |
| 258 | prune_non_escaping_scopes.rs |
| 259 | prune_non_reactive_dependencies.rs |
| 260 | prune_unused_scopes.rs |
| 261 | merge_reactive_scopes_that_invalidate_together.rs |
| 262 | prune_always_invalidating_scopes.rs |
| 263 | propagate_early_returns.rs |
| 264 | prune_unused_lvalues.rs |
| 265 | promote_used_temporaries.rs |
| 266 | extract_scope_declarations_from_destructuring.rs |
| 267 | stabilize_block_ids.rs |
| 268 | rename_variables.rs |
| 269 | prune_hoisted_contexts.rs |
| 270 | validate_preserved_manual_memoization.rs |
| 271 | ``` |
| 272 | |
| 273 | **Cargo.toml dependencies**: `react_compiler_hir`, `react_compiler_diagnostics`, `indexmap` |
| 274 | |
| 275 | Add to workspace `Cargo.toml` members and as dependency of `react_compiler`. |
| 276 | |
| 277 | Maps to TS directory: `src/ReactiveScopes/` |
| 278 | |
| 279 | ## 3. Debug Printing |
| 280 | |
| 281 | ### Approach: New Verbose Format (like DebugPrintHIR) |
| 282 | |
| 283 | Create a new verbose `DebugPrintReactiveFunction` format that prints every field of every type recursively, analogous to `DebugPrintHIR`. Both TS and Rust need new implementations. |
| 284 | |
| 285 | ### TS Side |
| 286 | |
| 287 | Create `compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintReactiveFunction.ts`: |
| 288 | |
| 289 | - Entry point: `export function printDebugReactiveFunction(fn: ReactiveFunction): string` |
| 290 | - Uses the same `DebugPrinter` class from `DebugPrintHIR.ts` |
| 291 | - Prints function metadata: id, name_hint, generator, async, loc, params (full Place detail), directives |
| 292 | - Recursively prints `fn.body` (ReactiveBlock): |
| 293 | - `ReactiveInstruction`: id, lvalue (full Place with identifier declaration), value, effects, loc |
| 294 | - `ReactiveScopeBlock`/`PrunedReactiveScopeBlock`: full scope detail (id, range, dependencies with paths and locs, declarations with identifier info, reassignments, earlyReturnValue, merged, loc), then nested instructions |
| 295 | - `ReactiveTerminalStatement`: label info, terminal kind, all fields including nested blocks |
| 296 | - `ReactiveValue` compound types: kind, all fields recursively; `Instruction` variant delegates to `formatInstructionValue` |
| 297 | - Appends outlined functions and Environment errors (same pattern as DebugPrintHIR) |
| 298 | - Reuses shared formatters: `formatPlace`, `formatIdentifier`, `formatType`, `formatLoc`, `formatAliasingEffect`, `formatInstructionValue` |
| 299 | - Export from `compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts` |
| 300 | |
| 301 | ### Rust Side |
| 302 | |
| 303 | `compiler/crates/react_compiler_reactive_scopes/src/print_reactive_function.rs`: |
| 304 | |
| 305 | - Entry point: `pub fn debug_reactive_function(func: &ReactiveFunction, env: &Environment) -> String` |
| 306 | - Uses the `DebugPrinter` struct pattern from `compiler/crates/react_compiler/src/debug_print.rs` |
| 307 | - Must produce output identical to the TS `printDebugReactiveFunction` |
| 308 | |
| 309 | ### Shared Print Helpers |
| 310 | |
| 311 | Extract these as `pub` from `compiler/crates/react_compiler/src/debug_print.rs` (currently private): |
| 312 | - `format_place(place, env) -> String` |
| 313 | - `format_identifier(id, env) -> String` |
| 314 | - `format_type(type_id, env) -> String` |
| 315 | - `format_loc(loc) -> String` |
| 316 | - `format_aliasing_effect(effect) -> String` |
| 317 | - `format_instruction_value(value, env, indent) -> Vec<String>` |
| 318 | - The `DebugPrinter` struct itself (or extract to a shared module) |
| 319 | |
| 320 | ## 4. Test Infrastructure Changes |
| 321 | |
| 322 | ### `compiler/scripts/test-rust-port.ts` |
| 323 | |
| 324 | 1. **Import** `printDebugReactiveFunction` from the new TS file |
| 325 | |
| 326 | 2. **Handle `kind: 'reactive'`** — replace the `throw new Error(...)` at lines 297-305: |
| 327 | ```typescript |
| 328 | } else if (entry.kind === 'reactive') { |
| 329 | log.push({ |
| 330 | kind: 'entry', |
| 331 | name: entry.name, |
| 332 | value: printDebugReactiveFunction(entry.value), |
| 333 | }); |
| 334 | } |
| 335 | ``` |
| 336 | |
| 337 | 3. **Handle `kind: 'ast'`** — keep the TODO error for now (codegen is deferred) |
| 338 | |
| 339 | 4. **ID normalization** — the existing `normalizeIds` function handles `bb\d+`, `@\d+`, `Identifier(\d+)`, `Type(\d+)`, `\w+\$\d+`, `mutableRange` patterns. Should work for reactive output. Verify after BuildReactiveFunction is ported; may need additional patterns for scope-specific fields in the verbose format. |
| 340 | |
| 341 | ### Rust Pipeline (`pipeline.rs`) |
| 342 | |
| 343 | After `PropagateScopeDependenciesHIR`, transition from HIR to ReactiveFunction: |
| 344 | |
| 345 | ```rust |
| 346 | let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(&hir, &env); |
| 347 | let debug = react_compiler_reactive_scopes::debug_reactive_function(&reactive_fn, &env); |
| 348 | context.log_debug(DebugLogEntry::new("BuildReactiveFunction", debug)); |
| 349 | |
| 350 | react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn)?; |
| 351 | context.log_debug(DebugLogEntry::new("AssertWellFormedBreakTargets", "ok".to_string())); |
| 352 | |
| 353 | react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn); |
| 354 | let debug = react_compiler_reactive_scopes::debug_reactive_function(&reactive_fn, &env); |
| 355 | context.log_debug(DebugLogEntry::new("PruneUnusedLabels", debug)); |
| 356 | |
| 357 | // ... etc for each pass |
| 358 | ``` |
| 359 | |
| 360 | ## 5. Phased Porting Plan |
| 361 | |
| 362 | ### Phase 1 — Foundation |
| 363 | |
| 364 | 1. Create `reactive.rs` in `react_compiler_hir` with all types from Section 1 |
| 365 | 2. Create `react_compiler_reactive_scopes` crate skeleton with `Cargo.toml` and empty `lib.rs` |
| 366 | 3. Create TS `DebugPrintReactiveFunction.ts` with verbose format |
| 367 | 4. Extract shared print helpers from `debug_print.rs` as public |
| 368 | 5. Port verbose format to Rust `print_reactive_function.rs` |
| 369 | 6. Update `test-rust-port.ts` to handle `kind: 'reactive'` |
| 370 | |
| 371 | ### Phase 2 — BuildReactiveFunction |
| 372 | |
| 373 | The critical pass (~700 lines). Converts HIR CFG to ReactiveFunction tree. |
| 374 | |
| 375 | - **Source**: `compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts` |
| 376 | - **Target**: `compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs` |
| 377 | - **Key structures to port**: |
| 378 | - `Context` class: tracks `emitted: Set<BlockId>`, `scopeFallthroughs: Set<BlockId>`, `#scheduled: Set<BlockId>`, `#catchHandlers: Set<BlockId>`, `#controlFlowStack: Array<ControlFlowTarget>` |
| 379 | - `Driver` class: `traverseBlock`, `visitBlock`, `visitValueBlock`, `visitValueBlockTerminal`, `visitTestBlock`, `extractValueBlockResult`, `wrapWithSequence`, `visitBreak`, `visitContinue` |
| 380 | - **Signature**: `pub fn build_reactive_function(hir: &HirFunction, env: &Environment) -> ReactiveFunction` |
| 381 | - **Wire into pipeline.rs** |
| 382 | - **Test**: `bash compiler/scripts/test-rust-port.sh BuildReactiveFunction` |
| 383 | |
| 384 | ### Phase 3 — Validation Passes |
| 385 | |
| 386 | - `assert_well_formed_break_targets` (~30 lines) — checks break/continue targets exist |
| 387 | - `assert_scope_instructions_within_scopes` (~80 lines) — validates scope ranges contain instructions |
| 388 | |
| 389 | ### Phase 4 — Simple Transforms (pipeline order) |
| 390 | |
| 391 | 1. `prune_unused_labels` (~50 lines) — removes unnecessary labels emitted by BuildReactiveFunction |
| 392 | 2. `prune_non_reactive_dependencies` (~40 lines) — removes non-reactive deps from scopes |
| 393 | 3. `prune_unused_scopes` (~60 lines) — converts scopes without outputs to pruned-scopes |
| 394 | 4. `prune_always_invalidating_scopes` (~80 lines) — removes always-invalidating scopes |
| 395 | 5. `prune_unused_lvalues` (~70 lines) — nulls out unused lvalues |
| 396 | 6. `stabilize_block_ids` (~60 lines) — renumbers block IDs for stable output |
| 397 | |
| 398 | ### Phase 5 — Complex Transforms (pipeline order) |
| 399 | |
| 400 | 1. `prune_non_escaping_scopes` (~500 lines) — most complex reactive pass, removes scopes for non-escaping values |
| 401 | 2. `merge_reactive_scopes_that_invalidate_together` (~400 lines) — merges adjacent scopes with same deps |
| 402 | 3. `propagate_early_returns` (~200 lines) — handles early returns inside reactive scopes |
| 403 | 4. `promote_used_temporaries` (~400 lines) — promotes temporaries to named variables |
| 404 | 5. `extract_scope_declarations_from_destructuring` (~150 lines) — handles destructuring in scope declarations |
| 405 | 6. `rename_variables` (~200 lines) — renames variables for output, returns `HashSet<String>` |
| 406 | 7. `prune_hoisted_contexts` (~100 lines) — removes hoisted context declarations |
| 407 | |
| 408 | ### Phase 6 — Codegen (deferred, separate plan) |
| 409 | |
| 410 | - `codegen_function` (~2000+ lines) — converts ReactiveFunction to CodegenFunction (Babel AST) |
| 411 | - Depends on Babel AST output types being available in Rust |
| 412 | - Will be planned separately |
| 413 | |
| 414 | ## 6. Pass Signatures |
| 415 | |
| 416 | ```rust |
| 417 | // Construction: |
| 418 | pub fn build_reactive_function(hir: &HirFunction, env: &Environment) -> ReactiveFunction; |
| 419 | |
| 420 | // Debug printing: |
| 421 | pub fn debug_reactive_function(func: &ReactiveFunction, env: &Environment) -> String; |
| 422 | |
| 423 | // Validation (read-only): |
| 424 | pub fn assert_well_formed_break_targets(func: &ReactiveFunction) -> Result<(), CompilerDiagnostic>; |
| 425 | pub fn assert_scope_instructions_within_scopes(func: &ReactiveFunction, env: &Environment) -> Result<(), CompilerDiagnostic>; |
| 426 | |
| 427 | // Transforms (no env needed): |
| 428 | pub fn prune_unused_labels(func: &mut ReactiveFunction); |
| 429 | pub fn stabilize_block_ids(func: &mut ReactiveFunction); |
| 430 | |
| 431 | // Transforms (read env for scope/identifier data): |
| 432 | pub fn prune_non_escaping_scopes(func: &mut ReactiveFunction, env: &Environment); |
| 433 | pub fn prune_non_reactive_dependencies(func: &mut ReactiveFunction, env: &Environment); |
| 434 | pub fn prune_unused_scopes(func: &mut ReactiveFunction, env: &Environment); |
| 435 | pub fn prune_always_invalidating_scopes(func: &mut ReactiveFunction, env: &Environment); |
| 436 | pub fn prune_unused_lvalues(func: &mut ReactiveFunction, env: &Environment); |
| 437 | pub fn promote_used_temporaries(func: &mut ReactiveFunction, env: &Environment); |
| 438 | pub fn prune_hoisted_contexts(func: &mut ReactiveFunction, env: &Environment); |
| 439 | |
| 440 | // Transforms (mutate env — create temporaries, modify scope data): |
| 441 | pub fn merge_reactive_scopes_that_invalidate_together(func: &mut ReactiveFunction, env: &mut Environment); |
| 442 | pub fn propagate_early_returns(func: &mut ReactiveFunction, env: &mut Environment); |
| 443 | pub fn rename_variables(func: &mut ReactiveFunction, env: &mut Environment) -> HashSet<String>; |
| 444 | pub fn extract_scope_declarations_from_destructuring(func: &mut ReactiveFunction, env: &mut Environment); |
| 445 | |
| 446 | // Validation (optional, gated on config): |
| 447 | pub fn validate_preserved_manual_memoization(func: &ReactiveFunction, env: &Environment) -> Result<(), CompilerDiagnostic>; |
| 448 | ``` |
| 449 | |
| 450 | ## 7. Visitor/Transform Framework |
| 451 | |
| 452 | Use closure-based traversal helpers and direct recursion, matching the existing Rust codebase style (standalone functions, not trait hierarchies). |
| 453 | |
| 454 | ```rust |
| 455 | /// Read-only traversal of all statements in a block (recursive into nested blocks) |
| 456 | pub fn visit_reactive_block(block: &ReactiveBlock, visitor: &mut impl FnMut(&ReactiveStatement)); |
| 457 | |
| 458 | /// Mutating traversal with drain-and-rebuild pattern |
| 459 | pub fn transform_reactive_block( |
| 460 | block: &mut ReactiveBlock, |
| 461 | transform: &mut impl FnMut(ReactiveStatement) -> TransformResult, |
| 462 | ); |
| 463 | |
| 464 | pub enum TransformResult { |
| 465 | Keep(ReactiveStatement), |
| 466 | Remove, |
| 467 | Replace(ReactiveStatement), |
| 468 | ReplaceMany(Vec<ReactiveStatement>), |
| 469 | } |
| 470 | |
| 471 | /// Iterate over all Place operands in a ReactiveValue |
| 472 | pub fn each_reactive_value_operand(value: &ReactiveValue) -> impl Iterator<Item = &Place>; |
| 473 | |
| 474 | /// Map over all blocks contained in a ReactiveTerminal |
| 475 | pub fn map_terminal_blocks(terminal: &mut ReactiveTerminal, f: impl FnMut(&mut ReactiveBlock)); |
| 476 | ``` |
| 477 | |
| 478 | The drain-and-rebuild pattern for transforms: |
| 479 | 1. `let stmts: Vec<_> = block.drain(..).collect();` |
| 480 | 2. For each statement, call the transform closure |
| 481 | 3. Collect results into a new Vec |
| 482 | 4. Assign back to `*block` |
| 483 | |
| 484 | This avoids borrow checker issues with in-place mutation while iterating. |
| 485 | |
| 486 | ## 8. Skill Updates |
| 487 | |
| 488 | ### `compiler/.claude/skills/compiler-orchestrator/SKILL.md` |
| 489 | |
| 490 | Expand pass table rows #32-#49: |
| 491 | |
| 492 | | # | Log Name | Kind | Notes | |
| 493 | |---|----------|------|-------| |
| 494 | | 32 | BuildReactiveFunction | reactive | | |
| 495 | | 33 | AssertWellFormedBreakTargets | debug | Validation | |
| 496 | | 34 | PruneUnusedLabels | reactive | | |
| 497 | | 35 | AssertScopeInstructionsWithinScopes | debug | Validation | |
| 498 | | 36 | PruneNonEscapingScopes | reactive | | |
| 499 | | 37 | PruneNonReactiveDependencies | reactive | | |
| 500 | | 38 | PruneUnusedScopes | reactive | | |
| 501 | | 39 | MergeReactiveScopesThatInvalidateTogether | reactive | | |
| 502 | | 40 | PruneAlwaysInvalidatingScopes | reactive | | |
| 503 | | 41 | PropagateEarlyReturns | reactive | | |
| 504 | | 42 | PruneUnusedLValues | reactive | | |
| 505 | | 43 | PromoteUsedTemporaries | reactive | | |
| 506 | | 44 | ExtractScopeDeclarationsFromDestructuring | reactive | | |
| 507 | | 45 | StabilizeBlockIds | reactive | | |
| 508 | | 46 | RenameVariables | reactive | | |
| 509 | | 47 | PruneHoistedContexts | reactive | | |
| 510 | | 48 | ValidatePreservedManualMemoization | debug | Conditional | |
| 511 | | 49 | Codegen | ast | | |
| 512 | |
| 513 | Remove "BLOCKED" status from #32. Add crate mapping: `src/ReactiveScopes/` -> `react_compiler_reactive_scopes`. |
| 514 | |
| 515 | ### `compiler/.claude/skills/compiler-port/SKILL.md` |
| 516 | |
| 517 | - **Step 0**: Remove the block on `kind: 'reactive'` passes (currently says "report that test-rust-port only supports `hir` kind passes currently and stop") |
| 518 | - **Step 1**: Add `src/ReactiveScopes/` -> `react_compiler_reactive_scopes` to the TS-to-Rust crate mapping table |
| 519 | - **Step 2**: Add reactive types file to context gathering list |
| 520 | |
| 521 | ### `compiler/.claude/agents/port-pass.md` |
| 522 | |
| 523 | - Add note that reactive passes take `&mut ReactiveFunction` + `&Environment`/`&mut Environment` (not `&mut HirFunction`) |
| 524 | - Test command remains: `bash compiler/scripts/test-rust-port.sh <PassName>` |
| 525 | |
| 526 | ## 9. Key Files |
| 527 | |
| 528 | | File | Action | |
| 529 | |------|--------| |
| 530 | | `compiler/crates/react_compiler_hir/src/reactive.rs` | Create: all reactive types | |
| 531 | | `compiler/crates/react_compiler_hir/src/lib.rs` | Edit: `pub mod reactive; pub use reactive::*;` | |
| 532 | | `compiler/crates/react_compiler_reactive_scopes/` | Create: new crate | |
| 533 | | `compiler/crates/Cargo.toml` (workspace) | Edit: add member | |
| 534 | | `compiler/crates/react_compiler/Cargo.toml` | Edit: add dependency | |
| 535 | | `compiler/crates/react_compiler/src/debug_print.rs` | Edit: extract shared helpers as `pub` | |
| 536 | | `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` | Edit: wire reactive passes | |
| 537 | | `compiler/packages/.../src/HIR/DebugPrintReactiveFunction.ts` | Create: verbose debug printer | |
| 538 | | `compiler/packages/.../src/HIR/index.ts` | Edit: export | |
| 539 | | `compiler/scripts/test-rust-port.ts` | Edit: handle `kind: 'reactive'` | |
| 540 | | `compiler/.claude/skills/compiler-orchestrator/SKILL.md` | Edit: expand pass table | |
| 541 | | `compiler/.claude/skills/compiler-port/SKILL.md` | Edit: remove reactive block, add crate mapping | |
| 542 | | `compiler/.claude/agents/port-pass.md` | Edit: add reactive pass patterns | |
| 543 | |
| 544 | ## 10. TS Source Files Reference |
| 545 | |
| 546 | | Pass | TS Source | |
| 547 | |------|-----------| |
| 548 | | BuildReactiveFunction | `src/ReactiveScopes/BuildReactiveFunction.ts` | |
| 549 | | AssertWellFormedBreakTargets | `src/ReactiveScopes/AssertWellFormedBreakTargets.ts` | |
| 550 | | PruneUnusedLabels | `src/ReactiveScopes/PruneUnusedLabels.ts` | |
| 551 | | AssertScopeInstructionsWithinScopes | `src/ReactiveScopes/AssertScopeInstructionsWithinScopes.ts` | |
| 552 | | PruneNonEscapingScopes | `src/ReactiveScopes/PruneNonEscapingScopes.ts` | |
| 553 | | PruneNonReactiveDependencies | `src/ReactiveScopes/PruneNonReactiveDependencies.ts` | |
| 554 | | PruneUnusedScopes | `src/ReactiveScopes/PruneUnusedScopes.ts` | |
| 555 | | MergeReactiveScopesThatInvalidateTogether | `src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts` | |
| 556 | | PruneAlwaysInvalidatingScopes | `src/ReactiveScopes/PruneAlwaysInvalidatingScopes.ts` | |
| 557 | | PropagateEarlyReturns | `src/ReactiveScopes/PropagateEarlyReturns.ts` | |
| 558 | | PruneUnusedLValues | `src/ReactiveScopes/PruneTemporaryLValues.ts` | |
| 559 | | PromoteUsedTemporaries | `src/ReactiveScopes/PromoteUsedTemporaries.ts` | |
| 560 | | ExtractScopeDeclarationsFromDestructuring | `src/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts` | |
| 561 | | StabilizeBlockIds | `src/ReactiveScopes/StabilizeBlockIds.ts` | |
| 562 | | RenameVariables | `src/ReactiveScopes/RenameVariables.ts` | |
| 563 | | PruneHoistedContexts | `src/ReactiveScopes/PruneHoistedContexts.ts` | |
| 564 | | ValidatePreservedManualMemoization | `src/Validation/ValidatePreservedManualMemoization.ts` | |
| 565 | | Visitors/Transform | `src/ReactiveScopes/visitors.ts` | |
| 566 | | PrintReactiveFunction | `src/ReactiveScopes/PrintReactiveFunction.ts` | |
| 567 | | CodegenReactiveFunction | `src/ReactiveScopes/CodegenReactiveFunction.ts` | |