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:

  1. Restores control flow constructs - Reconstructs if, while, for, switch, and other control flow statements from the CFG's basic blocks and terminals
  2. Eliminates phi nodes - Replaces SSA phi nodes with compound value expressions (ternaries, logical expressions, sequence expressions)
  3. Handles labeled break/continue - Tracks control flow targets to emit explicit labeled break and continue statements when needed
  4. Preserves reactive scope information - Scope terminals are converted to ReactiveScopeBlock nodes in the tree

Input Invariants

Output Guarantees

Algorithm

Core Classes

  1. Driver - Traverses blocks and emits ReactiveBlock arrays
  2. Context - 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 targets
    • scopeFallthroughs: Set<BlockId> - Fallthroughs for scope blocks

Traversal Strategy

  1. Start at the entry block and call traverseBlock(entryBlock)
  2. For each block:
    • Emit all instructions as ReactiveInstructionStatement
    • Process the terminal based on its kind

Terminal Processing

Simple Terminals:

Control Flow Terminals:

Value Terminals (expressions that produce values):

Break/Continue:

Scope Terminals:

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:

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: