| 1 | # buildReactiveFunction |
| 2 | |
| 3 | ## File |
| 4 | `src/ReactiveScopes/BuildReactiveFunction.ts` |
| 5 | |
| 6 | ## Purpose |
| 7 | The `buildReactiveFunction` pass converts the compiler's HIR (High-level Intermediate Representation) from a **Control Flow Graph (CFG)** representation to a **tree-based ReactiveFunction** representation that is closer to an AST. This is a critical transformation in the React Compiler pipeline that: |
| 8 | |
| 9 | 1. **Restores control flow constructs** - Reconstructs `if`, `while`, `for`, `switch`, and other control flow statements from the CFG's basic blocks and terminals |
| 10 | 2. **Eliminates phi nodes** - Replaces SSA phi nodes with compound value expressions (ternaries, logical expressions, sequence expressions) |
| 11 | 3. **Handles labeled break/continue** - Tracks control flow targets to emit explicit labeled `break` and `continue` statements when needed |
| 12 | 4. **Preserves reactive scope information** - Scope terminals are converted to `ReactiveScopeBlock` nodes in the tree |
| 13 | |
| 14 | ## Input Invariants |
| 15 | - HIR is in SSA form (variables have been renamed with unique identifiers) |
| 16 | - Basic blocks are connected (valid predecessor/successor relationships) |
| 17 | - Each block ends with a valid terminal |
| 18 | - Phi nodes exist at merge points for values from different control flow paths |
| 19 | - Reactive scopes have been constructed (`scope` terminals exist) |
| 20 | - Scope dependencies are computed (`PropagateScopeDependenciesHIR` has run) |
| 21 | |
| 22 | ## Output Guarantees |
| 23 | - **Tree structure** - The output is a `ReactiveFunction` with a `body: ReactiveBlock` containing a tree of `ReactiveStatement` nodes |
| 24 | - **No CFG structure** - Basic blocks are eliminated; control flow is represented through nested reactive terminals |
| 25 | - **No phi nodes** - Value merges are represented as `ConditionalExpression`, `LogicalExpression`, or `SequenceExpression` values |
| 26 | - **Labels emitted for all control flow** - Every terminal that can be a break/continue target has a label; unnecessary labels are removed by subsequent `PruneUnusedLabels` pass |
| 27 | - **Each block emitted exactly once** - A block cannot be generated twice |
| 28 | - **Scope blocks preserved** - `scope` terminals become `ReactiveScopeBlock` nodes |
| 29 | |
| 30 | ## Algorithm |
| 31 | |
| 32 | ### Core Classes |
| 33 | |
| 34 | 1. **`Driver`** - Traverses blocks and emits ReactiveBlock arrays |
| 35 | 2. **`Context`** - Tracks state: |
| 36 | - `emitted: Set<BlockId>` - Which blocks have been generated |
| 37 | - `#scheduled: Set<BlockId>` - Blocks that will be emitted by parent constructs |
| 38 | - `#controlFlowStack: Array<ControlFlowTarget>` - Stack of active break/continue targets |
| 39 | - `scopeFallthroughs: Set<BlockId>` - Fallthroughs for scope blocks |
| 40 | |
| 41 | ### Traversal Strategy |
| 42 | |
| 43 | 1. Start at the entry block and call `traverseBlock(entryBlock)` |
| 44 | 2. For each block: |
| 45 | - Emit all instructions as `ReactiveInstructionStatement` |
| 46 | - Process the terminal based on its kind |
| 47 | |
| 48 | ### Terminal Processing |
| 49 | |
| 50 | **Simple Terminals:** |
| 51 | - `return`, `throw` - Emit directly as `ReactiveTerminal` |
| 52 | - `unreachable` - No-op |
| 53 | |
| 54 | **Control Flow Terminals:** |
| 55 | - `if` - Schedule fallthrough, recursively traverse consequent/alternate, emit `ReactiveIfTerminal` |
| 56 | - `while`, `do-while`, `for`, `for-of`, `for-in` - Use `scheduleLoop()` which tracks continue targets |
| 57 | - `switch` - Process cases in reverse order |
| 58 | - `label` - Schedule fallthrough, traverse inner block |
| 59 | |
| 60 | **Value Terminals (expressions that produce values):** |
| 61 | - `ternary`, `logical`, `optional`, `sequence` - Produce `ReactiveValue` compound expressions |
| 62 | |
| 63 | **Break/Continue:** |
| 64 | - `goto` with `GotoVariant.Break` - Determine if break is implicit, unlabeled, or labeled |
| 65 | - `goto` with `GotoVariant.Continue` - Determine continue type |
| 66 | |
| 67 | **Scope Terminals:** |
| 68 | - `scope`, `pruned-scope` - Schedule fallthrough, traverse inner block, emit as `ReactiveScopeBlock` |
| 69 | |
| 70 | ## Key Data Structures |
| 71 | |
| 72 | ### ReactiveFunction |
| 73 | ```typescript |
| 74 | type ReactiveFunction = { |
| 75 | loc: SourceLocation; |
| 76 | id: ValidIdentifierName | null; |
| 77 | params: Array<Place | SpreadPattern>; |
| 78 | generator: boolean; |
| 79 | async: boolean; |
| 80 | body: ReactiveBlock; |
| 81 | env: Environment; |
| 82 | directives: Array<string>; |
| 83 | }; |
| 84 | ``` |
| 85 | |
| 86 | ### ReactiveBlock |
| 87 | ```typescript |
| 88 | type ReactiveBlock = Array<ReactiveStatement>; |
| 89 | ``` |
| 90 | |
| 91 | ### ReactiveStatement |
| 92 | ```typescript |
| 93 | type ReactiveStatement = |
| 94 | | ReactiveInstructionStatement // {kind: 'instruction', instruction} |
| 95 | | ReactiveTerminalStatement // {kind: 'terminal', terminal, label} |
| 96 | | ReactiveScopeBlock // {kind: 'scope', scope, instructions} |
| 97 | | PrunedReactiveScopeBlock; // {kind: 'pruned-scope', ...} |
| 98 | ``` |
| 99 | |
| 100 | ### ReactiveValue (for compound expressions) |
| 101 | ```typescript |
| 102 | type ReactiveValue = |
| 103 | | InstructionValue // Regular instruction values |
| 104 | | ReactiveLogicalValue // a && b, a || b, a ?? b |
| 105 | | ReactiveSequenceValue // (a, b, c) |
| 106 | | ReactiveTernaryValue // a ? b : c |
| 107 | | ReactiveOptionalCallValue; // a?.b() |
| 108 | ``` |
| 109 | |
| 110 | ### ControlFlowTarget |
| 111 | ```typescript |
| 112 | type ControlFlowTarget = |
| 113 | | {type: 'if'; block: BlockId; id: number} |
| 114 | | {type: 'switch'; block: BlockId; id: number} |
| 115 | | {type: 'case'; block: BlockId; id: number} |
| 116 | | {type: 'loop'; block: BlockId; continueBlock: BlockId; ...}; |
| 117 | ``` |
| 118 | |
| 119 | ## Edge Cases |
| 120 | |
| 121 | ### Nested Control Flow |
| 122 | The scheduling mechanism handles arbitrarily nested control flow by pushing/popping from the control flow stack. |
| 123 | |
| 124 | ### Value Blocks with Complex Expressions |
| 125 | `SequenceExpression` handles cases where value blocks contain multiple instructions. |
| 126 | |
| 127 | ### Scope Fallthroughs |
| 128 | Breaks to scope fallthroughs are treated as implicit (no explicit break needed). |
| 129 | |
| 130 | ### Catch Handlers |
| 131 | Scheduled specially via `scheduleCatchHandler()` to prevent re-emission. |
| 132 | |
| 133 | ### Unreachable Blocks |
| 134 | The `reachable()` check prevents emitting unreachable blocks. |
| 135 | |
| 136 | ## TODOs |
| 137 | The code contains several `CompilerError.throwTodo()` calls for unsupported patterns: |
| 138 | 1. Optional chaining test blocks must end in `branch` |
| 139 | 2. Logical expression test blocks must end in `branch` |
| 140 | 3. Support for value blocks within try/catch statements |
| 141 | 4. Support for labeled statements combined with value blocks |
| 142 | |
| 143 | ## Example |
| 144 | |
| 145 | ### Fixture: `ternary-expression.js` |
| 146 | |
| 147 | **Input:** |
| 148 | ```javascript |
| 149 | function ternary(props) { |
| 150 | const a = props.a && props.b ? props.c || props.d : (props.e ?? props.f); |
| 151 | const b = props.a ? (props.b && props.c ? props.d : props.e) : props.f; |
| 152 | return a ? b : null; |
| 153 | } |
| 154 | ``` |
| 155 | |
| 156 | **HIR (CFG with many basic blocks):** |
| 157 | The HIR contains 33 basic blocks with `Ternary`, `Logical`, `Branch`, and `Goto` terminals, plus phi nodes at merge points. |
| 158 | |
| 159 | **ReactiveFunction Output (Tree):** |
| 160 | ``` |
| 161 | function ternary(props$62{reactive}) { |
| 162 | [1] $84 = Ternary |
| 163 | Sequence |
| 164 | [2] $66 = Logical |
| 165 | Sequence [...] |
| 166 | && Sequence [...] |
| 167 | ? |
| 168 | Sequence [...] // props.c || props.d |
| 169 | : |
| 170 | Sequence [...] // props.e ?? props.f |
| 171 | [40] StoreLocal a$99 = $98 |
| 172 | ... |
| 173 | [82] return $145 |
| 174 | } |
| 175 | ``` |
| 176 | |
| 177 | The transformation eliminates: |
| 178 | - 33 basic blocks reduced to a single tree |
| 179 | - Phi nodes replaced with nested `Ternary` and `Logical` value expressions |
| 180 | - CFG edges replaced with tree nesting |