buildReactiveFunction
File
src/ReactiveScopes/BuildReactiveFunction.ts
Purpose
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:
- Restores control flow constructs - Reconstructs
if,while,for,switch, and other control flow statements from the CFG's basic blocks and terminals - Eliminates phi nodes - Replaces SSA phi nodes with compound value expressions (ternaries, logical expressions, sequence expressions)
- Handles labeled break/continue - Tracks control flow targets to emit explicit labeled
breakandcontinuestatements when needed - Preserves reactive scope information - Scope terminals are converted to
ReactiveScopeBlocknodes in the tree
Input Invariants
- HIR is in SSA form (variables have been renamed with unique identifiers)
- Basic blocks are connected (valid predecessor/successor relationships)
- Each block ends with a valid terminal
- Phi nodes exist at merge points for values from different control flow paths
- Reactive scopes have been constructed (
scopeterminals exist) - Scope dependencies are computed (
PropagateScopeDependenciesHIRhas run)
Output Guarantees
- Tree structure - The output is a
ReactiveFunctionwith abody: ReactiveBlockcontaining a tree ofReactiveStatementnodes - No CFG structure - Basic blocks are eliminated; control flow is represented through nested reactive terminals
- No phi nodes - Value merges are represented as
ConditionalExpression,LogicalExpression, orSequenceExpressionvalues - Labels emitted for all control flow - Every terminal that can be a break/continue target has a label; unnecessary labels are removed by subsequent
PruneUnusedLabelspass - Each block emitted exactly once - A block cannot be generated twice
- Scope blocks preserved -
scopeterminals becomeReactiveScopeBlocknodes
Algorithm
Core Classes
Driver- Traverses blocks and emits ReactiveBlock arraysContext- Tracks state:emitted: Set<BlockId>- Which blocks have been generated#scheduled: Set<BlockId>- Blocks that will be emitted by parent constructs#controlFlowStack: Array<ControlFlowTarget>- Stack of active break/continue targetsscopeFallthroughs: Set<BlockId>- Fallthroughs for scope blocks
Traversal Strategy
- Start at the entry block and call
traverseBlock(entryBlock) - For each block:
- Emit all instructions as
ReactiveInstructionStatement - Process the terminal based on its kind
- Emit all instructions as
Terminal Processing
Simple Terminals:
- return, throw - Emit directly as ReactiveTerminal
- unreachable - No-op
Control Flow Terminals:
- if - Schedule fallthrough, recursively traverse consequent/alternate, emit ReactiveIfTerminal
- while, do-while, for, for-of, for-in - Use scheduleLoop() which tracks continue targets
- switch - Process cases in reverse order
- label - Schedule fallthrough, traverse inner block
Value Terminals (expressions that produce values):
- ternary, logical, optional, sequence - Produce ReactiveValue compound expressions
Break/Continue:
- goto with GotoVariant.Break - Determine if break is implicit, unlabeled, or labeled
- goto with GotoVariant.Continue - Determine continue type
Scope Terminals:
- scope, pruned-scope - Schedule fallthrough, traverse inner block, emit as ReactiveScopeBlock
Key Data Structures
ReactiveFunction
type ReactiveFunction = {
loc: SourceLocation;
id: ValidIdentifierName | null;
params: Array<Place | SpreadPattern>;
generator: boolean;
async: boolean;
body: ReactiveBlock;
env: Environment;
directives: Array<string>;
};
ReactiveBlock
type ReactiveBlock = Array<ReactiveStatement>;
ReactiveStatement
type ReactiveStatement =
| ReactiveInstructionStatement // {kind: 'instruction', instruction}
| ReactiveTerminalStatement // {kind: 'terminal', terminal, label}
| ReactiveScopeBlock // {kind: 'scope', scope, instructions}
| PrunedReactiveScopeBlock; // {kind: 'pruned-scope', ...}
ReactiveValue (for compound expressions)
type ReactiveValue =
| InstructionValue // Regular instruction values
| ReactiveLogicalValue // a && b, a || b, a ?? b
| ReactiveSequenceValue // (a, b, c)
| ReactiveTernaryValue // a ? b : c
| ReactiveOptionalCallValue; // a?.b()
ControlFlowTarget
type ControlFlowTarget =
| {type: 'if'; block: BlockId; id: number}
| {type: 'switch'; block: BlockId; id: number}
| {type: 'case'; block: BlockId; id: number}
| {type: 'loop'; block: BlockId; continueBlock: BlockId; ...};
Edge Cases
Nested Control Flow
The scheduling mechanism handles arbitrarily nested control flow by pushing/popping from the control flow stack.
Value Blocks with Complex Expressions
SequenceExpression handles cases where value blocks contain multiple instructions.
Scope Fallthroughs
Breaks to scope fallthroughs are treated as implicit (no explicit break needed).
Catch Handlers
Scheduled specially via scheduleCatchHandler() to prevent re-emission.
Unreachable Blocks
The reachable() check prevents emitting unreachable blocks.
TODOs
The code contains several CompilerError.throwTodo() calls for unsupported patterns:
1. Optional chaining test blocks must end in branch
2. Logical expression test blocks must end in branch
3. Support for value blocks within try/catch statements
4. Support for labeled statements combined with value blocks
Example
Fixture: ternary-expression.js
Input:
javascript
function ternary(props) {
const a = props.a && props.b ? props.c || props.d : (props.e ?? props.f);
const b = props.a ? (props.b && props.c ? props.d : props.e) : props.f;
return a ? b : null;
}
HIR (CFG with many basic blocks):
The HIR contains 33 basic blocks with Ternary, Logical, Branch, and Goto terminals, plus phi nodes at merge points.
ReactiveFunction Output (Tree):
function ternary(props$62{reactive}) {
[1] $84 = Ternary
Sequence
[2] $66 = Logical
Sequence [...]
&& Sequence [...]
?
Sequence [...] // props.c || props.d
:
Sequence [...] // props.e ?? props.f
[40] StoreLocal a$99 = $98
...
[82] return $145
}
The transformation eliminates:
- 33 basic blocks reduced to a single tree
- Phi nodes replaced with nested Ternary and Logical value expressions
- CFG edges replaced with tree nesting