|
| 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 | +- `packages/babel-plugin-react-compiler/` - Main compiler package |
| 8 | + - `src/HIR/` - High-level Intermediate Representation types and utilities |
| 9 | + - `src/Inference/` - Effect inference passes (aliasing, mutation, etc.) |
| 10 | + - `src/Validation/` - Validation passes that check for errors |
| 11 | + - `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering |
| 12 | + - `src/__tests__/fixtures/compiler/` - Test fixtures |
| 13 | + - `error.todo-*.js` - Unsupported feature, correctly throws Todo error (graceful bailout) |
| 14 | + - `error.bug-*.js` - Known bug, throws wrong error type or incorrect behavior |
| 15 | + - `*.expect.md` - Expected output for each fixture |
| 16 | + |
| 17 | +## Running Tests |
| 18 | + |
| 19 | +```bash |
| 20 | +# Run all tests |
| 21 | +yarn snap |
| 22 | + |
| 23 | +# Run tests matching a pattern |
| 24 | +# Example: yarn snap -p 'error.*' |
| 25 | +yarn snap -p <pattern> |
| 26 | + |
| 27 | +# Run a single fixture in debug mode. Use the path relative to the __tests__/fixtures/compiler directory |
| 28 | +# For each step of compilation, outputs the step name and state of the compiled program |
| 29 | +# Example: yarn snap -p simple.js -d |
| 30 | +yarn snap -p <file-basename> -d |
| 31 | + |
| 32 | +# Update fixture outputs (also works with -p) |
| 33 | +yarn snap -u |
| 34 | +``` |
| 35 | + |
| 36 | +## Version Control |
| 37 | + |
| 38 | +This repository uses Sapling (`sl`) for version control. Sapling is similar to Mercurial: there is not staging area, but new/deleted files must be explicitlyu added/removed. |
| 39 | + |
| 40 | +```bash |
| 41 | +# Check status |
| 42 | +sl status |
| 43 | + |
| 44 | +# Add new files, remove deleted files |
| 45 | +sl addremove |
| 46 | + |
| 47 | +# Commit all changes |
| 48 | +sl commit -m "Your commit message" |
| 49 | + |
| 50 | +# Commit with multi-line message using heredoc |
| 51 | +sl commit -m "$(cat <<'EOF' |
| 52 | +Summary line |
| 53 | +
|
| 54 | +Detailed description here |
| 55 | +EOF |
| 56 | +)" |
| 57 | +``` |
| 58 | + |
| 59 | +## Key Concepts |
| 60 | + |
| 61 | +### HIR (High-level Intermediate Representation) |
| 62 | + |
| 63 | +The compiler converts source code to HIR for analysis. Key types in `src/HIR/HIR.ts`: |
| 64 | + |
| 65 | +- **HIRFunction** - A function being compiled |
| 66 | + - `body.blocks` - Map of BasicBlocks |
| 67 | + - `context` - Captured variables from outer scope |
| 68 | + - `params` - Function parameters |
| 69 | + - `returns` - The function's return place |
| 70 | + - `aliasingEffects` - Effects that describe the function's behavior when called |
| 71 | + |
| 72 | +- **Instruction** - A single operation |
| 73 | + - `lvalue` - The place being assigned to |
| 74 | + - `value` - The instruction kind (CallExpression, FunctionExpression, LoadLocal, etc.) |
| 75 | + - `effects` - Array of AliasingEffects for this instruction |
| 76 | + |
| 77 | +- **Terminal** - Block terminators (return, branch, etc.) |
| 78 | + - `effects` - Array of AliasingEffects |
| 79 | + |
| 80 | +- **Place** - A reference to a value |
| 81 | + - `identifier.id` - Unique IdentifierId |
| 82 | + |
| 83 | +- **Phi nodes** - Join points for values from different control flow paths |
| 84 | + - Located at `block.phis` |
| 85 | + - `phi.place` - The result place |
| 86 | + - `phi.operands` - Map of predecessor block to source place |
| 87 | + |
| 88 | +### AliasingEffects System |
| 89 | + |
| 90 | +Effects describe data flow and operations. Defined in `src/Inference/AliasingEffects.ts`: |
| 91 | + |
| 92 | +**Data Flow Effects:** |
| 93 | +- `Impure` - Marks a place as containing an impure value (e.g., Date.now() result, ref.current) |
| 94 | +- `Capture a -> b` - Value from `a` is captured into `b` (mutable capture) |
| 95 | +- `Alias a -> b` - `b` aliases `a` |
| 96 | +- `ImmutableCapture a -> b` - Immutable capture (like Capture but read-only) |
| 97 | +- `Assign a -> b` - Direct assignment |
| 98 | +- `MaybeAlias a -> b` - Possible aliasing |
| 99 | +- `CreateFrom a -> b` - Created from source |
| 100 | + |
| 101 | +**Mutation Effects:** |
| 102 | +- `Mutate value` - Value is mutated |
| 103 | +- `MutateTransitive value` - Value and transitive captures are mutated |
| 104 | +- `MutateConditionally value` - May mutate |
| 105 | +- `MutateTransitiveConditionally value` - May mutate transitively |
| 106 | + |
| 107 | +**Other Effects:** |
| 108 | +- `Render place` - Place is used in render context (JSX props, component return) |
| 109 | +- `Freeze place` - Place is frozen (made immutable) |
| 110 | +- `Create place` - New value created |
| 111 | +- `CreateFunction` - Function expression created, includes `captures` array |
| 112 | +- `Apply` - Function application with receiver, function, args, and result |
| 113 | + |
| 114 | +### Hook Aliasing Signatures |
| 115 | + |
| 116 | +Located in `src/HIR/Globals.ts`, hooks can define custom aliasing signatures to control how data flows through them. |
| 117 | + |
| 118 | +**Structure:** |
| 119 | +```typescript |
| 120 | +aliasing: { |
| 121 | + receiver: '@receiver', // The hook function itself |
| 122 | + params: ['@param0'], // Named positional parameters |
| 123 | + rest: '@rest', // Rest parameters (or null) |
| 124 | + returns: '@returns', // Return value |
| 125 | + temporaries: [], // Temporary values during execution |
| 126 | + effects: [ // Array of effects to apply when hook is called |
| 127 | + {kind: 'Freeze', value: '@param0', reason: ValueReason.HookCaptured}, |
| 128 | + {kind: 'Assign', from: '@param0', into: '@returns'}, |
| 129 | + ], |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +**Common patterns:** |
| 134 | + |
| 135 | +1. **RenderHookAliasing** (useState, useContext, useMemo, useCallback): |
| 136 | + - Freezes arguments (`Freeze @rest`) |
| 137 | + - Marks arguments as render-time (`Render @rest`) |
| 138 | + - Creates frozen return value |
| 139 | + - Aliases arguments to return |
| 140 | + |
| 141 | +2. **EffectHookAliasing** (useEffect, useLayoutEffect, useInsertionEffect): |
| 142 | + - Freezes function and deps |
| 143 | + - Creates internal effect object |
| 144 | + - Captures function and deps into effect |
| 145 | + - Returns undefined |
| 146 | + |
| 147 | +3. **Event handler hooks** (useEffectEvent): |
| 148 | + - Freezes callback (`Freeze @fn`) |
| 149 | + - Aliases input to return (`Assign @fn -> @returns`) |
| 150 | + - NO Render effect (callback not called during render) |
| 151 | + |
| 152 | +**Example: useEffectEvent** |
| 153 | +```typescript |
| 154 | +const UseEffectEventHook = addHook( |
| 155 | + DEFAULT_SHAPES, |
| 156 | + { |
| 157 | + positionalParams: [Effect.Freeze], // Takes one positional param |
| 158 | + restParam: null, |
| 159 | + returnType: {kind: 'Function', ...}, |
| 160 | + calleeEffect: Effect.Read, |
| 161 | + hookKind: 'useEffectEvent', |
| 162 | + returnValueKind: ValueKind.Frozen, |
| 163 | + aliasing: { |
| 164 | + receiver: '@receiver', |
| 165 | + params: ['@fn'], // Name for the callback parameter |
| 166 | + rest: null, |
| 167 | + returns: '@returns', |
| 168 | + temporaries: [], |
| 169 | + effects: [ |
| 170 | + {kind: 'Freeze', value: '@fn', reason: ValueReason.HookCaptured}, |
| 171 | + {kind: 'Assign', from: '@fn', into: '@returns'}, |
| 172 | + // Note: NO Render effect - callback is not called during render |
| 173 | + ], |
| 174 | + }, |
| 175 | + }, |
| 176 | + BuiltInUseEffectEventId, |
| 177 | +); |
| 178 | + |
| 179 | +// Add as both names for compatibility |
| 180 | +['useEffectEvent', UseEffectEventHook], |
| 181 | +['experimental_useEffectEvent', UseEffectEventHook], |
| 182 | +``` |
| 183 | + |
| 184 | +**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. |
| 185 | + |
| 186 | +## Feature Flags |
| 187 | + |
| 188 | +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: |
| 189 | + |
| 190 | +```javascript |
| 191 | +// enableJsxOutlining @enableChangeVariableCodegen:false |
| 192 | + |
| 193 | +...code... |
| 194 | +``` |
| 195 | + |
| 196 | +Would enable the `enableJsxOutlining` feature and disable the `enableChangeVariableCodegen` feature. |
| 197 | + |
| 198 | +## Debugging Tips |
| 199 | + |
| 200 | +1. Run `yarn snap -p <fixture>` to see full HIR output with effects |
| 201 | +2. Look for `@aliasingEffects=` on FunctionExpressions |
| 202 | +3. Look for `Impure`, `Render`, `Capture` effects on instructions |
| 203 | +4. Check the pass ordering in Pipeline.ts to understand when effects are populated vs validated |
| 204 | + |
| 205 | +## Error Handling for Unsupported Features |
| 206 | + |
| 207 | +When the compiler encounters an unsupported but known pattern, use `CompilerError.throwTodo()` instead of `CompilerError.invariant()`. Todo errors cause graceful bailouts in production; Invariant errors are hard failures indicating unexpected/invalid states. |
| 208 | + |
| 209 | +```typescript |
| 210 | +// Unsupported but expected pattern - graceful bailout |
| 211 | +CompilerError.throwTodo({ |
| 212 | + reason: `Support [description of unsupported feature]`, |
| 213 | + loc: terminal.loc, |
| 214 | +}); |
| 215 | + |
| 216 | +// Invariant is for truly unexpected/invalid states - hard failure |
| 217 | +CompilerError.invariant(false, { |
| 218 | + reason: `Unexpected [thing]`, |
| 219 | + loc: terminal.loc, |
| 220 | +}); |
| 221 | +``` |
0 commit comments