Now I have a comprehensive view of the React Compiler's HIR system. Let me compile all this information into the final analysis document. I'll structure it according to the requested template.
The key components I need to analyze are:
1. HIR Core Model - Place, Identifier, Instruction, InstructionValue, Terminal, BasicBlock, HIR, HIRFunction
2. Identity System - BlockId, InstructionId, IdentifierId, ScopeId
3. InstructionValue variants - all ~40 variants
4. Terminal variants - all ~20 variants
5. Place/Identifier distinction
6. BuildHIR lowering - AST to HIR conversion
7. Type System (Types.ts) - types, shapes
8. Visitor patterns (visitors.ts)
Let me also compare with the CloudLang types:
- ast.ml (19 node types)
- ir.ml (Cloudflare config IR)
I need to provide specific OCaml type sketches for the adapted CloudLang version.
Based on my analysis of the React Compiler's HIR system and the CloudLang types, here is the complete analysis:
React Compiler HIR Type System and Construction - Complete Analysis
HIR Core Model
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
Purpose
The HIR (High-level Intermediate Representation) is the core data model for the React Compiler. It represents TypeScript/JavaScript code as a Control Flow Graph (CFG) with:
- SSA-style instructions for expressions
- Basic blocks with terminal nodes for control flow
- Effect and mutation tracking via AliasingEffects
- Reactive scope tracking for memoization analysis
Input Invariants
- Babel AST is well-formed (valid TypeScript/JavaScript)
- Variables are properly scoped according to JavaScript scoping rules
- No duplicate identifiers in same lexical scope (after renaming)
Output Guarantees
- All instructions have unique IDs
- Blocks are in reverse postorder for forward dataflow analysis
- Every variable reference maps to a unique Identifier with consistent
id and declarationId
- Phi nodes at join points for SSA form
- ReactiveScope boundaries are properly nested
Algorithm (detailed, step-by-step)
The HIR construction follows a multi-stage process:
- Environment Setup: Create HIRBuilder with Environment tracking bindings, scopes, and exception handlers
- Function Entry: Process parameters, create entry block, handle hoistable bindings
- Statement Lowering: For each statement in AST body:
- If
IfStatement: Create consequent/alternate blocks + continuation, branch terminal
- If
WhileStatement: Create conditional block, loop body, test branch terminal
- If
ForStatement: Create init/test/update blocks, loop body, branch chain
- If
ForOfStatement: Create GetIterator + IteratorNext + loop structure
- If
ForInStatement: Create NextPropertyOf + loop structure
- If
TryStatement: Create try block, catch handler block, exception terminal
- If
VariableDeclaration: Lower with DeclareLocal/StoreLocal or Destructure
- If
ReturnStatement: Create return terminal
- If
Break/Continue: Lookup block from scope stack, goto terminal
- If
SwitchStatement: Create case blocks in reverse order for fallthrough
- Expression Lowering: Each expression becomes instruction(s) storing to temporaries
- Block Completion:
- Compute reverse postorder
- Remove dead code and unnecessary try/catch
- Mark instruction IDs and predecessors
Key Data Structures (with type signatures)
// Core function representation
type HIRFunction = {
loc: SourceLocation;
id: ValidIdentifierName | null;
nameHint: string | null;
fnType: ReactFunctionType;
env: Environment;
params: Array<Place | SpreadPattern>;
returnTypeAnnotation: t.FlowType | t.TSType | null;
returns: Place;
context: Array<Place>;
body: HIR;
generator: boolean;
async: boolean;
directives: Array<string>;
aliasingEffects: Array<AliasingEffect> | null;
}
// CFG representation
type HIR = {
entry: BlockId;
blocks: Map<BlockId, BasicBlock>; // reverse postorder
}
type BasicBlock = {
kind: BlockKind; // 'block' | 'value' | 'loop' | 'sequence' | 'catch'
id: BlockId;
instructions: Array<Instruction>;
terminal: Terminal;
preds: Set<BlockId>;
phis: Set<Phi>;
}
// Single operation
type Instruction = {
id: InstructionId;
lvalue: Place | null;
value: InstructionValue;
effects: Array<AliasingEffect> | null;
loc: SourceLocation;
}
// Value reference point
type Place = {
kind: 'Identifier';
identifier: Identifier;
effect: Effect; // Unknown, Read, Capture, Mutate, etc.
reactive: boolean;
loc: SourceLocation;
}
// SSA variable
type Identifier = {
id: IdentifierId; // Unique SSA version
declarationId: DeclarationId; // Original declaration
name: IdentifierName | null; // null for temps
mutableRange: MutableRange;
scope: ReactiveScope | null;
type: Type;
loc: SourceLocation;
}
type MutableRange = {
start: InstructionId;
end: InstructionId;
}
Edge Cases
- Dead code: Unreachable blocks retained for analysis
- Empty branches: If without else uses continuation directly
- Hoisting: Variables used before declaration require HoistedConst/Let
- Exception handling: try/catch creates MaybeThrowTerminal between all instructions
- Optional chaining: OptionalTerminal tests for null/undefined at each step
Mapping to CloudLang
What we'd adapt:
- Basic block CFG structure (essential for closure analysis)
- Place/Identifier with effect tracking (core to mutation analysis)
- Instruction flattening (critical for SSA and dataflow)
- BlockKind distinction (statement vs expression blocks)
- MutableRange tracking (needed for closure lifetime analysis)
What we'd skip:
- JSX support (CloudLang targets Workers, not React)
- ReactiveScope (specific to React memoization)
- Hooks-specific instructions (StartMemoize, FinishMemoize)
- Complex exception modeling (simpler bailout for CloudLang)
- ForInStatement (rare in CloudLang patterns)
- Class/ObjectMethod (CloudLang focuses on functional patterns)
OCaml type sketches:
(* Core function representation *)
type function_body = {
entry : block_id;
blocks : (block_id, basic_block) Map.t;
}
type basic_block = {
kind : block_kind;
id : block_id;
instructions : instruction list;
terminal : terminal;
preds : block_id Set.t;
phis : phi Set.t;
}
and block_kind =
| Block
| Value
| Loop
| Sequence
| Catch
(* Single operation *)
and instruction = {
id : instruction_id;
lvalue : place option;
value : instruction_value;
loc : location;
}
(* Value reference point *)
and place = {
identifier : identifier;
effect : effect;
loc : location;
}
(* SSA variable *)
and identifier = {
id : identifier_id; (* Unique SSA version *)
declaration_id : declaration_id; (* Original declaration *)
name : string option; (* None for temps *)
mutable_range : (instruction_id * instruction_id);
scope : scope_id option;
type : ir_type;
loc : location;
}
and effect =
| Unknown
| Read
| Capture
| Mutate
| ConditionallyMutate
| Store
| Freeze
and instruction_id = int
and block_id = int
and declaration_id = int
and scope_id = int
Example (before/after)
Before (TypeScript AST):
function foo(x: number) {
const y = x + 1;
if (y > 10) {
return y;
} else {
const z = y * 2;
return z;
}
}
After (HIR):
foo(x: number): #t3
bb0 (block):
[#0] #t0 = Primitive { value: 1 }
[#1] #t1 = Binary x + #t0
[#2] Const y = #t1
[#3] If (#2) then:bb1 else:bb2 fallthrough=bb3
bb1 (value):
[#4] Return Explicit #t2
bb2 (block):
[#5] #t3 = Primitive { value: 2 }
[#6] #t4 = Binary #t2 * #t3
[#7] Const z = #t6
[#8] Return Explicit #t7
bb3 (value):
[#9] Return Void <undefined>
Identity System
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts (lines 1709-1781)
Purpose
Branded/opaque types prevent accidental misuse of IDs and provide type safety at the compiler level.
Input Invariants
- IDs are non-negative integers
- IDs are monotonically increasing during construction
- Each ID space is independent (BlockId != InstructionId, etc.)
Output Guarantees
- IDs from different spaces cannot be confused at type level
- Invalid IDs fail runtime assertions via
make*Id constructors
- Type system enforces proper use (e.g., can't pass BlockId where InstructionId expected)
Algorithm
Each ID type has a constructor that validates the input:
const opaqueBlockId = Symbol();
export type BlockId = number & {[opaqueBlockId]: 'BlockId'};
export function makeBlockId(id: number): BlockId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected block id to be a non-negative integer',
loc: GeneratedSource,
});
return id as BlockId;
}
Key Data Structures
type BlockId = number & { [opaqueBlockId]: 'BlockId' };
type InstructionId = number & { [opaqueInstructionId]: 'InstructionId' };
type IdentifierId = number & { [opaqueIdentifierId]: 'IdentifierId' };
type ScopeId = number & { [opaqueScopeId]: 'ScopeId' };
type DeclarationId = number & { [opaqueDeclarationId]: 'DeclarationId' };
type TypeId = number & { [opaqueTypeId]: 'IdentifierId' };
type PropertyLiteral = (string | number) & { [opaquePropertyLiteral]: 'PropertyLiteral' };
Edge Cases
- IDs from external sources (like AST node positions) are not automatically opaque
- Generated IDs are sequential but not necessarily contiguous after optimizations
Mapping to CloudLang
What we'd adapt:
- Same opaque type pattern for ID safety
- All same ID types (BlockId, InstructionId, etc.)
What we'd skip:
- TypeId (CloudLang may not need type inference initially)
OCaml type sketches:
type block_id = int (* Branded via constructor *)
type instruction_id = int
type identifier_id = int
type declaration_id = int
type scope_id = int
(* Opaque constructors with validation *)
let make_block_id : int -> block_id
let make_instruction_id : int -> instruction_id
let make_identifier_id : int -> identifier_id
InstructionValue Variants
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts (lines 899-1124)
Purpose
InstructionValue represents all possible operations that produce values. Each instruction stores its result in a Place.
Input Invariants
- All operands are Places (no nested expressions)
- All values have source locations
Output Guarantees
- Instructions are SSA form (each Place assigned once per block)
- Complex expressions are flattened to sequence of simple instructions
Algorithm
Each InstructionValue variant carries its specific operands and location.
Key Data Structures
type InstructionValue =
| LoadLocal | LoadContext
| { kind: 'DeclareLocal'; lvalue: LValue; type: ... }
| { kind: 'DeclareContext'; lvalue: LValue; }
| StoreLocal
| { kind: 'StoreContext'; lvalue: LValue; value: Place }
| Destructure
| { kind: 'Primitive'; value: number | boolean | string | null | undefined }
| JSXText
| { kind: 'BinaryExpression'; operator: ...; left: Place; right: Place }
| NewExpression
| CallExpression
| MethodCall
| { kind: 'UnaryExpression'; operator: ...; value: Place }
| { kind: 'TypeCastExpression'; value: Place; type: Type }
| JsxExpression
| { kind: 'ObjectExpression'; properties: Array<...> }
| ObjectMethod
| ArrayExpression
| { kind: 'JsxFragment'; children: Array<Place> }
| { kind: 'RegExpLiteral'; pattern: string; flags: string }
| { kind: 'MetaProperty'; meta: string; property: string }
| { kind: 'PropertyStore'; object: Place; property: PropertyLiteral; value: Place }
| PropertyLoad
| { kind: 'PropertyDelete'; object: Place; property: PropertyLiteral }
| { kind: 'ComputedStore'; object: Place; property: Place; value: Place }
| { kind: 'ComputedLoad'; object: Place; property: Place }
| { kind: 'ComputedDelete'; object: Place; property: Place }
| LoadGlobal | StoreGlobal
| FunctionExpression
| { kind: 'TaggedTemplateExpression'; tag: Place; value: ... }
| { kind: 'TemplateLiteral'; subexprs: Array<Place>; quasis: Array<...> }
| { kind: 'Await'; value: Place }
| { kind: 'GetIterator'; collection: Place }
| { kind: 'IteratorNext'; iterator: Place; collection: Place }
| { kind: 'NextPropertyOf'; value: Place }
| { kind: 'PrefixUpdate'; lvalue: Place; operation: ...; value: Place }
| { kind: 'PostfixUpdate'; lvalue: Place; operation: ...; value: Place }
| { kind: 'Debugger' }
| StartMemoize | FinishMemoize
| { kind: 'UnsupportedNode'; node: t.Node }
Edge Cases
- UnsupportedNode used for constructs not represented but passed through
- Debugger has no lvalue (side-effect only)
Mapping to CloudLang
Adaptable to CloudLang's TypeScript subset:
| Category |
InstructionValue |
CloudLang Relevant? |
Data Carried |
Semantics |
| Primitives |
Primitive |
Yes |
number/bool/string/null/undefined |
Literal value |
|
JSXText |
No |
string |
JSX content (skip) |
|
RegExpLiteral |
No |
pattern, flags |
Regular expression (skip initially) |
| Literals |
ArrayExpression |
Yes |
Array<Place | SpreadPattern | Hole> |
Array literal |
|
ObjectExpression |
Yes |
Array<ObjectProperty | SpreadPattern> |
Object literal |
|
TemplateLiteral |
Maybe |
quasis, subexprs |
Template string |
|
TaggedTemplateExpression |
No |
tag, value |
Tagged template (rare) |
|
JsxFragment |
No |
children |
JSX fragment (skip) |
|
MetaProperty |
No |
meta, property |
import.meta/new.target (skip) |
| Operators |
BinaryExpression |
Yes |
operator, left, right |
+, -, *, /, %, ===, !==, <, >, etc. |
|
UnaryExpression |
Yes |
operator, value |
-, !, typeof, void, etc. |
|
PrefixUpdate |
Yes |
lvalue, operation, value |
++x, --y |
|
PostfixUpdate |
Yes |
lvalue, operation, value |
x++, y-- |
| Control Flow |
NewExpression |
Maybe |
callee, args |
new Constructor() |
|
CallExpression |
Yes |
callee, args |
func(...args) |
|
MethodCall |
Yes |
receiver, property, args |
obj.method(...args) |
|
Await |
Maybe |
value |
await expr (for async Workers) |
|
Debugger |
Maybe |
- |
debugger statement |
| Variables |
LoadLocal |
Yes |
place |
Read local variable |
|
LoadContext |
No |
place |
Read captured variable (skip - simplify) |
|
StoreLocal |
Yes |
lvalue, value, type |
x = value |
|
StoreContext |
No |
lvalue, value |
Store to captured (skip) |
|
DeclareLocal |
Yes |
lvalue, type |
const/let declaration |
|
DeclareContext |
No |
lvalue |
Hoisted capture declaration (skip) |
|
Destructure |
Yes |
lvalue, value |
const {a, b} = obj |
|
LoadGlobal |
Maybe |
binding |
Read global/imported |
|
StoreGlobal |
Maybe |
name, value |
Write to global (rare) |
| Properties |
PropertyLoad |
Yes |
object, property |
obj.prop |
|
PropertyStore |
Yes |
object, property, value |
obj.prop = value |
|
PropertyDelete |
Maybe |
object, property |
delete obj.prop |
|
ComputedLoad |
Yes |
object, property |
obj[prop] |
|
ComputedStore |
Yes |
object, property, value |
obj[prop] = value |
|
ComputedDelete |
Maybe |
object, property |
delete obj[prop] |
| Functions |
FunctionExpression |
Yes |
loweredFunc |
() => {} or function() {} |
|
ObjectMethod |
Maybe |
loweredFunc |
{ method() {} } |
| Iterators |
GetIterator |
Maybe |
collection |
for-of iterator |
|
IteratorNext |
Maybe |
iterator, collection |
Iterator iteration |
|
NextPropertyOf |
Maybe |
value |
for-in enumeration |
| React-specific |
JsxExpression |
No |
tag, props, children |
JSX element (skip) |
|
TypeCastExpression |
Maybe |
value, type |
x as Type, satisfies Type |
|
StartMemoize |
No |
manualMemoId, deps |
useMemo marker (skip) |
|
FinishMemoize |
No |
manualMemoId, decl |
useMemo marker (skip) |
|
UnsupportedNode |
No |
node |
Fallback (skip) |
What we'd skip for CloudLang:
- JSX-related (JsxExpression, JSXText, JsxFragment)
- React-specific (StartMemoize, FinishMemoize)
- Complex iterators (GetIterator, IteratorNext, NextPropertyOf)
- ObjectMethod (use function expressions instead)
- TemplateLiteral (simplify to string concatenation initially)
- TaggedTemplateExpression, MetaProperty
OCaml type sketches:
type instruction_value =
| Primitive of primitive_value
| ArrayExpression of (place_or_spread_or_hole list)
| ObjectExpression of (obj_prop_or_spread list)
| BinaryExpression of { op: binary_op; left: place; right: place }
| UnaryExpression of { op: unary_op; value: place }
| PrefixUpdate of { lvalue: place; op: update_op; value: place }
| PostfixUpdate of { lvalue: place; op: update_op; value: place }
| CallExpression of { callee: place; args: (place_or_spread list) }
| MethodCall of { receiver: place; property: place; args: (place_or_spread list) }
| NewExpression of { callee: place; args: (place_or_spread list) }
| LoadLocal of place
| StoreLocal of { lvalue: place; value: place; type: ir_type option }
| DeclareLocal of { lvalue: place; kind: var_kind }
| Destructure of { lvalue: pattern; value: place }
| PropertyLoad of { object: place; property: property_literal }
| PropertyStore of { object: place; property: property_literal; value: place }
| ComputedLoad of { object: place; property: place }
| ComputedStore of { object: place; property: place; value: place }
| FunctionExpression of { params: (place_or_spread list); body: function_body }
| LoadGlobal of { binding: non_local_binding }
| Debugger
| TypeCast of { value: place; type: ir_type }
| Await of place
and primitive_value =
| LString of string
| LNumber of float
| LBool of bool
| LNull
| LUndefined
and binary_op =
| Add | Sub | Mul | Div | Mod
| EqEq | NotEq | StrictEq | StrictNotEq
| Lt | LtEq | Gt | GtEq
| And | Or | BitwiseAnd | BitwiseOr | BitwiseXor | LeftShift | RightShift
and unary_op =
| Negate | Not | BitwiseNot | Typeof | Void | Delete
and update_op = Increment | Decrement
and place_or_spread_or_hole =
| Place of place
| Spread of place
| Hole
and obj_prop_or_spread =
| Property of { key: obj_property_key; value: place }
| Spread of place
and obj_property_key =
| StringKey of string
| IdentifierKey of string
| ComputedKey of place
Terminal Variants
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts (lines 361-638)
Purpose
Terminal nodes end basic blocks and describe control flow between blocks.
Input Invariants
- Each terminal has a unique InstructionId
- Fallthrough blocks exist where applicable
- Terminal successors are valid BlockIds in the CFG
Output Guarantees
- CFG is well-formed (all referenced blocks exist)
- Loops have proper entry/exit points
- Exception handlers are reachable via MaybeThrowTerminal
Algorithm
Terminals are created based on control flow structure and connected via BlockIds.
Key Data Structures
type Terminal =
| UnsupportedTerminal
| UnreachableTerminal
| ThrowTerminal
| ReturnTerminal
| GotoTerminal
| IfTerminal
| BranchTerminal
| SwitchTerminal
| ForTerminal
| ForOfTerminal
| ForInTerminal
| DoWhileTerminal
| WhileTerminal
| LogicalTerminal
| TernaryTerminal
| OptionalTerminal
| LabelTerminal
| SequenceTerminal
| MaybeThrowTerminal
| TryTerminal
| ReactiveScopeTerminal
| PrunedScopeTerminal
type TerminalWithFallthrough = Terminal & { fallthrough: BlockId }
Edge Cases
- UnreachableTerminal marks paths that can never execute
- MaybeThrowTerminal is inserted before every instruction in try block
- SequenceTerminal groups expression statements within control flow
Mapping to CloudLang
| Category |
Terminal |
CloudLang Relevant? |
Data Carried |
Semantics |
| Unconditional |
Goto |
Yes |
block, variant (Break/Continue/Try) |
Jump to block |
|
Return |
Yes |
returnVariant (Void/Implicit/Explicit), value |
Return from function |
|
Throw |
Yes |
value |
Throw exception |
|
Unreachable |
Maybe |
- |
Code never reached |
|
Unsupported |
No |
- |
Fallback (skip) |
| Conditional |
If |
Yes |
test, consequent, alternate, fallthrough |
if/else statement |
|
Branch |
Yes |
test, consequent, alternate, fallthrough |
Conditional jump (for loops) |
|
Switch |
Maybe |
test, cases, fallthrough |
switch statement |
| Loops |
While |
Yes |
test, loop, fallthrough |
while loop |
|
DoWhile |
Maybe |
loop, test, fallthrough |
do-while loop |
|
For |
Yes |
init, test, update, loop, fallthrough |
for loop |
|
ForOf |
Maybe |
init, test, loop, fallthrough |
for-of loop |
|
ForIn |
No |
init, loop, fallthrough |
for-in loop (skip) |
| Expression-level |
Logical |
No |
operator, test, fallthrough |
&&, |
|
Ternary |
No |
test, fallthrough |
? : (skip - simplify) |
|
Optional |
No |
optional, test, fallthrough |
?. chaining (skip) |
|
Sequence |
Maybe |
block, fallthrough |
Group expressions |
|
Label |
Maybe |
block, fallthrough |
Labeled statement |
| Exception |
Try |
Maybe |
block, handlerBinding, handler, fallthrough |
try/catch |
|
MaybeThrow |
No |
continuation, handler |
Exception check (skip) |
| React-specific |
ReactiveScope |
No |
scope, block, fallthrough |
Scope boundary (skip) |
|
PrunedScope |
No |
scope, block, fallthrough |
Pruned scope (skip) |
What we'd skip:
- Logical, Ternary, Optional (simplify to If/Branch terminals)
- ReactiveScope, PrunedScope (React-specific)
- ForIn (rare in CloudLang)
OCaml type sketches:
type terminal =
| Goto of { block: block_id; variant: goto_variant }
| Return of { return_variant: return_variant; value: place }
| Throw of place
| Unreachable
| If of { test: place; consequent: block_id; alternate: block_id; fallthrough: block_id }
| Branch of { test: place; consequent: block_id; alternate: block_id; fallthrough: block_id }
| Switch of { test: place; cases: switch_case list; fallthrough: block_id }
| While of { test: block_id; loop: block_id; fallthrough: block_id }
| DoWhile of { loop: block_id; test: block_id; fallthrough: block_id }
| For of { init: block_id; test: block_id; update: block_id option; loop: block_id; fallthrough: block_id }
| ForOf of { init: block_id; test: block_id; loop: block_id; fallthrough: block_id }
| Try of { block: block_id; handler: place option; handler: block_id; fallthrough: block_id }
| Sequence of { block: block_id; fallthrough: block_id }
and goto_variant =
| Break
| Continue
| Try
and return_variant =
| Void
| Implicit
| Explicit
and switch_case = {
test: place option; (* None for default *)
block: block_id;
}
Place/Identifier Distinction
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts (lines 1160-1177, 1248-1277)
Purpose
Separate Place (where a value is stored) from Identifier (what the value is) enables:
- Effect tracking at each use site
- Separate analysis of variable definitions vs uses
- Efficient aliasing/closure analysis
Input Invariants
- Every Place contains an Identifier
- Identifiers have unique IDs but same declarationId for same original variable
Output Guarantees
- Can track how each variable is used at each point
- MutableRange describes when reassignments are legal
- Scope tracking enables reactive dependency inference
Key Data Structures
// A reference point with metadata
type Place = {
kind: 'Identifier'; // Currently only Identifier, designed for extensibility
identifier: Identifier;
effect: Effect;
reactive: boolean;
loc: SourceLocation;
}
// The actual variable definition
type Identifier = {
id: IdentifierId; // SSA version
declarationId: DeclarationId; // Links all SSA versions
name: IdentifierName | null;
mutableRange: MutableRange;
scope: ReactiveScope | null;
type: Type;
loc: SourceLocation;
}
// What we track about this variable's lifetime
type MutableRange = {
start: InstructionId; // First instruction where mutable
end: InstructionId; // First instruction where immutable
}
Why These Separate?
| Aspect |
Place |
Identifier |
| Represents |
Use site |
Definition site |
| Has per-site metadata |
Effect, reactive |
N/A |
| SSA version |
References Identifier.id |
Own id |
| Multiple instances |
Yes (one per use) |
No (one per SSA version) |
What Effect, Reactive, MutableRange, Scope Buy You
Effect:
- Describes how this specific use affects the value
- Read: pure read, no mutation
- Mutate: writes to the value
- Capture: stores into mutable container (potential alias)
- Store: assignment or store operation
- Freeze: marks value as immutable
Reactive:
- True if this Place is a dependency for a reactive scope
- Used for React memoization (skip for CloudLang)
MutableRange:
- Defines the instruction range where the Identifier can be mutated
- After
end, the value becomes immutable (enables freeze analysis)
- Critical for proving closures don't modify captured values
Scope:
- Links to the ReactiveScope that produces this value
- Enables dependency tracking for memoization (skip for CloudLang)
Edge Cases
- Hoisted variables have
id different from declarationId temporaries
- Temporary identifiers have
name: null
- SSA form ensures single definition per block
Mapping to CloudLang
What we'd adapt:
- Place/Identifier separation (core concept)
- Effect tracking (essential for mutation analysis)
- MutableRange (needed for closure safety)
- Simplified
reactive: false (always false for CloudLang)
- No
scope field initially
What we'd simplify:
- Remove
reactive field (always false)
- Remove
scope field initially (no reactive scopes)
- Keep
kind: 'Identifier' as only variant initially
OCaml type sketches:
type place = {
identifier : identifier;
effect : effect;
loc : location;
}
type identifier = {
id : identifier_id;
declaration_id : declaration_id;
name : string option;
mutable_range : (instruction_id * instruction_id);
type : ir_type;
loc : location;
}
and effect =
| Unknown
| Read
| Mutate
| Capture
| Store
| Freeze
BuildHIR Lowering
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
Purpose
Converts Babel AST to HIR CFG, flattening expressions to SSA form while preserving control flow structure.
Input Invariants
- Babel AST is valid TypeScript/JavaScript
- Babel scope information is correct
- Function has proper parameter declarations
Output Guarantees
- HIR CFG is well-formed and connected
- All expressions are flattened to temporaries
- Hoistable bindings are properly declared before first use
- Exception handlers are modeled where present
Algorithm (detailed, step-by-step)
Phase 1: Builder Initialization
- Create
HIRBuilder with Environment
- Register function parameters as Places
- Process complex parameter patterns (destructuring, rest)
Phase 2: Statement Lowering (main switch on stmtNode.type)
For each statement:
ThrowStatement: Lower argument to temporary, create ThrowTerminal
ReturnStatement: Lower argument (or undefined), create ReturnTerminal
IfStatement:
- Reserve continuation block
- Create consequent block with goto to continuation
- Create alternate block (or use continuation) with goto to continuation
- Lower test to temporary
- Create IfTerminal with test, consequent, alternate, fallthrough
BlockStatement:
- Collect hoistable bindings from scope
- For each statement: traverse to find forward references
- Insert DeclareContext for hoisted bindings at earliest use point
- Lower each statement in order
- Remove bindings as declarations are encountered
ForStatement:
- Reserve: test, continuation, init, update (optional), loop blocks
- In init block: lower VariableDeclaration (init)
- In loop block: push loop scope, lower body, continue to update or test
- In update block (optional): lower update expression
- In test block: lower test, branch to loop or continuation
- Create ForTerminal with init, test, update, loop, fallthrough
WhileStatement:
- Reserve: conditional, continuation, loop blocks
- In loop block: push loop scope, lower body, continue to conditional
- Terminate with WhileTerminal pointing to conditional
- Complete conditional: lower test, branch to loop or continuation
DoWhileStatement:
- Reserve: conditional, continuation, loop blocks
- In loop block: push loop scope, lower body, continue to conditional
- Terminate with DoWhileTerminal pointing to conditional
- Complete conditional: lower test, branch to loop or continuation
ForOfStatement:
- Reserve: init, test, continuation, loop blocks
- In loop block: push loop scope, lower body, continue to init
- Create GetIterator for collection
- Create IteratorNext in loop
- Assign iterator result to loop variable (Destructure)
- Branch to loop or continuation based on done flag
- Create ForOfTerminal
ForInStatement: Similar to ForOf but uses NextPropertyOf
SwitchStatement:
- Reserve continuation block
- Iterate cases in REVERSE order (for fallthrough)
- For each case:
- Create block, lower consequent
- Add goto to next case's block (or continuation for last)
- Build test (null for default)
- If no default, add implicit default to continuation
- Create SwitchTerminal
VariableDeclaration:
- Determine kind (Const/Let)
- For each declarator:
- If init: lower init to temporary
- Create DeclareLocal/StoreLocal/StoreContext/Destructure based on binding kind
- Context variables: DeclareContext, StoreContext
- Local variables: DeclareLocal, StoreLocal
- Patterns: Destructure
FunctionDeclaration:
- Skip in Babel (don't re-traverse)
- Lower function to value
- Store to binding with InstructionKind.Function
BreakStatement:
- Lookup break block from scope stack (loop/switch/label)
- Create GotoTerminal with GotoVariant.Break
ContinueStatement:
- Lookup continue block from scope stack (loop only)
- Create GotoTerminal with GotoVariant.Continue
TryStatement:
- Reserve: try, handler, continuation blocks
- Create catch binding (if present)
- In handler: lower binding, lower body
- In try block: push exception handler, lower body
- Create TryTerminal
LabeledStatement:
- Labeled loops: push label down to loop()
- Other statements: create LabelTerminal with label scope
DebuggerStatement: Create Debugger instruction
ExpressionStatement: Lower expression to temporary (side effect)
Phase 3: Expression Lowering
Each expression is lowered to a sequence of instructions:
- For literals: Create Primitive instruction
- For identifiers: Resolve to Place (LoadLocal/LoadContext/LoadGlobal)
- For binary/unary: Lower operands, create BinaryExpression/UnaryExpression
- For calls: Lower callee and args, create CallExpression/MethodCall/NewExpression
- For member access: Lower object, create PropertyLoad/ComputedLoad
- For objects/arrays: Lower elements, create ObjectExpression/ArrayExpression
- For functions: Recursively lower to FunctionExpression
Phase 4: Builder Finalization
- Call
builder.build()
- Compute reverse postorder of blocks
- Mark instruction IDs and predecessors
- Remove dead code and unnecessary try/catch
- Return HIRFunction
Key Data Structures
class HIRBuilder {
#completed: Map<BlockId, BasicBlock>
#current: WipBlock
#entry: BlockId
#scopes: Array<Scope> // Loop/Label/Switch
#context: Map<t.Identifier, SourceLocation> // Captured refs
#bindings: Bindings // Name -> Identifier mapping
#env: Environment
#exceptionHandlerStack: Array<BlockId>
}
type WipBlock = {
id: BlockId;
instructions: Array<Instruction>;
kind: BlockKind;
}
type Scope = LoopScope | LabelScope | SwitchScope
type LoopScope = { kind: 'loop'; label: string | null; continueBlock: BlockId; breakBlock: BlockId }
Edge Cases
- Empty test in for: generates Todo error
- Multiple defaults in switch: error
- Var declarations: Todo error (only const/let supported)
- For-await: Todo error
- Class declarations: Unsupported (lift out required)
- With statement: Unsupported
Mapping to CloudLang
What we'd adapt:
- Statement lowering for: If, While, For, VariableDeclaration, ExpressionStatement, Return, Break, Continue
- Expression flattening algorithm
- Scope management for loops
- Block ordering and terminal creation
- Basic error handling (bailout vs error)
What we'd skip/simplify:
- JSX-related statements
- ForIn/ForOf loops (use simple while/for pattern)
- LabeledStatement (labels rare in CloudLang)
- WithStatement (unsupported anyway)
- ClassDeclaration (unsupported anyway)
- Complex hoisting (simplify: declare at top of block)
- TryStatement (bailout for now)
OCaml algorithm sketch:
let lower_function (ast_node : node) (env : environment) : hir_function_result =
let builder = new_builder env in
(* Process parameters *)
List.iter (fun p ->
match p with
| Identifier name -> add_param builder name
| _ -> error "Complex params not supported"
) ast_node.params;
(* Lower body *)
match ast_node.body with
| BlockStatement stmts -> List.iter (lower_statement builder) stmts
| Expression expr ->
let tmp = lower_expression_to_temporary builder expr in
terminate builder (Return Explicit tmp);
(* Finalize and return *)
Ok { builder.build () with
id = ast_node.name;
params = builder.params;
(* ... *)
}
and lower_statement (builder : builder) (stmt : node) : unit =
match stmt with
| IfStatement { test; consequent; alternate; _ } ->
let continuation = reserve builder Block in
let consequent = enter builder Block (fun consequent_block ->
lower_statement builder consequent;
Goto { block = continuation; variant = Break }
) in
let alternate = match alternate with
| Some alt -> enter builder Block (fun alternate_block ->
lower_statement builder alt;
Goto { block = continuation; variant = Break }
)
| None -> continuation
in
let test_place = lower_expression_to_temporary builder test in
terminate builder (If {
test = test_place;
consequent;
alternate;
fallthrough = continuation
}) continuation
| WhileStatement { test; body; _ } ->
let cond_block = reserve builder Loop in
let continuation = reserve builder Block in
let loop_block = enter builder Block (fun loop_block_id ->
loop builder None cond_block continuation (fun () ->
lower_statement builder body;
Goto { block = cond_block; variant = Continue }
)
) in
let test_place = lower_expression_to_temporary builder test in
terminate builder (While {
test = cond_block;
loop = loop_block;
fallthrough = continuation
}) cond_block;
(* Complete cond block with branch *)
terminate builder (Branch {
test = test_place;
consequent = loop_block;
alternate = continuation;
fallthrough = cond_block
}) continuation
| VariableDeclaration { kind; declarations } ->
let var_kind = match kind with
| "const" -> Const
| "let" -> Let
| "var" -> error "var not supported"
in
List.iter (fun decl ->
match decl with
| VariableDeclarator { id; init } ->
let id_place = resolve_identifier builder id in
(match init with
| Some init_expr ->
let value_place = lower_expression_to_temporary builder init_expr in
(* Check if pattern or simple identifier *)
match id with
| Identifier _ ->
store_value builder var_kind id_place value_place
| _ ->
destructure builder var_kind id value_place
| None ->
(* Uninitialized - skip for now *)
()
)
) declarations
| ReturnStatement { argument } ->
let value_place = match argument with
| Some arg -> lower_expression_to_temporary builder arg
| None -> make_primitive_place Undefined
in
terminate builder (Return { return_variant = Explicit; value = value_place })
| ExpressionStatement expr ->
let _ = lower_expression_to_temporary builder expr in ()
| _ -> error "Unsupported statement: %s" (node_type stmt)
and lower_expression_to_temporary (builder : builder) (expr : node) : place =
let value = lower_expression builder expr in
emit_instruction builder { lvalue = None; value; loc = expr.loc };
value
and lower_expression (builder : builder) (expr : node) : place =
match expr with
| Literal lit ->
let value = lower_literal lit in
make_primitive_place value
| Identifier name ->
resolve_identifier builder name
| BinaryExpression { op; left; right } ->
let left_place = lower_expression_to_temporary builder left in
let right_place = lower_expression_to_temporary builder right in
let tmp = make_temporary builder in
emit_instruction builder {
lvalue = Some tmp;
value = BinaryExpression { op; left = left_place; right = right_place };
};
tmp
| CallExpression { callee; args } ->
let callee_place = lower_expression_to_temporary builder callee in
let arg_places = List.map (lower_expression_to_temporary builder) args in
let tmp = make_temporary builder in
emit_instruction builder {
lvalue = Some tmp;
value = CallExpression { callee = callee_place; args = arg_places };
};
tmp
| _ -> error "Unsupported expression: %s" (node_type expr)
Type System (Types.ts)
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/Types.ts
Purpose
Provides a type system for tracking value categories, shapes, and function signatures used in effect inference.
Input Invariants
- Types are created via
makeType() for fresh TypeVars
- Built-in types use predefined shapeIds
Output Guarantees
- Type equality is structural where applicable
- TypeVars are globally unique (via counter)
- Types can be duplicated with fresh TypeVars
Key Data Structures
type Type =
| BuiltInType // Primitive, Function, Object
| PhiType // Join of types at CFG merge
| TypeVar // Fresh type variable (for inference)
| PolyType // Polymorphic type
| PropType // Property access
| ObjectMethod // Method type
type BuiltInType = PrimitiveType | FunctionType | ObjectType
type PrimitiveType = { kind: 'Primitive' }
type FunctionType = {
kind: 'Function';
shapeId: string | null;
return: Type;
isConstructor: boolean;
}
type ObjectType = {
kind: 'Object';
shapeId: string | null;
}
type TypeVar = {
kind: 'Type';
id: TypeId;
}
type PhiType = {
kind: 'Phi';
operands: Array<Type>;
}
type PropType = {
kind: 'Property';
objectType: Type;
objectName: string;
propertyName:
| { kind: 'literal'; value: PropertyLiteral }
| { kind: 'computed'; value: Type };
}
type ObjectMethod = {
kind: 'ObjectMethod';
}
Algorithm
makeType() increments global counter and returns fresh TypeVar
duplicateType() creates copy with fresh TypeVars
typeEquals() structural comparison of types
- TypeVars compared by
id; others structurally
Edge Cases
- PhiType operands can have different types (unification needed)
- PropertyType with computed propertyName depends on runtime value
Mapping to CloudLang
What we'd adapt initially:
- Simple distinction: Primitive, Function, Object
- TypeVar for future type inference
- PhiType for SSA join points
- Skip complex shape system initially
What we'd skip:
- ObjectMethod (use FunctionExpression)
- PropType (computed properties rare)
- PolyType (not needed for basic CloudLang)
- Complex shapeId system
OCaml type sketches:
type ir_type =
| Primitive
| Function of function_type
| Object of { shape_id: string option }
| TypeVar of type_id
| PhiType of ir_type list
and function_type = {
return_type: ir_type;
is_constructor: bool;
}
ObjectShape (Shape System)
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
Purpose
Encodes function signatures and property shapes for effect inference (what hooks/builtins do with their arguments).
Input Invariants
- ShapeIds are unique in registry
- Aliasing signatures reference parameter names correctly
Output Guarantees
- Hook effects are correctly encoded
- Built-in behavior is captured
Key Data Structures
type ShapeRegistry = Map<string, ObjectShape>
type ObjectShape = {
properties: Map<string, BuiltInType | PolyType>;
functionType: FunctionSignature | null;
}
type FunctionSignature = {
positionalParams: Array<Effect>;
restParam: Effect | null;
returnType: BuiltInType | PolyType;
returnValueKind: ValueKind;
returnValueReason?: ValueReason;
calleeEffect: Effect;
hookKind: HookKind | null;
noAlias?: boolean;
mutableOnlyIfOperandsAreMutable?: boolean;
impure?: boolean;
aliasing?: AliasingSignature | null;
}
type HookKind =
| 'useContext' | 'useState' | 'useReducer' | 'useRef'
| 'useEffect' | 'useLayoutEffect' | 'useInsertionEffect'
| 'useMemo' | 'useCallback' | 'useTransition'
| 'useImperativeHandle' | 'useEffectEvent' | 'useOptimistic'
| 'useActionState' | 'Custom'
type AliasingSignature = {
receiver: IdentifierId;
params: Array<IdentifierId>;
rest: IdentifierId | null;
returns: IdentifierId;
temporaries: Array<Place>;
effects: Array<AliasingEffect>;
}
Algorithm
addFunction() adds function with signature to registry
parseAliasingSignatureConfig() converts declarative config to runtime form
- Effects reference placeholders like
@receiver, @param0 resolved to Place IDs
Edge Cases
- Hooks without aliasing signature fall back to
DefaultNonmutatingHook
- Method calls with
mutableOnlyIfOperandsAreMutable have polymorphic effects
Mapping to CloudLang
What we'd skip:
- Entire shape system initially (React-specific)
- Hook signatures
- AliasingEffect signatures
Future consideration:
- Could adapt for Cloudflare Worker API shapes (fetch, KV, D1, etc.)
Visitor Patterns (visitors.ts)
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts
Purpose
Provides traversal utilities for HIR analysis passes (operands, successors, mapping, etc.)
Input Invariants
Output Guarantees
- All operands are visited exactly once
- All successors are enumerable
Key Traversal Abstractions
// Operand iteration
function* eachInstructionOperand(instr: Instruction): Iterable<Place>
function* eachInstructionValueOperand(instrValue: InstructionValue): Iterable<Place>
// LValue iteration
function* eachInstructionLValue(instr: ReactiveInstruction): Iterable<Place>
// Mapping functions
function mapInstructionOperands(instr: Instruction, fn: (Place) => Place): void
function mapInstructionValueOperands(instrValue: InstructionValue, fn: (Place) => Place): void
function mapTerminalSuccessors(terminal: Terminal, fn: (BlockId) => BlockId): Terminal
// Terminal queries
function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId>
function terminalFallthrough(terminal: Terminal): BlockId | null
function terminalHasFallthrough<T extends Terminal>(terminal: T): boolean
// Pattern iteration
function* eachPatternOperand(pattern: Pattern): Iterable<Place>
function mapPatternOperands(pattern: Pattern, fn: (Place) => Place): void
// Scope traversal
class ScopeBlockTraversal {
recordScopes(block: BasicBlock): void
isScopeActive(scopeId: ScopeId): boolean
get currentScope(): ScopeId | null
}
Edge Cases
- SwitchTerminal yields all case blocks as successors
- Some terminals (return, throw) have no successors
- MaybeThrowTerminal yields continuation and optional handler
Mapping to CloudLang
What we'd adapt:
- All operand iteration utilities (essential for dataflow)
- Terminal successor enumeration (essential for CFG analysis)
- Mapping functions (essential for transformation passes)
What we'd skip:
- ReactiveScope-specific traversal
- JSX-specific patterns
OCaml type sketches:
(* Operand iteration *)
let iter_instruction_operands : instruction -> place iterator
let iter_instruction_value_operands : instruction_value -> place iterator
let iter_terminal_successors : terminal -> block_id iterator
(* Terminal queries *)
let terminal_fallthrough : terminal -> block_id option
let has_fallthrough : terminal -> bool
(* Mapping functions *)
let map_instruction_operands : (place -> place) -> instruction -> instruction
let map_terminal_successors : (block_id -> block_id) -> terminal -> terminal
(* Pattern iteration *)
let iter_pattern_operands : pattern -> place iterator
let map_pattern_operands : (place -> place) -> pattern -> unit
PrintHIR (Debug Printing)
File
/home/jm/data/ai/cloudlang-trees/react/react/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts
Purpose
Human-readable HIR debugging output.
Output Format
function_name(param1, param2): return_place
bb0 (block):
[#0] Local x = Primitive { value: 42 }
[#1] y = Binary x + #0
[#2] If (#1) then:bb1 else:bb2 fallthrough=bb3
bb1 (value):
[#3] Return Explicit #1
bb2 (value):
[#4] Return Void <undefined>
bb3 (value):
[#5] Return Void <undefined>
Mapping to CloudLang
What we'd adapt:
- Same output format structure
- Block/ID/terminal representation
OCaml sketch:
let print_function (fn : hir_function) : string =
let header = Printf.sprintf "%s(%s): %s"
(Option.value_or "<anonymous>" fn.id)
(String.concat ", " (List.map print_place fn.params))
(print_place fn.returns)
in
header ^ "\n" ^ (print_hir fn.body)
and print_hir (hir : hir) : string =
String.concat "\n" (Map.to_list (fun (bid, block) ->
Printf.sprintf "bb%d (%s):\n%s"
bid
(string_of_block_kind block.kind)
(String.concat "\n" (List.map print_instruction block.instructions))
^ (print_terminal block.terminal)
) hir.blocks)
and print_instruction (instr : instruction) : string =
let value_str = print_instruction_value instr.value in
match instr.lvalue with
| Some place -> Printf.sprintf "[#%d] %s = %s" instr.id (print_place place) value_str
| None -> Printf.sprintf "[#%d] %s" instr.id value_str
and print_place (place : place) : string =
Printf.sprintf "%s(%s%s%s)"
(print_identifier place.identifier)
(string_of_effect place.effect)
(if place.reactive then " reactive" else "")
Ownership/Containment Hierarchy
HIRFunction
├── params: Array<Place>
├── context: Array<Place> (captured variables)
├── body: HIR
│ ├── entry: BlockId
│ └── blocks: Map<BlockId, BasicBlock>
│ └── BasicBlock
│ ├── id: BlockId
│ ├── kind: BlockKind
│ ├── instructions: Array<Instruction>
│ │ └── Instruction
│ │ ├── id: InstructionId
│ │ ├── lvalue: Place | null
│ │ │ └── Place
│ │ │ └── identifier: Identifier
│ │ │ ├── id: IdentifierId
│ │ │ ├── declarationId: DeclarationId
│ │ │ ├── name: IdentifierName | null
│ │ │ ├── mutableRange: MutableRange
│ │ │ ├── scope: ReactiveScope | null
│ │ │ └── type: Type
│ │ └── value: InstructionValue
│ │ └── (one of ~40 variants)
│ │ └── operands: Array<Place>
│ └── terminal: Terminal
│ └── (one of ~20 variants)
│ └── successors: Array<BlockId>
├── env: Environment
│ ├── bindings: Map<name, {node, identifier}>
│ └── shapeRegistry: ShapeRegistry
│ └── Map<string, ObjectShape>
│ └── ObjectShape
│ ├── properties: Map<string, Type>
│ └── functionType: FunctionSignature | null
└── returns: Place
Summary: CloudLang HIR Design Recommendations
Minimal V1 for Closure Analysis
type hir_function = {
id : string option;
params : place list;
body : hir;
returns : place option;
loc : location option;
}
and hir = {
entry : block_id;
blocks : (block_id, basic_block) Map.t;
}
and basic_block = {
kind : block_kind;
id : block_id;
instructions : instruction list;
terminal : terminal;
preds : block_id Set.t;
phis : phi Set.t;
}
and place = {
identifier : identifier;
effect : effect;
loc : location option;
}
and identifier = {
id : identifier_id;
declaration_id : declaration_id;
name : string option;
mutable_range : (instruction_id * instruction_id);
type : ir_type option; (* Start simple *)
loc : location option;
}
and instruction = {
id : instruction_id;
lvalue : place option;
value : instruction_value;
effects : effect list option; (* For future aliasing analysis *)
loc : location option;
}
and instruction_value =
| Primitive of primitive_value
| BinaryExpression of { op: binary_op; left: place; right: place }
| UnaryExpression of { op: unary_op; value: place }
| CallExpression of { callee: place; args: (place_or_spread list) }
| FunctionExpression of { params: (place_or_spread list); body: hir_function }
| LoadLocal of place
| StoreLocal of { lvalue: place; value: place; kind: var_kind option }
| DeclareLocal of { lvalue: place; kind: var_kind }
| Destructure of { lvalue: pattern; value: place }
| ArrayExpression of (place_or_spread_or_hole list)
| ObjectExpression of (obj_prop_or_spread list)
| PropertyLoad of { object: place; property: property_literal }
| PropertyStore of { object: place; property: property_literal; value: place }
| ComputedLoad of { object: place; property: place }
| ComputedStore of { object: place; property: place; value: place }
| Return
| Debugger
and terminal =
| Goto of { block: block_id; variant: goto_variant }
| If of { test: place; consequent: block_id; alternate: block_id; fallthrough: block_id }
| Branch of { test: place; consequent: block_id; alternate: block_id; fallthrough: block_id }
| While of { test: block_id; loop: block_id; fallthrough: block_id }
| For of { init: block_id; test: block_id; update: block_id option; loop: block_id; fallthrough: block_id }
| Try of { block: block_id; handler: place option; handler: block_id; fallthrough: block_id }
| Return
| Unreachable
and effect =
| Unknown
| Read
| Mutate
| Capture
| Store
| Freeze
and primitive_value =
| LString of string
| LNumber of float
| LBool of bool
| LNull
| LUndefined
Key Adaptation Decisions
- Simplify: Start with ~15 instruction variants (vs 40+ in React)
- Skip JSX: CloudLang targets Workers, not UI
- Skip React-specific: No reactive scopes, hooks, memoization
- Retain Core: SSA, CFG, effect tracking, mutable ranges
- Extensible Design: Use ADTs for easy addition of new variants
- OCaml idioms: Use GADTs for pattern matching exhaustiveness