| 1 | # React Compiler Knowledge Base |
| 2 | |
| 3 | This document contains knowledge about the React Compiler gathered during development sessions. It serves as a reference for understanding the codebase architecture and key concepts. |
| 4 | |
| 5 | ## Project Structure |
| 6 | |
| 7 | When modifying the compiler, you MUST read the documentation about that pass in `compiler/packages/babel-plugin-react-compiler/docs/passes/` to learn more about the role of that pass within the compiler. |
| 8 | |
| 9 | - `packages/babel-plugin-react-compiler/` - Main compiler package |
| 10 | - `src/HIR/` - High-level Intermediate Representation types and utilities |
| 11 | - `src/Inference/` - Effect inference passes (aliasing, mutation, etc.) |
| 12 | - `src/Validation/` - Validation passes that check for errors |
| 13 | - `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering |
| 14 | - `src/__tests__/fixtures/compiler/` - Test fixtures |
| 15 | - `error.todo-*.js` - Unsupported feature, correctly throws Todo error (graceful bailout) |
| 16 | - `error.bug-*.js` - Known bug, throws wrong error type or incorrect behavior |
| 17 | - `*.expect.md` - Expected output for each fixture |
| 18 | |
| 19 | ## Running Tests |
| 20 | |
| 21 | ```bash |
| 22 | # Run all tests |
| 23 | yarn snap |
| 24 | |
| 25 | # Run tests matching a pattern |
| 26 | # Example: yarn snap -p 'error.*' |
| 27 | yarn snap -p <pattern> |
| 28 | |
| 29 | # Run a single fixture in debug mode. Use the path relative to the __tests__/fixtures/compiler directory |
| 30 | # For each step of compilation, outputs the step name and state of the compiled program |
| 31 | # Example: yarn snap -p simple.js -d |
| 32 | yarn snap -p <file-basename> -d |
| 33 | |
| 34 | # Update fixture outputs (also works with -p) |
| 35 | yarn snap -u |
| 36 | ``` |
| 37 | |
| 38 | ## Linting |
| 39 | |
| 40 | ```bash |
| 41 | # Run lint on the compiler source |
| 42 | yarn workspace babel-plugin-react-compiler lint |
| 43 | ``` |
| 44 | |
| 45 | ## Formatting |
| 46 | |
| 47 | ```bash |
| 48 | # Run prettier on all files (from the react root directory, not compiler/) |
| 49 | yarn prettier-all |
| 50 | ``` |
| 51 | |
| 52 | ## Compiling Arbitrary Files |
| 53 | |
| 54 | Use `yarn snap compile` to compile any file (not just fixtures) with the React Compiler: |
| 55 | |
| 56 | ```bash |
| 57 | # Compile a file and see the output |
| 58 | yarn snap compile <path> |
| 59 | |
| 60 | # Compile with debug logging to see the state after each compiler pass |
| 61 | # This is an alternative to `yarn snap -d -p <pattern>` when you don't have a fixture file yet |
| 62 | yarn snap compile --debug <path> |
| 63 | ``` |
| 64 | |
| 65 | ## Minimizing Test Cases |
| 66 | |
| 67 | Use `yarn snap minimize` to automatically reduce a failing test case to its minimal reproduction: |
| 68 | |
| 69 | ```bash |
| 70 | # Minimize a file that causes a compiler error |
| 71 | yarn snap minimize <path> |
| 72 | |
| 73 | # Minimize and update the file in-place with the minimized version |
| 74 | yarn snap minimize --update <path> |
| 75 | ``` |
| 76 | |
| 77 | ## Version Control |
| 78 | |
| 79 | This repository uses Sapling (`sl`) for version control. Sapling is similar to Mercurial: there is not staging area, but new/deleted files must be explicitly added/removed. |
| 80 | |
| 81 | ```bash |
| 82 | # Check status |
| 83 | sl status |
| 84 | |
| 85 | # Add new files, remove deleted files |
| 86 | sl addremove |
| 87 | |
| 88 | # Commit all changes |
| 89 | sl commit -m "Your commit message" |
| 90 | |
| 91 | # Commit with multi-line message using heredoc |
| 92 | sl commit -m "$(cat <<'EOF' |
| 93 | Summary line |
| 94 | |
| 95 | Detailed description here |
| 96 | EOF |
| 97 | )" |
| 98 | ``` |
| 99 | |
| 100 | ## Key Concepts |
| 101 | |
| 102 | ### HIR (High-level Intermediate Representation) |
| 103 | |
| 104 | The compiler converts source code to HIR for analysis. Key types in `src/HIR/HIR.ts`: |
| 105 | |
| 106 | - **HIRFunction** - A function being compiled |
| 107 | - `body.blocks` - Map of BasicBlocks |
| 108 | - `context` - Captured variables from outer scope |
| 109 | - `params` - Function parameters |
| 110 | - `returns` - The function's return place |
| 111 | - `aliasingEffects` - Effects that describe the function's behavior when called |
| 112 | |
| 113 | - **Instruction** - A single operation |
| 114 | - `lvalue` - The place being assigned to |
| 115 | - `value` - The instruction kind (CallExpression, FunctionExpression, LoadLocal, etc.) |
| 116 | - `effects` - Array of AliasingEffects for this instruction |
| 117 | |
| 118 | - **Terminal** - Block terminators (return, branch, etc.) |
| 119 | - `effects` - Array of AliasingEffects |
| 120 | |
| 121 | - **Place** - A reference to a value |
| 122 | - `identifier.id` - Unique IdentifierId |
| 123 | |
| 124 | - **Phi nodes** - Join points for values from different control flow paths |
| 125 | - Located at `block.phis` |
| 126 | - `phi.place` - The result place |
| 127 | - `phi.operands` - Map of predecessor block to source place |
| 128 | |
| 129 | ### AliasingEffects System |
| 130 | |
| 131 | Effects describe data flow and operations. Defined in `src/Inference/AliasingEffects.ts`: |
| 132 | |
| 133 | **Data Flow Effects:** |
| 134 | - `Impure` - Marks a place as containing an impure value (e.g., Date.now() result, ref.current) |
| 135 | - `Capture a -> b` - Value from `a` is captured into `b` (mutable capture) |
| 136 | - `Alias a -> b` - `b` aliases `a` |
| 137 | - `ImmutableCapture a -> b` - Immutable capture (like Capture but read-only) |
| 138 | - `Assign a -> b` - Direct assignment |
| 139 | - `MaybeAlias a -> b` - Possible aliasing |
| 140 | - `CreateFrom a -> b` - Created from source |
| 141 | |
| 142 | **Mutation Effects:** |
| 143 | - `Mutate value` - Value is mutated |
| 144 | - `MutateTransitive value` - Value and transitive captures are mutated |
| 145 | - `MutateConditionally value` - May mutate |
| 146 | - `MutateTransitiveConditionally value` - May mutate transitively |
| 147 | |
| 148 | **Other Effects:** |
| 149 | - `Render place` - Place is used in render context (JSX props, component return) |
| 150 | - `Freeze place` - Place is frozen (made immutable) |
| 151 | - `Create place` - New value created |
| 152 | - `CreateFunction` - Function expression created, includes `captures` array |
| 153 | - `Apply` - Function application with receiver, function, args, and result |
| 154 | |
| 155 | ### Hook Aliasing Signatures |
| 156 | |
| 157 | Located in `src/HIR/Globals.ts`, hooks can define custom aliasing signatures to control how data flows through them. |
| 158 | |
| 159 | **Structure:** |
| 160 | ```typescript |
| 161 | aliasing: { |
| 162 | receiver: '@receiver', // The hook function itself |
| 163 | params: ['@param0'], // Named positional parameters |
| 164 | rest: '@rest', // Rest parameters (or null) |
| 165 | returns: '@returns', // Return value |
| 166 | temporaries: [], // Temporary values during execution |
| 167 | effects: [ // Array of effects to apply when hook is called |
| 168 | {kind: 'Freeze', value: '@param0', reason: ValueReason.HookCaptured}, |
| 169 | {kind: 'Assign', from: '@param0', into: '@returns'}, |
| 170 | ], |
| 171 | } |
| 172 | ``` |
| 173 | |
| 174 | **Common patterns:** |
| 175 | |
| 176 | 1. **RenderHookAliasing** (useState, useContext, useMemo, useCallback): |
| 177 | - Freezes arguments (`Freeze @rest`) |
| 178 | - Marks arguments as render-time (`Render @rest`) |
| 179 | - Creates frozen return value |
| 180 | - Aliases arguments to return |
| 181 | |
| 182 | 2. **EffectHookAliasing** (useEffect, useLayoutEffect, useInsertionEffect): |
| 183 | - Freezes function and deps |
| 184 | - Creates internal effect object |
| 185 | - Captures function and deps into effect |
| 186 | - Returns undefined |
| 187 | |
| 188 | 3. **Event handler hooks** (useEffectEvent): |
| 189 | - Freezes callback (`Freeze @fn`) |
| 190 | - Aliases input to return (`Assign @fn -> @returns`) |
| 191 | - NO Render effect (callback not called during render) |
| 192 | |
| 193 | **Example: useEffectEvent** |
| 194 | ```typescript |
| 195 | const UseEffectEventHook = addHook( |
| 196 | DEFAULT_SHAPES, |
| 197 | { |
| 198 | positionalParams: [Effect.Freeze], // Takes one positional param |
| 199 | restParam: null, |
| 200 | returnType: {kind: 'Function', ...}, |
| 201 | calleeEffect: Effect.Read, |
| 202 | hookKind: 'useEffectEvent', |
| 203 | returnValueKind: ValueKind.Frozen, |
| 204 | aliasing: { |
| 205 | receiver: '@receiver', |
| 206 | params: ['@fn'], // Name for the callback parameter |
| 207 | rest: null, |
| 208 | returns: '@returns', |
| 209 | temporaries: [], |
| 210 | effects: [ |
| 211 | {kind: 'Freeze', value: '@fn', reason: ValueReason.HookCaptured}, |
| 212 | {kind: 'Assign', from: '@fn', into: '@returns'}, |
| 213 | // Note: NO Render effect - callback is not called during render |
| 214 | ], |
| 215 | }, |
| 216 | }, |
| 217 | BuiltInUseEffectEventId, |
| 218 | ); |
| 219 | |
| 220 | // Add as both names for compatibility |
| 221 | ['useEffectEvent', UseEffectEventHook], |
| 222 | ['experimental_useEffectEvent', UseEffectEventHook], |
| 223 | ``` |
| 224 | |
| 225 | **Key insight:** If a hook is missing an `aliasing` config, it falls back to `DefaultNonmutatingHook` which includes a `Render` effect on all arguments. This can cause false positives for hooks like `useEffectEvent` whose callbacks are not called during render. |
| 226 | |
| 227 | ## Feature Flags |
| 228 | |
| 229 | Feature flags are configured in `src/HIR/Environment.ts`, for example `enableJsxOutlining`. Test fixtures can override the active feature flags used for that fixture via a comment pragma on the first line of the fixture input, for example: |
| 230 | |
| 231 | ```javascript |
| 232 | // enableJsxOutlining @enableNameAnonymousFunctions:false |
| 233 | |
| 234 | ...code... |
| 235 | ``` |
| 236 | |
| 237 | Would enable the `enableJsxOutlining` feature and disable the `enableNameAnonymousFunctions` feature. |
| 238 | |
| 239 | ## Rust Port (Active) |
| 240 | |
| 241 | Work is tracked in `compiler/docs/rust-port/` with numbered plan docs. |
| 242 | Rust crates live in `compiler/crates/`. |
| 243 | |
| 244 | ### Before implementing from a plan: |
| 245 | - Run `git log --oneline --grep="<plan-name>"` to see what's already done |
| 246 | - Read the plan doc's Remaining Work / Status section |
| 247 | - Only implement what's actually remaining |
| 248 | |
| 249 | ### After implementing: |
| 250 | - Update the plan doc's status |
| 251 | - Run `/compiler-verify` |
| 252 | - Ensure `compiler/scripts/test-babel-ast.sh` passes |
| 253 | |
| 254 | ## Debugging Tips |
| 255 | |
| 256 | 1. Run `yarn snap -p <fixture>` to see full HIR output with effects |
| 257 | 2. Look for `@aliasingEffects=` on FunctionExpressions |
| 258 | 3. Look for `Impure`, `Render`, `Capture` effects on instructions |
| 259 | 4. Check the pass ordering in Pipeline.ts to understand when effects are populated vs validated |
| 260 | |
| 261 | ## Error Handling and Fault Tolerance |
| 262 | |
| 263 | The compiler is fault-tolerant: it runs all passes and accumulates errors on the `Environment` rather than throwing on the first error. This lets users see all compilation errors at once. |
| 264 | |
| 265 | **Recording errors** — Passes record errors via `env.recordError(diagnostic)`. Errors are accumulated on `Environment.#errors` and checked at the end of the pipeline via `env.hasErrors()` / `env.aggregateErrors()`. |
| 266 | |
| 267 | **`tryRecord()` wrapper** — In Pipeline.ts, validation passes are wrapped in `env.tryRecord(() => pass(hir))` which catches thrown `CompilerError`s (non-invariant) and records them. Infrastructure/transformation passes are NOT wrapped in `tryRecord()` because later passes depend on their output being structurally valid. |
| 268 | |
| 269 | **Error categories:** |
| 270 | - `CompilerError.throwTodo()` — Unsupported but known pattern. Graceful bailout. Can be caught by `tryRecord()`. |
| 271 | - `CompilerError.invariant()` — Truly unexpected/invalid state. Always throws immediately, never caught by `tryRecord()`. |
| 272 | - Non-`CompilerError` exceptions — Always re-thrown. |
| 273 | |
| 274 | **Key files:** `Environment.ts` (`recordError`, `tryRecord`, `hasErrors`, `aggregateErrors`), `Pipeline.ts` (pass orchestration), `Program.ts` (`tryCompileFunction` handles the `Result`). |
| 275 | |
| 276 | **Test fixtures:** `__tests__/fixtures/compiler/fault-tolerance/` contains multi-error fixtures verifying all errors are reported. |