diff --git a/compiler/.claude/settings.local.json b/compiler/.claude/settings.local.json index 9bbd18802e4..8571b9558f3 100644 --- a/compiler/.claude/settings.local.json +++ b/compiler/.claude/settings.local.json @@ -1,7 +1,12 @@ { "permissions": { "allow": [ - "Bash(node scripts/enable-feature-flag.js:*)" + "Bash(node scripts/enable-feature-flag.js:*)", + "Bash(yarn snap:*)", + "Bash(for test in \"error.invalid-access-ref-during-render\" \"error.invalid-ref-in-callback-invoked-during-render\" \"error.invalid-impure-functions-in-render-via-render-helper\")", + "Bash(do)", + "Bash(echo:*)", + "Bash(done)" ], "deny": [], "ask": [] diff --git a/compiler/CLAUDE.md b/compiler/CLAUDE.md new file mode 100644 index 00000000000..7a25725c659 --- /dev/null +++ b/compiler/CLAUDE.md @@ -0,0 +1,245 @@ +# React Compiler Knowledge Base + +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. + +## Project Structure + +- `packages/babel-plugin-react-compiler/` - Main compiler package + - `src/HIR/` - High-level Intermediate Representation types and utilities + - `src/Inference/` - Effect inference passes (aliasing, mutation, etc.) + - `src/Validation/` - Validation passes that check for errors + - `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering + - `src/__tests__/fixtures/compiler/` - Test fixtures + - `error.*.js` - Fixtures that should produce compilation errors + - `*.expect.md` - Expected output for each fixture + +## Running Tests + +```bash +# Run all tests +yarn snap + +# Run specific test by pattern +yarn snap -p + +# Update fixture outputs +yarn snap -u +``` + +## Version Control + +This repository uses Sapling (`sl`) for version control. Unlike git, Sapling does not require explicitly adding files to the staging area. + +```bash +# Check status +sl status + +# Commit all changes +sl commit -m "Your commit message" + +# Commit with multi-line message using heredoc +sl commit -m "$(cat <<'EOF' +Summary line + +Detailed description here +EOF +)" +``` + +## Key Concepts + +### HIR (High-level Intermediate Representation) + +The compiler converts source code to HIR for analysis. Key types in `src/HIR/HIR.ts`: + +- **HIRFunction** - A function being compiled + - `body.blocks` - Map of BasicBlocks + - `context` - Captured variables from outer scope + - `params` - Function parameters + - `returns` - The function's return place + - `aliasingEffects` - Effects that describe the function's behavior when called + +- **Instruction** - A single operation + - `lvalue` - The place being assigned to + - `value` - The instruction kind (CallExpression, FunctionExpression, LoadLocal, etc.) + - `effects` - Array of AliasingEffects for this instruction + +- **Terminal** - Block terminators (return, branch, etc.) + - `effects` - Array of AliasingEffects + +- **Place** - A reference to a value + - `identifier.id` - Unique IdentifierId + +- **Phi nodes** - Join points for values from different control flow paths + - Located at `block.phis` + - `phi.place` - The result place + - `phi.operands` - Map of predecessor block to source place + +### AliasingEffects System + +Effects describe data flow and operations. Defined in `src/Inference/AliasingEffects.ts`: + +**Data Flow Effects:** +- `Impure` - Marks a place as containing an impure value (e.g., Date.now() result, ref.current) +- `Capture a -> b` - Value from `a` is captured into `b` (mutable capture) +- `Alias a -> b` - `b` aliases `a` +- `ImmutableCapture a -> b` - Immutable capture (like Capture but read-only) +- `Assign a -> b` - Direct assignment +- `MaybeAlias a -> b` - Possible aliasing +- `CreateFrom a -> b` - Created from source + +**Mutation Effects:** +- `Mutate value` - Value is mutated +- `MutateTransitive value` - Value and transitive captures are mutated +- `MutateConditionally value` - May mutate +- `MutateTransitiveConditionally value` - May mutate transitively + +**Other Effects:** +- `Render place` - Place is used in render context (JSX props, component return) +- `Freeze place` - Place is frozen (made immutable) +- `Create place` - New value created +- `CreateFunction` - Function expression created, includes `captures` array +- `Apply` - Function application with receiver, function, args, and result + +### Hook Aliasing Signatures + +Located in `src/HIR/Globals.ts`, hooks can define custom aliasing signatures to control how data flows through them. + +**Structure:** +```typescript +aliasing: { + receiver: '@receiver', // The hook function itself + params: ['@param0'], // Named positional parameters + rest: '@rest', // Rest parameters (or null) + returns: '@returns', // Return value + temporaries: [], // Temporary values during execution + effects: [ // Array of effects to apply when hook is called + {kind: 'Freeze', value: '@param0', reason: ValueReason.HookCaptured}, + {kind: 'Assign', from: '@param0', into: '@returns'}, + ], +} +``` + +**Common patterns:** + +1. **RenderHookAliasing** (useState, useContext, useMemo, useCallback): + - Freezes arguments (`Freeze @rest`) + - Marks arguments as render-time (`Render @rest`) + - Creates frozen return value + - Aliases arguments to return + +2. **EffectHookAliasing** (useEffect, useLayoutEffect, useInsertionEffect): + - Freezes function and deps + - Creates internal effect object + - Captures function and deps into effect + - Returns undefined + +3. **Event handler hooks** (useEffectEvent): + - Freezes callback (`Freeze @fn`) + - Aliases input to return (`Assign @fn -> @returns`) + - NO Render effect (callback not called during render) + +**Example: useEffectEvent** +```typescript +const UseEffectEventHook = addHook( + DEFAULT_SHAPES, + { + positionalParams: [Effect.Freeze], // Takes one positional param + restParam: null, + returnType: {kind: 'Function', ...}, + calleeEffect: Effect.Read, + hookKind: 'useEffectEvent', + returnValueKind: ValueKind.Frozen, + aliasing: { + receiver: '@receiver', + params: ['@fn'], // Name for the callback parameter + rest: null, + returns: '@returns', + temporaries: [], + effects: [ + {kind: 'Freeze', value: '@fn', reason: ValueReason.HookCaptured}, + {kind: 'Assign', from: '@fn', into: '@returns'}, + // Note: NO Render effect - callback is not called during render + ], + }, + }, + BuiltInUseEffectEventId, +); + +// Add as both names for compatibility +['useEffectEvent', UseEffectEventHook], +['experimental_useEffectEvent', UseEffectEventHook], +``` + +**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. + +### Effect Inference Pipeline + +Effects are populated by `InferMutationAliasingEffects` (runs before validation): + +1. For `Date.now()`, `Math.random()` etc. - adds `Impure` effect (controlled by `validateNoImpureFunctionsInRender` config) +2. For `ref.current` access - adds `Impure` effect (controlled by `validateRefAccessDuringRender` config) +3. For return terminals - adds `Alias` from return value to `fn.returns` +4. For component/JSX returns - adds `Render` effect +5. For function expressions - adds `CreateFunction` effect with captures + +### Validation: validateNoImpureValuesInRender + +Located at `src/Validation/ValidateNoImpureValuesInRender.ts` + +**Purpose:** Detect when impure values (refs, Date.now results, etc.) flow into render context. + +**Algorithm:** +1. Track impure values in a Map +2. Track functions with impure returns separately (they're not impure values themselves) +3. Fixed-point iteration over all blocks: + - Process phi nodes (any impure operand makes result impure) + - Process instruction effects + - Process terminal effects + - Backwards propagation for mutated LoadLocal values +4. Validate: check all Render effects against impure values + +**Key patterns:** +- `Impure` effect marks the target as impure +- `Capture/Alias/etc` propagates impurity from source to target +- `Apply` propagates impurity from args/receiver to result +- `CreateFunction` propagates impurity from captured values (but NOT from body effects) +- If a value has both `Render` and `Capture` in same instruction, only error on Render (don't cascade) + +**Tracking functions with impure returns:** +- Separate from the `impure` map (function values aren't impure, just their returns) +- Populated when analyzing FunctionExpression bodies +- Used when: + 1. Calling the function - mark call result as impure + 2. Capturing the function - mark target as impure (for object.foo = impureFunc cases) + +**Backwards propagation:** +- When `$x = LoadLocal y` and `$x` is mutated with impure content, mark `y` as impure +- This handles: `const arr = []; arr.push(impure); render(arr)` + +## Known Issues / Edge Cases + +### Function Outlining +After `OutlineFunctions` pass, inner functions are replaced with `LoadGlobal(_temp)`. The validation runs BEFORE outlining, so it sees the original FunctionExpression. But be aware that test output shows post-outlining HIR. + +### SSA and LoadLocal +In SSA form, each `LoadLocal` creates a new identifier. When a loaded value is mutated: +- `$x = LoadLocal y` +- `mutate($x, impure)` +- `$z = LoadLocal y` (different from $x!) +- `render($z)` + +The impurity in $x must propagate back to y, then forward to $z. This requires backwards propagation in the fixed-point loop. + +## Configuration Flags + +In `Environment.ts` / test directives: +- `validateNoImpureFunctionsInRender` - Enable impure function validation (Date.now, Math.random, etc.) +- `validateRefAccessDuringRender` - Enable ref access validation + +## Debugging Tips + +1. Run `yarn snap -p ` to see full HIR output with effects +2. Look for `@aliasingEffects=` on FunctionExpressions +3. Look for `Impure`, `Render`, `Capture` effects on instructions +4. Check the pass ordering in Pipeline.ts to understand when effects are populated vs validated