diff --git a/compiler/CLAUDE.md b/compiler/CLAUDE.md index 94a7d4b5d6d9..db19d2cb8a86 100644 --- a/compiler/CLAUDE.md +++ b/compiler/CLAUDE.md @@ -215,12 +215,12 @@ const UseEffectEventHook = addHook( 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: ```javascript -// enableJsxOutlining @enableChangeVariableCodegen:false +// enableJsxOutlining @enableNameAnonymousFunctions:false ...code... ``` -Would enable the `enableJsxOutlining` feature and disable the `enableChangeVariableCodegen` feature. +Would enable the `enableJsxOutlining` feature and disable the `enableNameAnonymousFunctions` feature. ## Debugging Tips diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/disableMemoizationForDebugging-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/disableMemoizationForDebugging-output.txt deleted file mode 100644 index c0a2769bb3fa..000000000000 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/disableMemoizationForDebugging-output.txt +++ /dev/null @@ -1,14 +0,0 @@ -import { c as _c } from "react/compiler-runtime"; -export default function TestComponent(t0) { - const $ = _c(2); - const { x } = t0; - let t1; - if ($[0] !== x || true) { - t1 = ; - $[0] = x; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 8ad9f122aab3..20596e5d93bf 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -283,37 +283,6 @@ test('error is displayed when config has validation error', async ({page}) => { expect(output.replace(/\s+/g, ' ')).toContain('Unexpected compilationMode'); }); -test('disableMemoizationForDebugging flag works as expected', async ({ - page, -}) => { - const store: Store = { - source: TEST_SOURCE, - config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist'; - -({ - environment: { - disableMemoizationForDebugging: true - } -} satisfies PluginOptions);`, - showInternals: false, - }; - const hash = encodeStore(store); - await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); - await page.waitForFunction(isMonacoLoaded); - await expandConfigs(page); - await page.screenshot({ - fullPage: true, - path: 'test-results/07-config-disableMemoizationForDebugging-flag.png', - }); - - const text = - (await page.locator('.monaco-editor-output').allInnerTexts()) ?? []; - const output = await formatPrint(text); - - expect(output).not.toEqual(''); - expect(output).toMatchSnapshot('disableMemoizationForDebugging-output.txt'); -}); - test('error is displayed when source has syntax error', async ({page}) => { const syntaxErrorSource = `function TestComponent(props) { const oops = props. diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/06-inferTypes.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/06-inferTypes.md index eca37e884312..10878fa4a525 100644 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/06-inferTypes.md +++ b/compiler/packages/babel-plugin-react-compiler/docs/passes/06-inferTypes.md @@ -70,9 +70,6 @@ The `occursCheck` method prevents infinite types by detecting when a type variab - `DeclareContext` and `LoadContext` generate no type equations (intentionally untyped) - `StoreContext` with `Const` kind does propagate the rvalue type to enable ref inference through context variables -### Event Handler Inference -When `enableInferEventHandlers` is enabled, JSX props starting with "on" (e.g., `onClick`) on built-in DOM elements (excluding web components with hyphens) are inferred as `Function`. - ## TODOs 1. **Hook vs Function type ambiguity**: > "TODO: callee could be a hook or a function, so this type equation isn't correct. We should change Hook to a subtype of Function or change unifier logic." diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/31-codegenReactiveFunction.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/31-codegenReactiveFunction.md index a2132990ff98..922b2e9dec47 100644 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/31-codegenReactiveFunction.md +++ b/compiler/packages/babel-plugin-react-compiler/docs/passes/31-codegenReactiveFunction.md @@ -205,8 +205,6 @@ if ($[0] !== "source_hash_abc123") { } ``` -### Change Detection for Debugging -When `enableChangeDetectionForDebugging` is configured, additional code is generated to detect when cached values unexpectedly change. ### Labeled Breaks Control flow with labeled breaks (for early returns or loop exits) uses `codegenLabel` to generate consistent label names: @@ -231,7 +229,6 @@ type CodegenFunction = { prunedMemoBlocks: number; // Scopes that were pruned prunedMemoValues: number; // Values in pruned scopes hasInferredEffect: boolean; - hasFireRewrite: boolean; }; ``` diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/32-transformFire.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/32-transformFire.md deleted file mode 100644 index 26ee425ceea4..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/32-transformFire.md +++ /dev/null @@ -1,203 +0,0 @@ -# transformFire - -## File -`src/Transform/TransformFire.ts` - -## Purpose -This pass transforms `fire(fn())` calls inside `useEffect` lambdas into calls to a `useFire` hook that provides stable function references. The `fire()` function is a React API that allows effect callbacks to call functions with their current values while maintaining stable effect dependencies. - -Without this transform, if an effect depends on a function that changes every render, the effect would re-run on every render. The `useFire` hook provides a stable wrapper that always calls the latest version of the function. - -## Input Invariants -- The `enableFire` feature flag must be enabled -- `fire()` calls must only appear inside `useEffect` lambdas -- Each `fire()` call must have exactly one argument (a function call expression) -- The function being fired must be consistent across all `fire()` calls in the same effect - -## Output Guarantees -- All `fire(fn(...args))` calls are replaced with direct calls `fired_fn(...args)` -- A `useFire(fn)` hook call is inserted before the `useEffect` -- The fired function is stored in a temporary and captured by the effect -- The original function `fn` is removed from the effect's captured context - -## Algorithm - -### Phase 1: Find Fire Calls -```typescript -function replaceFireFunctions(fn: HIRFunction, context: Context): void { - // For each useEffect call instruction: - // 1. Find all fire() calls in the effect lambda - // 2. Validate they have proper arguments - // 3. Track which functions are being fired - - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - if (isUseEffectCall(instr)) { - const lambda = getEffectLambda(instr); - findAndReplaceFireCalls(lambda, fireFunctions); - } - } - } -} -``` - -### Phase 2: Insert useFire Hooks -For each function being fired, insert a `useFire` call: -```typescript -// Before: -useEffect(() => { - fire(foo(props)); -}, [foo, props]); - -// After: -const t0 = useFire(foo); -useEffect(() => { - t0(props); -}, [t0, props]); -``` - -### Phase 3: Replace Fire Calls -Transform `fire(fn(...args))` to `firedFn(...args)`: -```typescript -// The fire() wrapper is removed -// The inner function call uses the useFire'd version -fire(foo(x, y)) → t0(x, y) // where t0 = useFire(foo) -``` - -### Phase 4: Validate No Remaining Fire Uses -```typescript -function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { - // Ensure all fire() uses have been transformed - // Report errors for any remaining fire() calls -} -``` - -## Edge Cases - -### Fire Outside Effect -`fire()` calls outside `useEffect` lambdas cause a validation error: -```javascript -// ERROR: fire() can only be used inside useEffect -function Component() { - fire(callback()); -} -``` - -### Mixed Fire and Non-Fire Calls -All calls to the same function must either all use `fire()` or none: -```javascript -// ERROR: Cannot mix fire() and non-fire calls -useEffect(() => { - fire(foo(x)); - foo(y); // Error: foo is used with and without fire() -}); -``` - -### Multiple Arguments to Fire -`fire()` accepts exactly one argument (the function call): -```javascript -// ERROR: fire() takes exactly one argument -fire(foo, bar) // Invalid -fire() // Invalid -``` - -### Nested Effects -Fire calls in nested effects are validated separately: -```javascript -useEffect(() => { - useEffect(() => { // Error: nested effects not allowed - fire(foo()); - }); -}); -``` - -### Deep Scope Handling -The pass handles fire calls within deeply nested scopes inside effects: -```javascript -useEffect(() => { - if (cond) { - while (x) { - fire(foo(x)); // Still transformed correctly - } - } -}); -``` - -## TODOs -None in the source file. - -## Example - -### Fixture: `transform-fire/basic.js` - -**Input:** -```javascript -// @enableFire -function Component(props) { - const foo = (props_0) => { - console.log(props_0); - }; - useEffect(() => { - fire(foo(props)); - }); - return null; -} -``` - -**After TransformFire:** -``` -bb0 (block): - [1] $25 = Function @context[] ... // foo definition - [2] StoreLocal Const foo$32 = $25 - [3] $45 = LoadGlobal import { useFire } from 'react/compiler-runtime' - [4] $46 = LoadLocal foo$32 - [5] $47 = Call $45($46) // useFire(foo) - [6] StoreLocal Const #t44$44 = $47 - [7] $34 = LoadGlobal(global) useEffect - [8] $35 = Function @context[#t44$44, props$24] ... - <>(): - [1] $37 = LoadLocal #t44$44 // Load the fired function - [2] $38 = LoadLocal props$24 - [3] $39 = Call $37($38) // Call it directly (no fire wrapper) - [4] Return Void - [9] Call $34($35) // useEffect(lambda) - [10] Return null -``` - -**Generated Code:** -```javascript -import { useFire as _useFire } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(4); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = (props_0) => { - console.log(props_0); - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const foo = t0; - const t1 = _useFire(foo); - let t2; - if ($[1] !== props || $[2] !== t1) { - t2 = () => { - t1(props); - }; - $[1] = props; - $[2] = t1; - $[3] = t2; - } else { - t2 = $[3]; - } - useEffect(t2); - return null; -} -``` - -Key observations: -- `useFire` is imported from `react/compiler-runtime` -- `fire(foo(props))` becomes `t1(props)` where `t1 = _useFire(foo)` -- The effect now depends on `t1` (stable) and `props` (reactive) -- The original `foo` function is memoized and passed to `useFire` diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/33-lowerContextAccess.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/33-lowerContextAccess.md deleted file mode 100644 index e91bd3ec8e83..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/33-lowerContextAccess.md +++ /dev/null @@ -1,174 +0,0 @@ -# lowerContextAccess - -## File -`src/Optimization/LowerContextAccess.ts` - -## Purpose -This pass optimizes `useContext` calls by generating selector functions that extract only the needed properties from the context. Instead of subscribing to the entire context object, components can subscribe to specific slices, enabling more granular re-rendering. - -When a component destructures specific properties from a context, this pass transforms the `useContext` call to use a selector-based API that only triggers re-renders when the selected properties change. - -## Input Invariants -- The `lowerContextAccess` configuration must be set with: - - `source`: The module to import the lowered context hook from - - `importSpecifierName`: The name of the hook function -- The function must use `useContext` with destructuring patterns -- Only object destructuring patterns with identifier values are supported - -## Output Guarantees -- `useContext(Ctx)` calls with destructuring are replaced with selector calls -- A selector function is generated that extracts the needed properties -- The return type is changed from object to array for positional access -- Unused original `useContext` calls are removed by dead code elimination - -## Algorithm - -### Phase 1: Collect Context Access Patterns -```typescript -function lowerContextAccess(fn: HIRFunction, config: ExternalFunction): void { - const contextAccess: Map = new Map(); - const contextKeys: Map> = new Map(); - - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - // Find useContext calls - if (isUseContextCall(instr)) { - contextAccess.set(instr.lvalue.identifier.id, instr.value); - } - - // Find destructuring patterns that access context results - if (isDestructure(instr) && contextAccess.has(instr.value.value.id)) { - const keys = extractPropertyKeys(instr.value.pattern); - contextKeys.set(instr.value.value.id, keys); - } - } - } -} -``` - -### Phase 2: Generate Selector Functions -For each context access with known keys: -```typescript -// Original: -const {foo, bar} = useContext(MyContext); - -// Selector function generated: -(ctx) => [ctx.foo, ctx.bar] -``` - -### Phase 3: Transform Context Calls -```typescript -// Before: -$0 = useContext(MyContext) -{foo, bar} = $0 - -// After: -$0 = useContext_withSelector(MyContext, (ctx) => [ctx.foo, ctx.bar]) -[foo, bar] = $0 -``` - -### Phase 4: Update Destructuring -Change object destructuring to array destructuring to match selector return: -```typescript -// Before: { foo: foo$15, bar: bar$16 } = $14 -// After: [ foo$15, bar$16 ] = $14 -``` - -## Edge Cases - -### Dynamic Property Access -If context properties are accessed dynamically (not through destructuring), the optimization is skipped: -```javascript -const ctx = useContext(MyContext); -const x = ctx[dynamicKey]; // Cannot optimize -``` - -### Spread in Destructuring -Spread patterns prevent optimization: -```javascript -const {foo, ...rest} = useContext(MyContext); // Cannot optimize -``` - -### Non-Identifier Values -Only simple identifier destructuring is supported: -```javascript -const {foo: bar} = useContext(MyContext); // Supported (rename) -const {foo = defaultVal} = useContext(MyContext); // Not supported -``` - -### Multiple Context Accesses -Each `useContext` call is transformed independently: -```javascript -const {a} = useContext(CtxA); // Transformed -const {b} = useContext(CtxB); // Transformed separately -``` - -### Hook Guards -When `enableEmitHookGuards` is enabled, the selector function includes proper hook guard annotations. - -## TODOs -None in the source file. - -## Example - -### Fixture: `lower-context-selector-simple.js` - -**Input:** -```javascript -// @lowerContextAccess -function App() { - const {foo, bar} = useContext(MyContext); - return ; -} -``` - -**After OptimizePropsMethodCalls (where lowering happens):** -``` -bb0 (block): - [1] $12 = LoadGlobal(global) useContext // Original (now unused) - [2] $13 = LoadGlobal(global) MyContext - [3] $22 = LoadGlobal import { useContext_withSelector } from 'react-compiler-runtime' - [4] $36 = Function @context[] - <>(#t23$30): - [1] $31 = LoadLocal #t23$30 - [2] $32 = PropertyLoad $31.foo - [3] $33 = LoadLocal #t23$30 - [4] $34 = PropertyLoad $33.bar - [5] $35 = Array [$32, $34] // Return [foo, bar] - [6] Return $35 - [5] $14 = Call $22($13, $36) // useContext_withSelector(MyContext, selector) - [6] $17 = Destructure Const { foo: foo$15, bar: bar$16 } = $14 - ... -``` - -**Generated Code:** -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useContext_withSelector } from "react-compiler-runtime"; -function App() { - const $ = _c(2); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = (ctx) => [ctx.foo, ctx.bar]; - $[0] = t0; - } else { - t0 = $[0]; - } - const { foo, bar } = useContext_withSelector(MyContext, t0); - let t1; - if ($[1] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} -``` - -Key observations: -- `useContext` is replaced with `useContext_withSelector` -- A selector function `(ctx) => [ctx.foo, ctx.bar]` is generated -- The selector function is memoized (first cache slot) -- Only `foo` and `bar` properties are extracted, enabling granular subscriptions -- The selector return type changes from object to array diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/42-validateNoCapitalizedCalls.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/42-validateNoCapitalizedCalls.md index 70038be14e21..cf4c2bbe9545 100644 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/42-validateNoCapitalizedCalls.md +++ b/compiler/packages/babel-plugin-react-compiler/docs/passes/42-validateNoCapitalizedCalls.md @@ -49,13 +49,8 @@ const ALLOW_LIST = new Set([ ...(envConfig.validateNoCapitalizedCalls ?? []), // User-configured allowlist ]); -const hookPattern = envConfig.hookPattern != null - ? new RegExp(envConfig.hookPattern) - : null; - const isAllowed = (name: string): boolean => { - return ALLOW_LIST.has(name) || - (hookPattern != null && hookPattern.test(name)); + return ALLOW_LIST.has(name); }; ``` @@ -137,13 +132,6 @@ Users can allowlist specific functions via configuration: validateNoCapitalizedCalls: ['MyUtility', 'SomeFactory'] ``` -### Hook Patterns -Functions matching the configured hook pattern are allowed even if capitalized: -```typescript -// With hookPattern: 'React\\$use.*' -const x = React$useState(); // Allowed if it matches the hook pattern -``` - ### Method Calls vs Function Calls Both direct function calls and method calls on objects are checked: ```javascript diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/52-validateMemoizedEffectDependencies.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/52-validateMemoizedEffectDependencies.md deleted file mode 100644 index 81d78a39111d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/52-validateMemoizedEffectDependencies.md +++ /dev/null @@ -1,93 +0,0 @@ -# validateMemoizedEffectDependencies - -## File -`src/Validation/ValidateMemoizedEffectDependencies.ts` - -## Purpose -Validates that all known effect dependencies (for `useEffect`, `useLayoutEffect`, and `useInsertionEffect`) are properly memoized. This prevents a common bug where unmemoized effect dependencies can cause infinite re-render loops or other unexpected behavior. - -## Input Invariants -- Operates on ReactiveFunction (post-reactive scope inference) -- Reactive scopes have been assigned to values that need memoization -- Must run after scope inference but before codegen - -## Validation Rules -This pass checks two conditions: - -1. **Unmemoized dependencies with assigned scopes**: Disallows effect dependencies that should be memoized (have a reactive scope assigned) but where that reactive scope does not exist in the output. This catches cases where a reactive scope was pruned, such as when it spans a hook call. - -2. **Mutable dependencies at effect call site**: Disallows effect dependencies whose mutable range encompasses the effect call. This catches values that the compiler knows may be mutated after the effect is set up. - -When either condition is violated, the pass produces: -``` -Compilation Skipped: React Compiler has skipped optimizing this component because -the effect dependencies could not be memoized. Unmemoized effect dependencies can -trigger an infinite loop or other unexpected behavior -``` - -## Algorithm -1. Traverse the reactive function using a visitor pattern -2. Track all scopes that exist in the AST by adding them to a `Set` during `visitScope` -3. Only record a scope if its dependencies are also memoized (transitive memoization check) -4. When visiting an instruction that is an effect hook call (`useEffect`, `useLayoutEffect`, `useInsertionEffect`) with at least 2 arguments (function + deps array): - - Check if the dependency array is mutable at the call site using `isMutable()` - - Check if the dependency array's scope exists using `isUnmemoized()` - - If either check fails, push an error - -### Key Helper Functions - -**isEffectHook(identifier)**: Returns true if the identifier is `useEffect`, `useLayoutEffect`, or `useInsertionEffect`. - -**isUnmemoized(operand, scopes)**: Returns true if the operand has a scope assigned (`operand.scope != null`) but that scope doesn't exist in the set of valid scopes. - -## Edge Cases -- Only validates effects with 2+ arguments (ignores effects without dependency arrays) -- Transitive memoization: A scope is only considered valid if all its dependencies are also memoized -- Merged scopes are tracked together with their primary scope - -## TODOs -From the source code: -```typescript -// TODO: isMutable is not safe to call here as it relies on identifier mutableRange -// which is no longer valid at this point in the pipeline -``` - -## Example - -### Fixture: `error.invalid-useEffect-dep-not-memoized.js` - -**Input:** -```javascript -// @validateMemoizedEffectDependencies -import {useEffect} from 'react'; - -function Component(props) { - const data = {}; - useEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} -``` - -**Error:** -``` -Found 1 error: - -Compilation Skipped: React Compiler has skipped optimizing this component because -the effect dependencies could not be memoized. Unmemoized effect dependencies can -trigger an infinite loop or other unexpected behavior - -error.invalid-useEffect-dep-not-memoized.ts:6:2 - 4 | function Component(props) { - 5 | const data = {}; -> 6 | useEffect(() => { - | ^^^^^^^^^^^^^^^^^ -> 7 | console.log(props.value); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 8 | }, [data]); - | ^^^^^^^^^^^^^ -``` - -**Why it fails:** The `data` object is mutated after the `useEffect` call, which extends its mutable range past the effect. This means `data` cannot be safely memoized as an effect dependency because it might change after the effect is set up. diff --git a/compiler/packages/babel-plugin-react-compiler/docs/passes/README.md b/compiler/packages/babel-plugin-react-compiler/docs/passes/README.md index bc9e17ac5238..0f6b4183b07d 100644 --- a/compiler/packages/babel-plugin-react-compiler/docs/passes/README.md +++ b/compiler/packages/babel-plugin-react-compiler/docs/passes/README.md @@ -25,7 +25,7 @@ This directory contains detailed documentation for each pass in the React Compil ┌─────────────────────────────────────────────────────────────────────────────────────┐ │ PHASE 2: OPTIMIZATION │ │ │ -│ constantPropagation ──▶ deadCodeElimination ──▶ instructionReordering │ +│ constantPropagation ──▶ deadCodeElimination │ │ │ └─────────────────────────────────────────────────────────────────────────────────────┘ │ @@ -195,8 +195,6 @@ This directory contains detailed documentation for each pass in the React Compil | # | Pass | File | Description | |---|------|------|-------------| -| 32 | [transformFire](32-transformFire.md) | `Transform/TransformFire.ts` | Transform `fire()` calls in effects | -| 33 | [lowerContextAccess](33-lowerContextAccess.md) | `Optimization/LowerContextAccess.ts` | Optimize context access with selectors | | 34 | [optimizePropsMethodCalls](34-optimizePropsMethodCalls.md) | `Optimization/OptimizePropsMethodCalls.ts` | Normalize props method calls | | 35 | [optimizeForSSR](35-optimizeForSSR.md) | `Optimization/OptimizeForSSR.ts` | SSR-specific optimizations | | 36 | [outlineJSX](36-outlineJSX.md) | `Optimization/OutlineJsx.ts` | Outline JSX to components | @@ -220,7 +218,6 @@ This directory contains detailed documentation for each pass in the React Compil | 49 | [validateNoRefAccessInRender](49-validateNoRefAccessInRender.md) | `Validation/ValidateNoRefAccessInRender.ts` | Ref access constraints | | 50 | [validateNoFreezingKnownMutableFunctions](50-validateNoFreezingKnownMutableFunctions.md) | `Validation/ValidateNoFreezingKnownMutableFunctions.ts` | Mutable function isolation | | 51 | [validateExhaustiveDependencies](51-validateExhaustiveDependencies.md) | `Validation/ValidateExhaustiveDependencies.ts` | Dependency array completeness | -| 52 | [validateMemoizedEffectDependencies](52-validateMemoizedEffectDependencies.md) | `Validation/ValidateMemoizedEffectDependencies.ts` | Effect scope memoization | | 53 | [validatePreservedManualMemoization](53-validatePreservedManualMemoization.md) | `Validation/ValidatePreservedManualMemoization.ts` | Manual memo preservation | | 54 | [validateStaticComponents](54-validateStaticComponents.md) | `Validation/ValidateStaticComponents.ts` | Component identity stability | | 55 | [validateSourceLocations](55-validateSourceLocations.md) | `Validation/ValidateSourceLocations.ts` | Source location preservation | @@ -275,8 +272,6 @@ Many passes are controlled by feature flags in `Environment.ts`: | Flag | Enables Pass | |------|--------------| -| `enableFire` | transformFire | -| `lowerContextAccess` | lowerContextAccess | | `enableJsxOutlining` | outlineJSX | | `enableFunctionOutlining` | outlineFunctions | | `validateNoSetStateInRender` | validateNoSetStateInRender | diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index 1c9281eb0720..42822384d207 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -565,15 +565,12 @@ function printCodeFrame( function printErrorSummary(category: ErrorCategory, message: string): string { let heading: string; switch (category) { - case ErrorCategory.AutomaticEffectDependencies: case ErrorCategory.CapitalizedCalls: case ErrorCategory.Config: case ErrorCategory.EffectDerivationsOfState: case ErrorCategory.EffectSetState: case ErrorCategory.ErrorBoundaries: - case ErrorCategory.Factories: case ErrorCategory.FBT: - case ErrorCategory.Fire: case ErrorCategory.Gating: case ErrorCategory.Globals: case ErrorCategory.Hooks: @@ -637,10 +634,6 @@ export enum ErrorCategory { * Checking that useMemos always return a value */ VoidUseMemo = 'VoidUseMemo', - /** - * Checking for higher order functions acting as factories for components/hooks - */ - Factories = 'Factories', /** * Checks that manual memoization is preserved */ @@ -718,14 +711,6 @@ export enum ErrorCategory { * Suppressions */ Suppression = 'Suppression', - /** - * Issues with auto deps - */ - AutomaticEffectDependencies = 'AutomaticEffectDependencies', - /** - * Issues with `fire` - */ - Fire = 'Fire', /** * fbt-specific issues */ @@ -790,16 +775,6 @@ export function getRuleForCategory(category: ErrorCategory): LintRule { function getRuleForCategoryImpl(category: ErrorCategory): LintRule { switch (category) { - case ErrorCategory.AutomaticEffectDependencies: { - return { - category, - severity: ErrorSeverity.Error, - name: 'automatic-effect-dependencies', - description: - 'Verifies that automatic effect dependencies are compiled if opted-in', - preset: LintRulePreset.Off, - }; - } case ErrorCategory.CapitalizedCalls: { return { category, @@ -870,17 +845,6 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule { preset: LintRulePreset.Recommended, }; } - case ErrorCategory.Factories: { - return { - category, - severity: ErrorSeverity.Error, - name: 'component-hook-factories', - description: - 'Validates against higher order functions defining nested components or hooks. ' + - 'Components and hooks should be defined at the module level', - preset: LintRulePreset.Recommended, - }; - } case ErrorCategory.FBT: { return { category, @@ -890,15 +854,6 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule { preset: LintRulePreset.Off, }; } - case ErrorCategory.Fire: { - return { - category, - severity: ErrorSeverity.Error, - name: 'fire', - description: 'Validates usage of `fire`', - preset: LintRulePreset.Off, - }; - } case ErrorCategory.Gating: { return { category, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index df78607fc196..2fef4cfabe59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -88,7 +88,6 @@ export class ProgramContext { * Metadata from compilation */ retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - inferredEffectLocations: Set = new Set(); constructor({ program, @@ -108,14 +107,7 @@ export class ProgramContext { } isHookName(name: string): boolean { - if (this.opts.environment.hookPattern == null) { - return isHookName(name); - } else { - const match = new RegExp(this.opts.environment.hookPattern).exec(name); - return ( - match != null && typeof match[1] === 'string' && isHookName(match[1]) - ); - } + return isHookName(name); } hasReference(name: string): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index b758d7b024d0..2e5c9313a935 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -255,9 +255,7 @@ export type LoggerEvent = | CompileDiagnosticEvent | CompileSkipEvent | PipelineErrorEvent - | TimingEvent - | AutoDepsDecorationsEvent - | AutoDepsEligibleEvent; + | TimingEvent; export type CompileErrorEvent = { kind: 'CompileError'; @@ -294,17 +292,6 @@ export type TimingEvent = { kind: 'Timing'; measurement: PerformanceMeasure; }; -export type AutoDepsDecorationsEvent = { - kind: 'AutoDepsDecorations'; - fnLoc: t.SourceLocation; - decorations: Array; -}; -export type AutoDepsEligibleEvent = { - kind: 'AutoDepsEligible'; - fnLoc: t.SourceLocation; - depArrayLoc: t.SourceLocation; -}; - export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; debugLogIRs?: (value: CompilerPipelineValue) => void; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 30d665227159..90651818c777 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -34,15 +34,12 @@ import { dropManualMemoization, inferReactivePlaces, inlineImmediatelyInvokedFunctionExpressions, - inferEffectDependencies, } from '../Inference'; import { constantPropagation, deadCodeElimination, pruneMaybeThrows, - inlineJsxTransform, } from '../Optimization'; -import {instructionReordering} from '../Optimization/InstructionReordering'; import { CodegenFunction, alignObjectMethodScopes, @@ -69,7 +66,6 @@ import {alignReactiveScopesToBlockScopesHIR} from '../ReactiveScopes/AlignReacti import {flattenReactiveLoopsHIR} from '../ReactiveScopes/FlattenReactiveLoopsHIR'; import {flattenScopesWithHooksOrUseHIR} from '../ReactiveScopes/FlattenScopesWithHooksOrUseHIR'; import {pruneAlwaysInvalidatingScopes} from '../ReactiveScopes/PruneAlwaysInvalidatingScopes'; -import pruneInitializationDependencies from '../ReactiveScopes/PruneInitializationDependencies'; import {stabilizeBlockIds} from '../ReactiveScopes/StabilizeBlockIds'; import { eliminateRedundantPhi, @@ -80,7 +76,6 @@ import {inferTypes} from '../TypeInference'; import { validateContextVariableLValues, validateHooksUsage, - validateMemoizedEffectDependencies, validateNoCapitalizedCalls, validateNoRefAccessInRender, validateNoSetStateInRender, @@ -89,13 +84,11 @@ import { } from '../Validation'; import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender'; import {outlineFunctions} from '../Optimization/OutlineFunctions'; -import {lowerContextAccess} from '../Optimization/LowerContextAccess'; import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects'; import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement'; import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR'; import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; -import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; @@ -169,12 +162,7 @@ function runWithEnvironment( validateContextVariableLValues(hir); validateUseMemo(hir).unwrap(); - if ( - env.enableDropManualMemoization && - !env.config.enablePreserveExistingManualUseMemo && - !env.config.disableMemoizationForDebugging && - !env.config.enableChangeDetectionForDebugging - ) { + if (env.enableDropManualMemoization) { dropManualMemoization(hir).unwrap(); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -215,15 +203,6 @@ function runWithEnvironment( } } - if (env.config.enableFire) { - transformFire(hir); - log({kind: 'hir', name: 'TransformFire', value: hir}); - } - - if (env.config.lowerContextAccess) { - lowerContextAccess(hir, env.config.lowerContextAccess); - } - optimizePropsMethodCalls(hir); log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); @@ -246,12 +225,6 @@ function runWithEnvironment( // Note: Has to come after infer reference effects because "dead" code may still affect inference deadCodeElimination(hir); log({kind: 'hir', name: 'DeadCodeElimination', value: hir}); - - if (env.config.enableInstructionReordering) { - instructionReordering(hir); - log({kind: 'hir', name: 'InstructionReordering', value: hir}); - } - pruneMaybeThrows(hir); log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); @@ -433,24 +406,6 @@ function runWithEnvironment( value: hir, }); - if (env.config.inferEffectDependencies) { - inferEffectDependencies(hir); - log({ - kind: 'hir', - name: 'InferEffectDependencies', - value: hir, - }); - } - - if (env.config.inlineJsxTransform) { - inlineJsxTransform(hir, env.config.inlineJsxTransform); - log({ - kind: 'hir', - name: 'inlineJsxTransform', - value: hir, - }); - } - const reactiveFunction = buildReactiveFunction(hir); log({ kind: 'reactive', @@ -503,15 +458,6 @@ function runWithEnvironment( value: reactiveFunction, }); - if (env.config.enableChangeDetectionForDebugging != null) { - pruneInitializationDependencies(reactiveFunction); - log({ - kind: 'reactive', - name: 'PruneInitializationDependencies', - value: reactiveFunction, - }); - } - propagateEarlyReturns(reactiveFunction); log({ kind: 'reactive', @@ -561,10 +507,6 @@ function runWithEnvironment( value: reactiveFunction, }); - if (env.config.validateMemoizedEffectDependencies) { - validateMemoizedEffectDependencies(reactiveFunction).unwrap(); - } - if ( env.config.enablePreserveExistingMemoizationGuarantees || env.config.validatePreserveExistingMemoizationGuarantees diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 80ce909f35aa..de36ad218f7e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -352,7 +352,6 @@ function isFilePartOfSources( export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; - inferredEffectLocations: Set; }; /** * Main entrypoint for React Compiler. @@ -487,7 +486,6 @@ export function compileProgram( return { retryErrors: programContext.retryErrors, - inferredEffectLocations: programContext.inferredEffectLocations, }; } @@ -518,10 +516,6 @@ function findFunctionsToCompile( const fnType = getReactFunctionType(fn, pass); - if (pass.opts.environment.validateNoDynamicallyCreatedComponentsOrHooks) { - validateNoDynamicallyCreatedComponentsOrHooks(fn, pass, programContext); - } - if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -633,15 +627,7 @@ function processFn( } else { handleError(compileResult.error, programContext, fn.node.loc ?? null); } - if (outputMode === 'client') { - const retryResult = retryCompileFunction(fn, fnType, programContext); - if (retryResult == null) { - return null; - } - compiledFn = retryResult; - } else { - return null; - } + return null; } else { compiledFn = compileResult.compiledFn; } @@ -678,16 +664,6 @@ function processFn( if (programContext.hasModuleScopeOptOut) { return null; } else if (programContext.opts.outputMode === 'lint') { - /** - * inferEffectDependencies + noEmit is currently only used for linting. In - * this mode, add source locations for where the compiler *can* infer effect - * dependencies. - */ - for (const loc of compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) { - programContext.inferredEffectLocations.add(loc); - } - } return null; } else if ( programContext.opts.compilationMode === 'annotation' && @@ -746,52 +722,6 @@ function tryCompileFunction( } } -/** - * If non-memo feature flags are enabled, retry compilation with a more minimal - * feature set. - * - * @returns a CodegenFunction if retry was successful - */ -function retryCompileFunction( - fn: BabelFn, - fnType: ReactFunctionType, - programContext: ProgramContext, -): CodegenFunction | null { - const environment = programContext.opts.environment; - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - /** - * Note that function suppressions are not checked in the retry pipeline, as - * they only affect auto-memoization features. - */ - try { - const retryResult = compileFn( - fn, - environment, - fnType, - 'client-no-memo', - programContext, - programContext.opts.logger, - programContext.filename, - programContext.code, - ); - - if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { - return null; - } - return retryResult; - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - programContext.retryErrors.push({fn, error: err}); - } - return null; - } -} - /** * Applies React Compiler generated functions to the babel AST by replacing * existing functions in place or inserting new declarations. @@ -876,84 +806,17 @@ function shouldSkipCompilation( return false; } -/** - * Validates that Components/Hooks are always defined at module level. This prevents scope reference - * errors that occur when the compiler attempts to optimize the nested component/hook while its - * parent function remains uncompiled. - */ -function validateNoDynamicallyCreatedComponentsOrHooks( - fn: BabelFn, - pass: CompilerPass, - programContext: ProgramContext, -): void { - const parentNameExpr = getFunctionName(fn); - const parentName = - parentNameExpr !== null && parentNameExpr.isIdentifier() - ? parentNameExpr.node.name - : ''; - - const validateNestedFunction = ( - nestedFn: NodePath< - t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression - >, - ): void => { - if ( - nestedFn.node === fn.node || - programContext.alreadyCompiled.has(nestedFn.node) - ) { - return; - } - - if (nestedFn.scope.getProgramParent() !== nestedFn.scope.parent) { - const nestedFnType = getReactFunctionType(nestedFn as BabelFn, pass); - const nestedFnNameExpr = getFunctionName(nestedFn as BabelFn); - const nestedName = - nestedFnNameExpr !== null && nestedFnNameExpr.isIdentifier() - ? nestedFnNameExpr.node.name - : ''; - if (nestedFnType === 'Component' || nestedFnType === 'Hook') { - CompilerError.throwDiagnostic({ - category: ErrorCategory.Factories, - reason: `Components and hooks cannot be created dynamically`, - description: `The function \`${nestedName}\` appears to be a React ${nestedFnType.toLowerCase()}, but it's defined inside \`${parentName}\`. Components and Hooks should always be declared at module scope`, - details: [ - { - kind: 'error', - message: 'this function dynamically created a component/hook', - loc: parentNameExpr?.node.loc ?? fn.node.loc ?? null, - }, - { - kind: 'error', - message: 'the component is created here', - loc: nestedFnNameExpr?.node.loc ?? nestedFn.node.loc ?? null, - }, - ], - }); - } - } - - nestedFn.skip(); - }; - - fn.traverse({ - FunctionDeclaration: validateNestedFunction, - FunctionExpression: validateNestedFunction, - ArrowFunctionExpression: validateNestedFunction, - }); -} - function getReactFunctionType( fn: BabelFn, pass: CompilerPass, ): ReactFunctionType | null { - const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { const optInDirectives = tryFindDirectiveEnablingMemoization( fn.node.body.directives, pass.opts, ); if (optInDirectives.unwrapOr(null) != null) { - return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + return getComponentOrHookLike(fn) ?? 'Other'; } } @@ -974,13 +837,13 @@ function getReactFunctionType( } case 'infer': { // Check if this is a component or hook-like function - return componentSyntaxType ?? getComponentOrHookLike(fn, hookPattern); + return componentSyntaxType ?? getComponentOrHookLike(fn); } case 'syntax': { return componentSyntaxType; } case 'all': { - return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; + return getComponentOrHookLike(fn) ?? 'Other'; } default: { assertExhaustive( @@ -1022,10 +885,7 @@ function hasMemoCacheFunctionImport( return hasUseMemoCache; } -function isHookName(s: string, hookPattern: string | null): boolean { - if (hookPattern !== null) { - return new RegExp(hookPattern).test(s); - } +function isHookName(s: string): boolean { return /^use[A-Z0-9]/.test(s); } @@ -1034,16 +894,13 @@ function isHookName(s: string, hookPattern: string | null): boolean { * containing a hook name. */ -function isHook( - path: NodePath, - hookPattern: string | null, -): boolean { +function isHook(path: NodePath): boolean { if (path.isIdentifier()) { - return isHookName(path.node.name, hookPattern); + return isHookName(path.node.name); } else if ( path.isMemberExpression() && !path.node.computed && - isHook(path.get('property'), hookPattern) + isHook(path.get('property')) ) { const obj = path.get('object').node; const isPascalCaseNameSpace = /^[A-Z].*/; @@ -1184,19 +1041,18 @@ function getComponentOrHookLike( node: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, - hookPattern: string | null, ): ReactFunctionType | null { const functionName = getFunctionName(node); // Check if the name is component or hook like: if (functionName !== null && isComponentName(functionName)) { let isComponent = - callsHooksOrCreatesJsx(node, hookPattern) && + callsHooksOrCreatesJsx(node) && isValidComponentParams(node.get('params')) && !returnsNonNode(node); return isComponent ? 'Component' : null; - } else if (functionName !== null && isHook(functionName, hookPattern)) { + } else if (functionName !== null && isHook(functionName)) { // Hooks have hook invocations or JSX, but can take any # of arguments - return callsHooksOrCreatesJsx(node, hookPattern) ? 'Hook' : null; + return callsHooksOrCreatesJsx(node) ? 'Hook' : null; } /* @@ -1206,7 +1062,7 @@ function getComponentOrHookLike( if (node.isFunctionExpression() || node.isArrowFunctionExpression()) { if (isForwardRefCallback(node) || isMemoCallback(node)) { // As an added check we also look for hook invocations or JSX - return callsHooksOrCreatesJsx(node, hookPattern) ? 'Component' : null; + return callsHooksOrCreatesJsx(node) ? 'Component' : null; } } return null; @@ -1232,7 +1088,6 @@ function callsHooksOrCreatesJsx( node: NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression >, - hookPattern: string | null, ): boolean { let invokesHooks = false; let createsJsx = false; @@ -1243,7 +1098,7 @@ function callsHooksOrCreatesJsx( }, CallExpression(call) { const callee = call.get('callee'); - if (callee.isExpression() && isHook(callee, hookPattern)) { + if (callee.isExpression() && isHook(callee)) { invokesHooks = true; } }, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index fab0865b54d3..f612e1db070d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -10,137 +10,9 @@ import * as t from '@babel/types'; import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; -import {Environment, GeneratedSource} from '../HIR'; +import {GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import { - CompilerDiagnostic, - CompilerDiagnosticOptions, - ErrorCategory, -} from '../CompilerError'; - -function throwInvalidReact( - options: CompilerDiagnosticOptions, - {logger, filename}: TraversalState, -): never { - logger?.logEvent(filename, { - kind: 'CompileError', - fnLoc: null, - detail: new CompilerDiagnostic(options), - }); - CompilerError.throwDiagnostic(options); -} - -function isAutodepsSigil( - arg: NodePath, -): boolean { - // Check for AUTODEPS identifier imported from React - if (arg.isIdentifier() && arg.node.name === 'AUTODEPS') { - const binding = arg.scope.getBinding(arg.node.name); - if (binding && binding.path.isImportSpecifier()) { - const importSpecifier = binding.path.node as t.ImportSpecifier; - if (importSpecifier.imported.type === 'Identifier') { - return (importSpecifier.imported as t.Identifier).name === 'AUTODEPS'; - } - } - return false; - } - - // Check for React.AUTODEPS member expression - if (arg.isMemberExpression() && !arg.node.computed) { - const object = arg.get('object'); - const property = arg.get('property'); - - if ( - object.isIdentifier() && - object.node.name === 'React' && - property.isIdentifier() && - property.node.name === 'AUTODEPS' - ) { - return true; - } - } - - return false; -} -function assertValidEffectImportReference( - autodepsIndex: number, - paths: Array>, - context: TraversalState, -): void { - for (const path of paths) { - const parent = path.parentPath; - if (parent != null && parent.isCallExpression()) { - const args = parent.get('arguments'); - const maybeCalleeLoc = path.node.loc; - const hasInferredEffect = - maybeCalleeLoc != null && - context.inferredEffectLocations.has(maybeCalleeLoc); - /** - * Error on effect calls that still have AUTODEPS in their args - */ - const hasAutodepsArg = args.some(isAutodepsSigil); - if (hasAutodepsArg && !hasInferredEffect) { - const maybeErrorDiagnostic = matchCompilerDiagnostic( - path, - context.transformErrors, - ); - /** - * Note that we cannot easily check the type of the first argument here, - * as it may have already been transformed by the compiler (and not - * memoized). - */ - throwInvalidReact( - { - category: ErrorCategory.AutomaticEffectDependencies, - reason: - 'Cannot infer dependencies of this effect. This will break your build!', - description: - 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics' + - (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), - details: [ - { - kind: 'error', - message: 'Cannot infer dependencies', - loc: parent.node.loc ?? GeneratedSource, - }, - ], - }, - context, - ); - } - } - } -} - -function assertValidFireImportReference( - paths: Array>, - context: TraversalState, -): void { - if (paths.length > 0) { - const maybeErrorDiagnostic = matchCompilerDiagnostic( - paths[0], - context.transformErrors, - ); - throwInvalidReact( - { - category: ErrorCategory.Fire, - reason: '[Fire] Untransformed reference to compiler-required feature.', - description: - 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + - (maybeErrorDiagnostic != null ? ` ${maybeErrorDiagnostic}` : ''), - details: [ - { - kind: 'error', - message: 'Untransformed `fire` call', - loc: paths[0].node.loc ?? GeneratedSource, - }, - ], - }, - context, - ); - } -} export default function validateNoUntransformedReferences( path: NodePath, filename: string | null, @@ -152,28 +24,6 @@ export default function validateNoUntransformedReferences( string, Map >(); - if (env.enableFire) { - /** - * Error on any untransformed references to `fire` (e.g. including non-call - * expressions) - */ - for (const module of Environment.knownReactModules) { - const react = getOrInsertWith(moduleLoadChecks, module, () => new Map()); - react.set('fire', assertValidFireImportReference); - } - } - if (env.inferEffectDependencies) { - for (const { - function: {source, importSpecifierName}, - autodepsIndex, - } of env.inferEffectDependencies) { - const module = getOrInsertWith(moduleLoadChecks, source, () => new Map()); - module.set( - importSpecifierName, - assertValidEffectImportReference.bind(null, autodepsIndex), - ); - } - } if (moduleLoadChecks.size > 0) { transformProgram(path, moduleLoadChecks, filename, logger, compileResult); } @@ -185,7 +35,6 @@ type TraversalState = { logger: Logger | null; filename: string | null; transformErrors: Array<{fn: NodePath; error: CompilerError}>; - inferredEffectLocations: Set; }; type CheckInvalidReferenceFn = ( paths: Array>, @@ -281,8 +130,6 @@ function transformProgram( filename, logger, transformErrors: compileResult?.retryErrors ?? [], - inferredEffectLocations: - compileResult?.inferredEffectLocations ?? new Set(), }; path.traverse({ ImportDeclaration(path: NodePath) { @@ -313,15 +160,3 @@ function transformProgram( }, }); } - -function matchCompilerDiagnostic( - badReference: NodePath, - transformErrors: Array<{fn: NodePath; error: CompilerError}>, -): string | null { - for (const {fn, error} of transformErrors) { - if (fn.isAncestor(badReference)) { - return error.toString(); - } - } - return null; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index 3e1c2b5e58c5..c47a41145157 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -124,9 +124,7 @@ export function collectHoistablePropertyLoads( hoistableFromOptionals, registry, nestedFnImmutableContext: null, - assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional - ? new Set() - : getAssumedInvokedFunctions(fn), + assumedInvokedFns: getAssumedInvokedFunctions(fn), }); } @@ -142,9 +140,7 @@ export function collectHoistablePropertyLoadsInInnerFn( hoistableFromOptionals, registry: new PropertyPathRegistry(), nestedFnImmutableContext: null, - assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional - ? new Set() - : getAssumedInvokedFunctions(fn), + assumedInvokedFns: getAssumedInvokedFunctions(fn), }; const nestedFnImmutableContext = new Set( fn.context diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 7ceb5bf005ce..80caee2caf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -54,14 +54,6 @@ import {FlowTypeEnv} from '../Flood/Types'; import {defaultModuleTypeProvider} from './DefaultModuleTypeProvider'; import {assertExhaustive} from '../Utils/utils'; -export const ReactElementSymbolSchema = z.object({ - elementSymbol: z.union([ - z.literal('react.element'), - z.literal('react.transitional.element'), - ]), - globalDevVar: z.string(), -}); - export const ExternalFunctionSchema = z.object({ // Source for the imported module that exports the `importSpecifierName` functions source: z.string(), @@ -82,8 +74,6 @@ export const InstrumentationSchema = z ); export type ExternalFunction = z.infer; -export const USE_FIRE_FUNCTION_NAME = 'useFire'; -export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__'; export const MacroSchema = z.string(); @@ -236,24 +226,9 @@ export const EnvironmentConfigSchema = z.object({ .enum(['off', 'all', 'missing-only', 'extra-only']) .default('off'), - /** - * When this is true, rather than pruning existing manual memoization but ensuring or validating - * that the memoized values remain memoized, the compiler will simply not prune existing calls to - * useMemo/useCallback. - */ - enablePreserveExistingManualUseMemo: z.boolean().default(false), - // 🌲 enableForest: z.boolean().default(false), - /** - * Enable use of type annotations in the source to drive type inference. By default - * Forget attemps to infer types using only information that is guaranteed correct - * given the source, and does not trust user-supplied type annotations. This mode - * enables trusting user type annotations. - */ - enableUseTypeAnnotations: z.boolean().default(false), - /** * Allows specifying a function that can populate HIR with type information from * Flow @@ -268,53 +243,8 @@ export const EnvironmentConfigSchema = z.object({ */ enableOptionalDependencies: z.boolean().default(true), - enableFire: z.boolean().default(false), - enableNameAnonymousFunctions: z.boolean().default(false), - /** - * Enables inference and auto-insertion of effect dependencies. Takes in an array of - * configurable module and import pairs to allow for user-land experimentation. For example, - * [ - * { - * module: 'react', - * imported: 'useEffect', - * autodepsIndex: 1, - * },{ - * module: 'MyExperimentalEffectHooks', - * imported: 'useExperimentalEffect', - * autodepsIndex: 2, - * }, - * ] - * would insert dependencies for calls of `useEffect` imported from `react` and calls of - * useExperimentalEffect` from `MyExperimentalEffectHooks`. - * - * `autodepsIndex` tells the compiler which index we expect the AUTODEPS to appear in. - * With the configuration above, we'd insert dependencies for `useEffect` if it has two - * arguments, and the second is AUTODEPS. - * - * Still experimental. - */ - inferEffectDependencies: z - .nullable( - z.array( - z.object({ - function: ExternalFunctionSchema, - autodepsIndex: z.number().min(1, 'autodepsIndex must be > 0'), - }), - ), - ) - .default(null), - - /** - * Enables inlining ReactElement object literals in place of JSX - * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime - * Currently a prod-only optimization, requiring Fast JSX dependencies - * - * The symbol configuration is set for backwards compatability with pre-React 19 transforms - */ - inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null), - /* * Enable validation of hooks to partially check that the component honors the rules of hooks. * When disabled, the component is assumed to follow the rules (though the Babel plugin looks @@ -366,16 +296,6 @@ export const EnvironmentConfigSchema = z.object({ */ validateStaticComponents: z.boolean().default(false), - /** - * Validates that the dependencies of all effect hooks are memoized. This helps ensure - * that Forget does not introduce infinite renders caused by a dependency changing, - * triggering an effect, which triggers re-rendering, which causes a dependency to change, - * triggering the effect, etc. - * - * Covers useEffect, useLayoutEffect, useInsertionEffect. - */ - validateMemoizedEffectDependencies: z.boolean().default(false), - /** * Validates that there are no capitalized calls other than those allowed by the allowlist. * Calls to capitalized functions are often functions that used to be components and may @@ -422,38 +342,8 @@ export const EnvironmentConfigSchema = z.object({ * then this flag will assume that `x` is not subusequently modified. */ enableTransitivelyFreezeFunctionExpressions: z.boolean().default(true), - - /* - * Enables codegen mutability debugging. This emits a dev-mode only to log mutations - * to values that Forget assumes are immutable (for Forget compiled code). - * For example: - * emitFreeze: { - * source: 'ReactForgetRuntime', - * importSpecifierName: 'makeReadOnly', - * } - * - * produces: - * import {makeReadOnly} from 'ReactForgetRuntime'; - * - * function Component(props) { - * if (c_0) { - * // ... - * $[0] = __DEV__ ? makeReadOnly(x) : x; - * } else { - * x = $[0]; - * } - * } - */ - enableEmitFreeze: ExternalFunctionSchema.nullable().default(null), - enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null), - /** - * Enable instruction reordering. See InstructionReordering.ts for the details - * of the approach. - */ - enableInstructionReordering: z.boolean().default(false), - /** * Enables function outlinining, where anonymous functions that do not close over * local variables can be extracted into top-level helper functions. @@ -535,80 +425,12 @@ export const EnvironmentConfigSchema = z.object({ // Enable validation of mutable ranges assertValidMutableRanges: z.boolean().default(false), - /* - * Enable emitting "change variables" which store the result of whether a particular - * reactive scope dependency has changed since the scope was last executed. - * - * Ex: - * ``` - * const c_0 = $[0] !== input; // change variable - * let output; - * if (c_0) ... - * ``` - * - * Defaults to false, where the comparison is inlined: - * - * ``` - * let output; - * if ($[0] !== input) ... - * ``` - */ - enableChangeVariableCodegen: z.boolean().default(false), - - /** - * Enable emitting comments that explain Forget's output, and which - * values are being checked and which values produced by each memo block. - * - * Intended for use in demo purposes (incl playground) - */ - enableMemoizationComments: z.boolean().default(false), - /** * [TESTING ONLY] Throw an unknown exception during compilation to * simulate unexpected exceptions e.g. errors from babel functions. */ throwUnknownException__testonly: z.boolean().default(false), - /** - * Enables deps of a function epxression to be treated as conditional. This - * makes sure we don't load a dep when it's a property (to check if it has - * changed) and instead check the receiver. - * - * This makes sure we don't end up throwing when the reciver is null. Consider - * this code: - * - * ``` - * function getLength() { - * return props.bar.length; - * } - * ``` - * - * It's only safe to memoize `getLength` against props, not props.bar, as - * props.bar could be null when this `getLength` function is created. - * - * This does cause the memoization to now be coarse grained, which is - * non-ideal. - */ - enableTreatFunctionDepsAsConditional: z.boolean().default(false), - - /** - * When true, always act as though the dependencies of a memoized value - * have changed. This makes the compiler not actually perform any optimizations, - * but is useful for debugging. Implicitly also sets - * @enablePreserveExistingManualUseMemo, because otherwise memoization in the - * original source will be disabled as well. - */ - disableMemoizationForDebugging: z.boolean().default(false), - - /** - * When true, rather using memoized values, the compiler will always re-compute - * values, and then use a heuristic to compare the memoized value to the newly - * computed one. This detects cases where rules of react violations may cause the - * compiled code to behave differently than the original. - */ - enableChangeDetectionForDebugging: - ExternalFunctionSchema.nullable().default(null), - /** * The react native re-animated library uses custom Babel transforms that * requires the calls to library API remain unmodified. @@ -619,19 +441,6 @@ export const EnvironmentConfigSchema = z.object({ */ enableCustomTypeDefinitionForReanimated: z.boolean().default(false), - /** - * If specified, this value is used as a pattern for determing which global values should be - * treated as hooks. The pattern should have a single capture group, which will be used as - * the hook name for the purposes of resolving hook definitions (for builtin hooks)_. - * - * For example, by default `React$useState` would not be treated as a hook. By specifying - * `hookPattern: 'React$(\w+)'`, the compiler will treat this value equivalently to `useState()`. - * - * This setting is intended for cases where Forget is compiling code that has been prebundled - * and identifiers have been changed. - */ - hookPattern: z.string().nullable().default(null), - /** * If enabled, this will treat objects named as `ref` or if their names end with the substring `Ref`, * and contain a property named `current`, as React refs. @@ -656,28 +465,6 @@ export const EnvironmentConfigSchema = z.object({ */ enableTreatSetIdentifiersAsStateSetters: z.boolean().default(false), - /* - * If specified a value, the compiler lowers any calls to `useContext` to use - * this value as the callee. - * - * A selector function is compiled and passed as an argument along with the - * context to this function call. - * - * The compiler automatically figures out the keys by looking for the immediate - * destructuring of the return value from the useContext call. In the future, - * this can be extended to different kinds of context access like property - * loads and accesses over multiple statements as well. - * - * ``` - * // input - * const {foo, bar} = useContext(MyContext); - * - * // output - * const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]); - * ``` - */ - lowerContextAccess: ExternalFunctionSchema.nullable().default(null), - /** * If enabled, will validate useMemos that don't return any values: * @@ -689,13 +476,6 @@ export const EnvironmentConfigSchema = z.object({ */ validateNoVoidUseMemo: z.boolean().default(true), - /** - * Validates that Components/Hooks are always defined at module level. This prevents scope - * reference errors that occur when the compiler attempts to optimize the nested component/hook - * while its parent function remains uncompiled. - */ - validateNoDynamicallyCreatedComponentsOrHooks: z.boolean().default(false), - /** * When enabled, allows setState calls in effects based on valid patterns involving refs: * - Allow setState where the value being set is derived from a ref. This is useful where @@ -717,15 +497,6 @@ export const EnvironmentConfigSchema = z.object({ * 3. Force update / external sync - should use useSyncExternalStore */ enableVerboseNoSetStateInEffect: z.boolean().default(false), - - /** - * Enables inference of event handler types for JSX props on built-in DOM elements. - * When enabled, functions passed to event handler props (props starting with "on") - * on primitive JSX tags are inferred to have the BuiltinEventHandlerId type, which - * allows ref access within those functions since DOM event handlers are guaranteed - * by React to only execute in response to events, not during render. - */ - enableInferEventHandlers: z.boolean().default(false), }); export type EnvironmentConfig = z.infer; @@ -767,9 +538,6 @@ export class Environment { fnType: ReactFunctionType; outputMode: CompilerOutputMode; programContext: ProgramContext; - hasFireRewrite: boolean; - hasInferredEffect: boolean; - inferredEffectLocations: Set = new Set(); #contextIdentifiers: Set; #hoistedIdentifiers: Set; @@ -799,20 +567,6 @@ export class Environment { this.programContext = programContext; this.#shapes = new Map(DEFAULT_SHAPES); this.#globals = new Map(DEFAULT_GLOBALS); - this.hasFireRewrite = false; - this.hasInferredEffect = false; - - if ( - config.disableMemoizationForDebugging && - config.enableChangeDetectionForDebugging != null - ) { - CompilerError.throwInvalidConfig({ - reason: `Invalid environment config: the 'disableMemoizationForDebugging' and 'enableChangeDetectionForDebugging' options cannot be used together`, - description: null, - loc: null, - suggestions: null, - }); - } for (const [hookName, hook] of this.config.customHooks) { CompilerError.invariant(!this.#globals.has(hookName), { @@ -1029,18 +783,6 @@ export class Environment { binding: NonLocalBinding, loc: SourceLocation, ): Global | null { - if (this.config.hookPattern != null) { - const match = new RegExp(this.config.hookPattern).exec(binding.name); - if ( - match != null && - typeof match[1] === 'string' && - isHookName(match[1]) - ) { - const resolvedName = match[1]; - return this.#globals.get(resolvedName) ?? this.#getCustomHookType(); - } - } - switch (binding.kind) { case 'ModuleLocal': { // don't resolve module locals diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index 441b5d5452a7..faf7c9f2b72b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -9,9 +9,6 @@ import {Effect, ValueKind, ValueReason} from './HIR'; import { BUILTIN_SHAPES, BuiltInArrayId, - BuiltInAutodepsId, - BuiltInFireFunctionId, - BuiltInFireId, BuiltInMapId, BuiltInMixedReadonlyId, BuiltInObjectId, @@ -846,26 +843,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ BuiltInUseOperatorId, ), ], - [ - 'fire', - addFunction( - DEFAULT_SHAPES, - [], - { - positionalParams: [], - restParam: null, - returnType: { - kind: 'Function', - return: {kind: 'Poly'}, - shapeId: BuiltInFireFunctionId, - isConstructor: false, - }, - calleeEffect: Effect.Read, - returnValueKind: ValueKind.Frozen, - }, - BuiltInFireId, - ), - ], [ 'useEffectEvent', addHook( @@ -887,7 +864,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ BuiltInUseEffectEventId, ), ], - ['AUTODEPS', addObject(DEFAULT_SHAPES, BuiltInAutodepsId, [])], ]; TYPED_GLOBALS.push( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index fa78e3d3001c..bf2af5f6834c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1888,12 +1888,6 @@ export function isDispatcherType(id: Identifier): boolean { return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInDispatch'; } -export function isFireFunctionType(id: Identifier): boolean { - return ( - id.type.kind === 'Function' && id.type.shapeId === 'BuiltInFireFunction' - ); -} - export function isEffectEventFunctionType(id: Identifier): boolean { return ( id.type.kind === 'Function' && diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts index d104d799d726..849d73cf3642 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -383,12 +383,8 @@ export const BuiltInUseTransitionId = 'BuiltInUseTransition'; export const BuiltInUseOptimisticId = 'BuiltInUseOptimistic'; export const BuiltInSetOptimisticId = 'BuiltInSetOptimistic'; export const BuiltInStartTransitionId = 'BuiltInStartTransition'; -export const BuiltInFireId = 'BuiltInFire'; -export const BuiltInFireFunctionId = 'BuiltInFireFunction'; export const BuiltInUseEffectEventId = 'BuiltInUseEffectEvent'; export const BuiltInEffectEventId = 'BuiltInEffectEventFunction'; -export const BuiltInAutodepsId = 'BuiltInAutoDepsId'; -export const BuiltInEventHandlerId = 'BuiltInEventHandlerId'; // See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types export const ReanimatedSharedValueId = 'ReanimatedSharedValueId'; @@ -1249,19 +1245,6 @@ addFunction( BuiltInEffectEventId, ); -addFunction( - BUILTIN_SHAPES, - [], - { - positionalParams: [], - restParam: Effect.ConditionallyMutate, - returnType: {kind: 'Poly'}, - calleeEffect: Effect.ConditionallyMutate, - returnValueKind: ValueKind.Mutable, - }, - BuiltInEventHandlerId, -); - /** * MixedReadOnly = * | primitive diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts deleted file mode 100644 index 2997a449dead..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ /dev/null @@ -1,675 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import * as t from '@babel/types'; -import {CompilerError, SourceLocation} from '..'; -import { - ArrayExpression, - Effect, - FunctionExpression, - GeneratedSource, - HIRFunction, - IdentifierId, - Instruction, - makeInstructionId, - TInstruction, - InstructionId, - ScopeId, - ReactiveScopeDependency, - Place, - ReactiveScope, - ReactiveScopeDependencies, - Terminal, - isUseRefType, - isSetStateType, - isFireFunctionType, - makeScopeId, - HIR, - BasicBlock, - BlockId, - isEffectEventFunctionType, -} from '../HIR'; -import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; -import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; -import {ReactiveScopeDependencyTreeHIR} from '../HIR/DeriveMinimalDependenciesHIR'; -import {DEFAULT_EXPORT} from '../HIR/Environment'; -import { - createTemporaryPlace, - fixScopeAndIdentifierRanges, - markInstructionIds, - markPredecessors, - reversePostorderBlocks, -} from '../HIR/HIRBuilder'; -import { - collectTemporariesSidemap, - DependencyCollectionContext, - handleInstruction, -} from '../HIR/PropagateScopeDependenciesHIR'; -import {buildDependencyInstructions} from '../HIR/ScopeDependencyUtils'; -import { - eachInstructionOperand, - eachTerminalOperand, - terminalFallthrough, -} from '../HIR/visitors'; -import {empty} from '../Utils/Stack'; -import {getOrInsertWith} from '../Utils/utils'; -import {deadCodeElimination} from '../Optimization'; -import {BuiltInAutodepsId} from '../HIR/ObjectShape'; - -/** - * Infers reactive dependencies captured by useEffect lambdas and adds them as - * a second argument to the useEffect call if no dependency array is provided. - */ -export function inferEffectDependencies(fn: HIRFunction): void { - const fnExpressions = new Map< - IdentifierId, - TInstruction - >(); - - const autodepFnConfigs = new Map>(); - for (const effectTarget of fn.env.config.inferEffectDependencies!) { - const moduleTargets = getOrInsertWith( - autodepFnConfigs, - effectTarget.function.source, - () => new Map(), - ); - moduleTargets.set( - effectTarget.function.importSpecifierName, - effectTarget.autodepsIndex, - ); - } - const autodepFnLoads = new Map(); - const autodepModuleLoads = new Map>(); - - const scopeInfos = new Map(); - - const loadGlobals = new Set(); - - /** - * When inserting LoadLocals, we need to retain the reactivity of the base - * identifier, as later passes e.g. PruneNonReactiveDeps take the reactivity of - * a base identifier as the "maximal" reactivity of all its references. - * Concretely, - * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) - */ - const reactiveIds = inferReactiveIdentifiers(fn); - const rewriteBlocks: Array = []; - - for (const [, block] of fn.body.blocks) { - if (block.terminal.kind === 'scope') { - const scopeBlock = fn.body.blocks.get(block.terminal.block)!; - if ( - scopeBlock.instructions.length === 1 && - scopeBlock.terminal.kind === 'goto' && - scopeBlock.terminal.block === block.terminal.fallthrough - ) { - scopeInfos.set( - block.terminal.scope.id, - block.terminal.scope.dependencies, - ); - } - } - const rewriteInstrs: Array = []; - for (const instr of block.instructions) { - const {value, lvalue} = instr; - if (value.kind === 'FunctionExpression') { - fnExpressions.set( - lvalue.identifier.id, - instr as TInstruction, - ); - } else if (value.kind === 'PropertyLoad') { - if ( - typeof value.property === 'string' && - autodepModuleLoads.has(value.object.identifier.id) - ) { - const moduleTargets = autodepModuleLoads.get( - value.object.identifier.id, - )!; - const propertyName = value.property; - const numRequiredArgs = moduleTargets.get(propertyName); - if (numRequiredArgs != null) { - autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs); - } - } - } else if (value.kind === 'LoadGlobal') { - loadGlobals.add(lvalue.identifier.id); - /* - * TODO: Handle properties on default exports, like - * import React from 'react'; - * React.useEffect(...); - */ - if (value.binding.kind === 'ImportNamespace') { - const moduleTargets = autodepFnConfigs.get(value.binding.module); - if (moduleTargets != null) { - autodepModuleLoads.set(lvalue.identifier.id, moduleTargets); - } - } - if ( - value.binding.kind === 'ImportSpecifier' || - value.binding.kind === 'ImportDefault' - ) { - const moduleTargets = autodepFnConfigs.get(value.binding.module); - if (moduleTargets != null) { - const importSpecifierName = - value.binding.kind === 'ImportSpecifier' - ? value.binding.imported - : DEFAULT_EXPORT; - const numRequiredArgs = moduleTargets.get(importSpecifierName); - if (numRequiredArgs != null) { - autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs); - } - } - } - } else if ( - value.kind === 'CallExpression' || - value.kind === 'MethodCall' - ) { - const callee = - value.kind === 'CallExpression' ? value.callee : value.property; - - const autodepsArgIndex = value.args.findIndex( - arg => - arg.kind === 'Identifier' && - arg.identifier.type.kind === 'Object' && - arg.identifier.type.shapeId === BuiltInAutodepsId, - ); - const autodepsArgExpectedIndex = autodepFnLoads.get( - callee.identifier.id, - ); - - if ( - value.args.length > 0 && - autodepsArgExpectedIndex != null && - autodepsArgIndex === autodepsArgExpectedIndex && - autodepFnLoads.has(callee.identifier.id) && - value.args[0].kind === 'Identifier' - ) { - // We have a useEffect call with no deps array, so we need to infer the deps - const effectDeps: Array = []; - const deps: ArrayExpression = { - kind: 'ArrayExpression', - elements: effectDeps, - loc: GeneratedSource, - }; - const depsPlace = createTemporaryPlace(fn.env, GeneratedSource); - depsPlace.effect = Effect.Read; - - const fnExpr = fnExpressions.get(value.args[0].identifier.id); - if (fnExpr != null) { - // We have a function expression, so we can infer its dependencies - const scopeInfo = - fnExpr.lvalue.identifier.scope != null - ? scopeInfos.get(fnExpr.lvalue.identifier.scope.id) - : null; - let minimalDeps: Set; - if (scopeInfo != null) { - minimalDeps = new Set(scopeInfo); - } else { - minimalDeps = inferMinimalDependencies(fnExpr); - } - /** - * Step 1: push dependencies to the effect deps array - * - * Note that it's invalid to prune all non-reactive deps in this pass, see - * the `infer-effect-deps/pruned-nonreactive-obj` fixture for an - * explanation. - */ - - const usedDeps = []; - for (const maybeDep of minimalDeps) { - if ( - ((isUseRefType(maybeDep.identifier) || - isSetStateType(maybeDep.identifier)) && - !reactiveIds.has(maybeDep.identifier.id)) || - isFireFunctionType(maybeDep.identifier) || - isEffectEventFunctionType(maybeDep.identifier) - ) { - // exclude non-reactive hook results, which will never be in a memo block - continue; - } - - const dep = truncateDepAtCurrent(maybeDep); - const {place, value, exitBlockId} = buildDependencyInstructions( - dep, - fn.env, - ); - rewriteInstrs.push({ - kind: 'block', - location: instr.id, - value, - exitBlockId: exitBlockId, - }); - effectDeps.push(place); - usedDeps.push(dep); - } - - // For LSP autodeps feature. - const decorations: Array = []; - for (const loc of collectDepUsages(usedDeps, fnExpr.value)) { - if (typeof loc === 'symbol') { - continue; - } - decorations.push(loc); - } - if (typeof value.loc !== 'symbol') { - fn.env.logger?.logEvent(fn.env.filename, { - kind: 'AutoDepsDecorations', - fnLoc: value.loc, - decorations, - }); - } - - // Step 2: push the inferred deps array as an argument of the useEffect - rewriteInstrs.push({ - kind: 'instr', - location: instr.id, - value: { - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - effects: null, - }, - }); - value.args[autodepsArgIndex] = { - ...depsPlace, - effect: Effect.Freeze, - }; - fn.env.inferredEffectLocations.add(callee.loc); - } else if (loadGlobals.has(value.args[0].identifier.id)) { - // Global functions have no reactive dependencies, so we can insert an empty array - rewriteInstrs.push({ - kind: 'instr', - location: instr.id, - value: { - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: {...depsPlace, effect: Effect.Mutate}, - value: deps, - effects: null, - }, - }); - value.args[autodepsArgIndex] = { - ...depsPlace, - effect: Effect.Freeze, - }; - fn.env.inferredEffectLocations.add(callee.loc); - } - } else if ( - value.args.length >= 2 && - value.args.length - 1 === autodepFnLoads.get(callee.identifier.id) && - value.args[0] != null && - value.args[0].kind === 'Identifier' - ) { - const penultimateArg = value.args[value.args.length - 2]; - const depArrayArg = value.args[value.args.length - 1]; - if ( - depArrayArg.kind !== 'Spread' && - penultimateArg.kind !== 'Spread' && - typeof depArrayArg.loc !== 'symbol' && - typeof penultimateArg.loc !== 'symbol' && - typeof value.loc !== 'symbol' - ) { - fn.env.logger?.logEvent(fn.env.filename, { - kind: 'AutoDepsEligible', - fnLoc: value.loc, - depArrayLoc: { - ...depArrayArg.loc, - start: penultimateArg.loc.end, - end: depArrayArg.loc.end, - }, - }); - } - } - } - } - rewriteSplices(block, rewriteInstrs, rewriteBlocks); - } - - if (rewriteBlocks.length > 0) { - for (const block of rewriteBlocks) { - fn.body.blocks.set(block.id, block); - } - - /** - * Fixup the HIR to restore RPO, ensure correct predecessors, and renumber - * instructions. - */ - reversePostorderBlocks(fn.body); - markPredecessors(fn.body); - // Renumber instructions and fix scope ranges - markInstructionIds(fn.body); - fixScopeAndIdentifierRanges(fn.body); - deadCodeElimination(fn); - - fn.env.hasInferredEffect = true; - } -} - -function truncateDepAtCurrent( - dep: ReactiveScopeDependency, -): ReactiveScopeDependency { - const idx = dep.path.findIndex(path => path.property === 'current'); - if (idx === -1) { - return dep; - } else { - return {...dep, path: dep.path.slice(0, idx)}; - } -} - -type SpliceInfo = - | {kind: 'instr'; location: InstructionId; value: Instruction} - | { - kind: 'block'; - location: InstructionId; - value: HIR; - exitBlockId: BlockId; - }; - -function rewriteSplices( - originalBlock: BasicBlock, - splices: Array, - rewriteBlocks: Array, -): void { - if (splices.length === 0) { - return; - } - /** - * Splice instructions or value blocks into the original block. - * --- original block --- - * bb_original - * instr1 - * ... - * instr2 <-- splice location - * instr3 - * ... - * - * - * If there is more than one block in the splice, this means that we're - * splicing in a set of value-blocks of the following structure: - * --- blocks we're splicing in --- - * bb_entry: - * instrEntry - * ... - * fallthrough=bb_exit - * - * bb1(value): - * ... - * - * bb_exit: - * instrExit - * ... - * - * - * - * --- rewritten blocks --- - * bb_original - * instr1 - * ... (original instructions) - * instr2 - * instrEntry - * ... (spliced instructions) - * fallthrough=bb_exit - * - * bb1(value): - * ... - * - * bb_exit: - * instrExit - * ... (spliced instructions) - * instr3 - * ... (original instructions) - * - */ - const originalInstrs = originalBlock.instructions; - let currBlock: BasicBlock = {...originalBlock, instructions: []}; - rewriteBlocks.push(currBlock); - - let cursor = 0; - - for (const rewrite of splices) { - while (originalInstrs[cursor].id < rewrite.location) { - CompilerError.invariant( - originalInstrs[cursor].id < originalInstrs[cursor + 1].id, - { - reason: - '[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted', - loc: originalInstrs[cursor].loc, - }, - ); - currBlock.instructions.push(originalInstrs[cursor]); - cursor++; - } - CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, { - reason: - '[InferEffectDependencies] Internal invariant broken: splice location not found', - loc: originalInstrs[cursor].loc, - }); - - if (rewrite.kind === 'instr') { - currBlock.instructions.push(rewrite.value); - } else if (rewrite.kind === 'block') { - const {entry, blocks} = rewrite.value; - const entryBlock = blocks.get(entry)!; - // splice in all instructions from the entry block - currBlock.instructions.push(...entryBlock.instructions); - if (blocks.size > 1) { - /** - * We're splicing in a set of value-blocks, which means we need - * to push new blocks and update terminals. - */ - CompilerError.invariant( - terminalFallthrough(entryBlock.terminal) === rewrite.exitBlockId, - { - reason: - '[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough', - loc: entryBlock.terminal.loc, - }, - ); - const originalTerminal = currBlock.terminal; - currBlock.terminal = entryBlock.terminal; - - for (const [id, block] of blocks) { - if (id === entry) { - continue; - } - if (id === rewrite.exitBlockId) { - block.terminal = originalTerminal; - currBlock = block; - } - rewriteBlocks.push(block); - } - } - } - } - currBlock.instructions.push(...originalInstrs.slice(cursor)); -} - -function inferReactiveIdentifiers(fn: HIRFunction): Set { - const reactiveIds: Set = new Set(); - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - /** - * No need to traverse into nested functions as - * 1. their effects are recorded in `LoweredFunction.dependencies` - * 2. we don't mark `reactive` in these anyways - */ - for (const place of eachInstructionOperand(instr)) { - if (place.reactive) { - reactiveIds.add(place.identifier.id); - } - } - } - - for (const place of eachTerminalOperand(block.terminal)) { - if (place.reactive) { - reactiveIds.add(place.identifier.id); - } - } - } - return reactiveIds; -} - -function collectDepUsages( - deps: Array, - fnExpr: FunctionExpression, -): Array { - const identifiers: Map = new Map(); - const loadedDeps: Set = new Set(); - const sourceLocations = []; - for (const dep of deps) { - identifiers.set(dep.identifier.id, dep); - } - - for (const [, block] of fnExpr.loweredFunc.func.body.blocks) { - for (const instr of block.instructions) { - if ( - instr.value.kind === 'LoadLocal' && - identifiers.has(instr.value.place.identifier.id) - ) { - loadedDeps.add(instr.lvalue.identifier.id); - } - for (const place of eachInstructionOperand(instr)) { - if (loadedDeps.has(place.identifier.id)) { - // TODO(@jbrown215): handle member exprs!! - sourceLocations.push(place.identifier.loc); - } - } - } - } - - return sourceLocations; -} - -function inferMinimalDependencies( - fnInstr: TInstruction, -): Set { - const fn = fnInstr.value.loweredFunc.func; - - const temporaries = collectTemporariesSidemap(fn, new Set()); - const { - hoistableObjects, - processedInstrsInOptional, - temporariesReadInOptional, - } = collectOptionalChainSidemap(fn); - - const hoistablePropertyLoads = collectHoistablePropertyLoadsInInnerFn( - fnInstr, - temporaries, - hoistableObjects, - ); - const hoistableToFnEntry = hoistablePropertyLoads.get(fn.body.entry); - CompilerError.invariant(hoistableToFnEntry != null, { - reason: - '[InferEffectDependencies] Internal invariant broken: missing entry block', - loc: fnInstr.loc, - }); - - const dependencies = inferDependencies( - fnInstr, - new Map([...temporaries, ...temporariesReadInOptional]), - processedInstrsInOptional, - ); - - const tree = new ReactiveScopeDependencyTreeHIR( - [...hoistableToFnEntry.assumedNonNullObjects].map(o => o.fullPath), - ); - for (const dep of dependencies) { - tree.addDependency({...dep}); - } - - return tree.deriveMinimalDependencies(); -} - -function inferDependencies( - fnInstr: TInstruction, - temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, -): Set { - const fn = fnInstr.value.loweredFunc.func; - const context = new DependencyCollectionContext( - new Set(), - temporaries, - processedInstrsInOptional, - ); - for (const dep of fn.context) { - context.declare(dep.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - const placeholderScope: ReactiveScope = { - id: makeScopeId(0), - range: { - start: fnInstr.id, - end: makeInstructionId(fnInstr.id + 1), - }, - dependencies: new Set(), - reassignments: new Set(), - declarations: new Map(), - earlyReturnValue: null, - merged: new Set(), - loc: GeneratedSource, - }; - context.enterScope(placeholderScope); - inferDependenciesInFn(fn, context, temporaries); - context.exitScope(placeholderScope, false); - const resultUnfiltered = context.deps.get(placeholderScope); - CompilerError.invariant(resultUnfiltered != null, { - reason: - '[InferEffectDependencies] Internal invariant broken: missing scope dependencies', - loc: fn.loc, - }); - - const fnContext = new Set(fn.context.map(dep => dep.identifier.id)); - const result = new Set(); - for (const dep of resultUnfiltered) { - if (fnContext.has(dep.identifier.id)) { - result.add(dep); - } - } - - return result; -} - -function inferDependenciesInFn( - fn: HIRFunction, - context: DependencyCollectionContext, - temporaries: ReadonlyMap, -): void { - for (const [, block] of fn.body.blocks) { - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); - } - } - } - for (const instr of block.instructions) { - if ( - instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod' - ) { - context.declare(instr.lvalue.identifier, { - id: instr.id, - scope: context.currentScope, - }); - /** - * Recursively visit the inner function to extract dependencies - */ - const innerFn = instr.value.loweredFunc.func; - context.enterInnerFn(instr as TInstruction, () => { - inferDependenciesInFn(innerFn, context, temporaries); - }); - } else { - handleInstruction(instr, context); - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts index eb645cc218dc..6ff5a0c53bf4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts @@ -9,4 +9,3 @@ export {default as analyseFunctions} from './AnalyseFunctions'; export {dropManualMemoization} from './DropManualMemoization'; export {inferReactivePlaces} from './InferReactivePlaces'; export {inlineImmediatelyInvokedFunctionExpressions} from './InlineImmediatelyInvokedFunctionExpressions'; -export {inferEffectDependencies} from './InferEffectDependencies'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts deleted file mode 100644 index 3588cf32f9f6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts +++ /dev/null @@ -1,790 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - BasicBlock, - BlockId, - BuiltinTag, - DeclarationId, - Effect, - forkTemporaryIdentifier, - GotoTerminal, - GotoVariant, - HIRFunction, - Identifier, - IfTerminal, - Instruction, - InstructionKind, - JsxAttribute, - makeInstructionId, - makePropertyLiteral, - ObjectProperty, - Phi, - Place, - promoteTemporary, - SpreadPattern, -} from '../HIR'; -import { - createTemporaryPlace, - fixScopeAndIdentifierRanges, - markInstructionIds, - markPredecessors, - reversePostorderBlocks, -} from '../HIR/HIRBuilder'; -import {CompilerError, EnvironmentConfig} from '..'; -import { - mapInstructionLValues, - mapInstructionOperands, - mapInstructionValueOperands, - mapTerminalOperands, -} from '../HIR/visitors'; -import {ErrorCategory} from '../CompilerError'; - -type InlinedJsxDeclarationMap = Map< - DeclarationId, - {identifier: Identifier; blockIdsToIgnore: Set} ->; - -/** - * A prod-only, RN optimization to replace JSX with inlined ReactElement object literals - * - * Example: - * <>foo - * _______________ - * let t1; - * if (__DEV__) { - * t1 = <>foo - * } else { - * t1 = {...} - * } - * - */ -export function inlineJsxTransform( - fn: HIRFunction, - inlineJsxTransformConfig: NonNullable< - EnvironmentConfig['inlineJsxTransform'] - >, -): void { - const inlinedJsxDeclarations: InlinedJsxDeclarationMap = new Map(); - /** - * Step 1: Codegen the conditional and ReactElement object literal - */ - for (const [_, currentBlock] of [...fn.body.blocks]) { - let fallthroughBlockInstructions: Array | null = null; - const instructionCount = currentBlock.instructions.length; - for (let i = 0; i < instructionCount; i++) { - const instr = currentBlock.instructions[i]!; - // TODO: Support value blocks - if (currentBlock.kind === 'value') { - fn.env.logger?.logEvent(fn.env.filename, { - kind: 'CompileDiagnostic', - fnLoc: null, - detail: { - category: ErrorCategory.Todo, - reason: 'JSX Inlining is not supported on value blocks', - loc: instr.loc, - }, - }); - continue; - } - switch (instr.value.kind) { - case 'JsxExpression': - case 'JsxFragment': { - /** - * Split into blocks for new IfTerminal: - * current, then, else, fallthrough - */ - const currentBlockInstructions = currentBlock.instructions.slice( - 0, - i, - ); - const thenBlockInstructions = currentBlock.instructions.slice( - i, - i + 1, - ); - const elseBlockInstructions: Array = []; - fallthroughBlockInstructions ??= currentBlock.instructions.slice( - i + 1, - ); - - const fallthroughBlockId = fn.env.nextBlockId; - const fallthroughBlock: BasicBlock = { - kind: currentBlock.kind, - id: fallthroughBlockId, - instructions: fallthroughBlockInstructions, - terminal: currentBlock.terminal, - preds: new Set(), - phis: new Set(), - }; - - /** - * Complete current block - * - Add instruction for variable declaration - * - Add instruction for LoadGlobal used by conditional - * - End block with a new IfTerminal - */ - const varPlace = createTemporaryPlace(fn.env, instr.value.loc); - promoteTemporary(varPlace.identifier); - const varLValuePlace = createTemporaryPlace(fn.env, instr.value.loc); - const thenVarPlace = { - ...varPlace, - identifier: forkTemporaryIdentifier( - fn.env.nextIdentifierId, - varPlace.identifier, - ), - }; - const elseVarPlace = { - ...varPlace, - identifier: forkTemporaryIdentifier( - fn.env.nextIdentifierId, - varPlace.identifier, - ), - }; - const varInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...varLValuePlace}, - value: { - kind: 'DeclareLocal', - lvalue: {place: {...varPlace}, kind: InstructionKind.Let}, - type: null, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - currentBlockInstructions.push(varInstruction); - - const devGlobalPlace = createTemporaryPlace(fn.env, instr.value.loc); - const devGlobalInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...devGlobalPlace, effect: Effect.Mutate}, - value: { - kind: 'LoadGlobal', - binding: { - kind: 'Global', - name: inlineJsxTransformConfig.globalDevVar, - }, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - currentBlockInstructions.push(devGlobalInstruction); - const thenBlockId = fn.env.nextBlockId; - const elseBlockId = fn.env.nextBlockId; - const ifTerminal: IfTerminal = { - kind: 'if', - test: {...devGlobalPlace, effect: Effect.Read}, - consequent: thenBlockId, - alternate: elseBlockId, - fallthrough: fallthroughBlockId, - loc: instr.loc, - id: makeInstructionId(0), - }; - currentBlock.instructions = currentBlockInstructions; - currentBlock.terminal = ifTerminal; - - /** - * Set up then block where we put the original JSX return - */ - const thenBlock: BasicBlock = { - id: thenBlockId, - instructions: thenBlockInstructions, - kind: 'block', - phis: new Set(), - preds: new Set(), - terminal: { - kind: 'goto', - block: fallthroughBlockId, - variant: GotoVariant.Break, - id: makeInstructionId(0), - loc: instr.loc, - }, - }; - fn.body.blocks.set(thenBlockId, thenBlock); - - const resassignElsePlace = createTemporaryPlace( - fn.env, - instr.value.loc, - ); - const reassignElseInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...resassignElsePlace}, - value: { - kind: 'StoreLocal', - lvalue: { - place: elseVarPlace, - kind: InstructionKind.Reassign, - }, - value: {...instr.lvalue}, - type: null, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - thenBlockInstructions.push(reassignElseInstruction); - - /** - * Set up else block where we add new codegen - */ - const elseBlockTerminal: GotoTerminal = { - kind: 'goto', - block: fallthroughBlockId, - variant: GotoVariant.Break, - id: makeInstructionId(0), - loc: instr.loc, - }; - const elseBlock: BasicBlock = { - id: elseBlockId, - instructions: elseBlockInstructions, - kind: 'block', - phis: new Set(), - preds: new Set(), - terminal: elseBlockTerminal, - }; - fn.body.blocks.set(elseBlockId, elseBlock); - - /** - * ReactElement object literal codegen - */ - const {refProperty, keyProperty, propsProperty} = - createPropsProperties( - fn, - instr, - elseBlockInstructions, - instr.value.kind === 'JsxExpression' ? instr.value.props : [], - instr.value.children, - ); - const reactElementInstructionPlace = createTemporaryPlace( - fn.env, - instr.value.loc, - ); - const reactElementInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...reactElementInstructionPlace, effect: Effect.Store}, - value: { - kind: 'ObjectExpression', - properties: [ - createSymbolProperty( - fn, - instr, - elseBlockInstructions, - '$$typeof', - inlineJsxTransformConfig.elementSymbol, - ), - instr.value.kind === 'JsxExpression' - ? createTagProperty( - fn, - instr, - elseBlockInstructions, - instr.value.tag, - ) - : createSymbolProperty( - fn, - instr, - elseBlockInstructions, - 'type', - 'react.fragment', - ), - refProperty, - keyProperty, - propsProperty, - ], - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - elseBlockInstructions.push(reactElementInstruction); - - const reassignConditionalInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...createTemporaryPlace(fn.env, instr.value.loc)}, - value: { - kind: 'StoreLocal', - lvalue: { - place: {...elseVarPlace}, - kind: InstructionKind.Reassign, - }, - value: {...reactElementInstruction.lvalue}, - type: null, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - elseBlockInstructions.push(reassignConditionalInstruction); - - /** - * Create phis to reassign the var - */ - const operands: Map = new Map(); - operands.set(thenBlockId, { - ...elseVarPlace, - }); - operands.set(elseBlockId, { - ...thenVarPlace, - }); - - const phiIdentifier = forkTemporaryIdentifier( - fn.env.nextIdentifierId, - varPlace.identifier, - ); - const phiPlace = { - ...createTemporaryPlace(fn.env, instr.value.loc), - identifier: phiIdentifier, - }; - const phis: Set = new Set([ - { - kind: 'Phi', - operands, - place: phiPlace, - }, - ]); - fallthroughBlock.phis = phis; - fn.body.blocks.set(fallthroughBlockId, fallthroughBlock); - - /** - * Track this JSX instruction so we can replace references in step 2 - */ - inlinedJsxDeclarations.set(instr.lvalue.identifier.declarationId, { - identifier: phiIdentifier, - blockIdsToIgnore: new Set([thenBlockId, elseBlockId]), - }); - break; - } - case 'FunctionExpression': - case 'ObjectMethod': { - inlineJsxTransform( - instr.value.loweredFunc.func, - inlineJsxTransformConfig, - ); - break; - } - } - } - } - - /** - * Step 2: Replace declarations with new phi values - */ - for (const [blockId, block] of fn.body.blocks) { - for (const instr of block.instructions) { - mapInstructionOperands(instr, place => - handlePlace(place, blockId, inlinedJsxDeclarations), - ); - - mapInstructionLValues(instr, lvalue => - handlelValue(lvalue, blockId, inlinedJsxDeclarations), - ); - - mapInstructionValueOperands(instr.value, place => - handlePlace(place, blockId, inlinedJsxDeclarations), - ); - } - - mapTerminalOperands(block.terminal, place => - handlePlace(place, blockId, inlinedJsxDeclarations), - ); - - if (block.terminal.kind === 'scope') { - const scope = block.terminal.scope; - for (const dep of scope.dependencies) { - dep.identifier = handleIdentifier( - dep.identifier, - inlinedJsxDeclarations, - ); - } - - for (const [origId, decl] of [...scope.declarations]) { - const newDecl = handleIdentifier( - decl.identifier, - inlinedJsxDeclarations, - ); - if (newDecl.id !== origId) { - scope.declarations.delete(origId); - scope.declarations.set(decl.identifier.id, { - identifier: newDecl, - scope: decl.scope, - }); - } - } - } - } - - /** - * Step 3: Fixup the HIR - * Restore RPO, ensure correct predecessors, renumber instructions, fix scope and ranges. - */ - reversePostorderBlocks(fn.body); - markPredecessors(fn.body); - markInstructionIds(fn.body); - fixScopeAndIdentifierRanges(fn.body); -} - -function createSymbolProperty( - fn: HIRFunction, - instr: Instruction, - nextInstructions: Array, - propertyName: string, - symbolName: string, -): ObjectProperty { - const symbolPlace = createTemporaryPlace(fn.env, instr.value.loc); - const symbolInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...symbolPlace, effect: Effect.Mutate}, - value: { - kind: 'LoadGlobal', - binding: {kind: 'Global', name: 'Symbol'}, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - nextInstructions.push(symbolInstruction); - - const symbolForPlace = createTemporaryPlace(fn.env, instr.value.loc); - const symbolForInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...symbolForPlace, effect: Effect.Read}, - value: { - kind: 'PropertyLoad', - object: {...symbolInstruction.lvalue}, - property: makePropertyLiteral('for'), - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - nextInstructions.push(symbolForInstruction); - - const symbolValuePlace = createTemporaryPlace(fn.env, instr.value.loc); - const symbolValueInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...symbolValuePlace, effect: Effect.Mutate}, - value: { - kind: 'Primitive', - value: symbolName, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - nextInstructions.push(symbolValueInstruction); - - const $$typeofPlace = createTemporaryPlace(fn.env, instr.value.loc); - const $$typeofInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...$$typeofPlace, effect: Effect.Mutate}, - value: { - kind: 'MethodCall', - receiver: symbolInstruction.lvalue, - property: symbolForInstruction.lvalue, - args: [symbolValueInstruction.lvalue], - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - const $$typeofProperty: ObjectProperty = { - kind: 'ObjectProperty', - key: {name: propertyName, kind: 'string'}, - type: 'property', - place: {...$$typeofPlace, effect: Effect.Capture}, - }; - nextInstructions.push($$typeofInstruction); - return $$typeofProperty; -} - -function createTagProperty( - fn: HIRFunction, - instr: Instruction, - nextInstructions: Array, - componentTag: BuiltinTag | Place, -): ObjectProperty { - let tagProperty: ObjectProperty; - switch (componentTag.kind) { - case 'BuiltinTag': { - const tagPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); - const tagInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...tagPropertyPlace, effect: Effect.Mutate}, - value: { - kind: 'Primitive', - value: componentTag.name, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - tagProperty = { - kind: 'ObjectProperty', - key: {name: 'type', kind: 'string'}, - type: 'property', - place: {...tagPropertyPlace, effect: Effect.Capture}, - }; - nextInstructions.push(tagInstruction); - break; - } - case 'Identifier': { - tagProperty = { - kind: 'ObjectProperty', - key: {name: 'type', kind: 'string'}, - type: 'property', - place: {...componentTag, effect: Effect.Capture}, - }; - break; - } - } - - return tagProperty; -} - -function createPropsProperties( - fn: HIRFunction, - instr: Instruction, - nextInstructions: Array, - propAttributes: Array, - children: Array | null, -): { - refProperty: ObjectProperty; - keyProperty: ObjectProperty; - propsProperty: ObjectProperty; -} { - let refProperty: ObjectProperty | undefined; - let keyProperty: ObjectProperty | undefined; - const props: Array = []; - const jsxAttributesWithoutKey = propAttributes.filter( - p => p.kind === 'JsxAttribute' && p.name !== 'key', - ); - const jsxSpreadAttributes = propAttributes.filter( - p => p.kind === 'JsxSpreadAttribute', - ); - const spreadPropsOnly = - jsxAttributesWithoutKey.length === 0 && jsxSpreadAttributes.length === 1; - propAttributes.forEach(prop => { - switch (prop.kind) { - case 'JsxAttribute': { - switch (prop.name) { - case 'key': { - keyProperty = { - kind: 'ObjectProperty', - key: {name: 'key', kind: 'string'}, - type: 'property', - place: {...prop.place}, - }; - break; - } - case 'ref': { - /** - * In the current JSX implementation, ref is both - * a property on the element and a property on props. - */ - refProperty = { - kind: 'ObjectProperty', - key: {name: 'ref', kind: 'string'}, - type: 'property', - place: {...prop.place}, - }; - const refPropProperty: ObjectProperty = { - kind: 'ObjectProperty', - key: {name: 'ref', kind: 'string'}, - type: 'property', - place: {...prop.place}, - }; - props.push(refPropProperty); - break; - } - default: { - const attributeProperty: ObjectProperty = { - kind: 'ObjectProperty', - key: {name: prop.name, kind: 'string'}, - type: 'property', - place: {...prop.place}, - }; - props.push(attributeProperty); - } - } - break; - } - case 'JsxSpreadAttribute': { - props.push({ - kind: 'Spread', - place: {...prop.argument}, - }); - break; - } - } - }); - - const propsPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); - if (children) { - let childrenPropProperty: ObjectProperty; - if (children.length === 1) { - childrenPropProperty = { - kind: 'ObjectProperty', - key: {name: 'children', kind: 'string'}, - type: 'property', - place: {...children[0], effect: Effect.Capture}, - }; - } else { - const childrenPropPropertyPlace = createTemporaryPlace( - fn.env, - instr.value.loc, - ); - - const childrenPropInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...childrenPropPropertyPlace, effect: Effect.Mutate}, - value: { - kind: 'ArrayExpression', - elements: [...children], - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - nextInstructions.push(childrenPropInstruction); - childrenPropProperty = { - kind: 'ObjectProperty', - key: {name: 'children', kind: 'string'}, - type: 'property', - place: {...childrenPropPropertyPlace, effect: Effect.Capture}, - }; - } - props.push(childrenPropProperty); - } - - if (refProperty == null) { - const refPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); - const refInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...refPropertyPlace, effect: Effect.Mutate}, - value: { - kind: 'Primitive', - value: null, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - refProperty = { - kind: 'ObjectProperty', - key: {name: 'ref', kind: 'string'}, - type: 'property', - place: {...refPropertyPlace, effect: Effect.Capture}, - }; - nextInstructions.push(refInstruction); - } - - if (keyProperty == null) { - const keyPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); - const keyInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...keyPropertyPlace, effect: Effect.Mutate}, - value: { - kind: 'Primitive', - value: null, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - keyProperty = { - kind: 'ObjectProperty', - key: {name: 'key', kind: 'string'}, - type: 'property', - place: {...keyPropertyPlace, effect: Effect.Capture}, - }; - nextInstructions.push(keyInstruction); - } - - let propsProperty: ObjectProperty; - if (spreadPropsOnly) { - const spreadProp = jsxSpreadAttributes[0]; - CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', { - reason: 'Spread prop attribute must be of kind JSXSpreadAttribute', - loc: instr.loc, - }); - propsProperty = { - kind: 'ObjectProperty', - key: {name: 'props', kind: 'string'}, - type: 'property', - place: {...spreadProp.argument, effect: Effect.Mutate}, - }; - } else { - const propsInstruction: Instruction = { - id: makeInstructionId(0), - lvalue: {...propsPropertyPlace, effect: Effect.Mutate}, - value: { - kind: 'ObjectExpression', - properties: props, - loc: instr.value.loc, - }, - effects: null, - loc: instr.loc, - }; - propsProperty = { - kind: 'ObjectProperty', - key: {name: 'props', kind: 'string'}, - type: 'property', - place: {...propsPropertyPlace, effect: Effect.Capture}, - }; - nextInstructions.push(propsInstruction); - } - - return {refProperty, keyProperty, propsProperty}; -} - -function handlePlace( - place: Place, - blockId: BlockId, - inlinedJsxDeclarations: InlinedJsxDeclarationMap, -): Place { - const inlinedJsxDeclaration = inlinedJsxDeclarations.get( - place.identifier.declarationId, - ); - if ( - inlinedJsxDeclaration == null || - inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) - ) { - return place; - } - - return {...place, identifier: inlinedJsxDeclaration.identifier}; -} - -function handlelValue( - lvalue: Place, - blockId: BlockId, - inlinedJsxDeclarations: InlinedJsxDeclarationMap, -): Place { - const inlinedJsxDeclaration = inlinedJsxDeclarations.get( - lvalue.identifier.declarationId, - ); - if ( - inlinedJsxDeclaration == null || - inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) - ) { - return lvalue; - } - - return {...lvalue, identifier: inlinedJsxDeclaration.identifier}; -} - -function handleIdentifier( - identifier: Identifier, - inlinedJsxDeclarations: InlinedJsxDeclarationMap, -): Identifier { - const inlinedJsxDeclaration = inlinedJsxDeclarations.get( - identifier.declarationId, - ); - return inlinedJsxDeclaration == null - ? identifier - : inlinedJsxDeclaration.identifier; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InstructionReordering.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InstructionReordering.ts deleted file mode 100644 index f0c038247e87..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InstructionReordering.ts +++ /dev/null @@ -1,503 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '..'; -import { - BasicBlock, - Environment, - GeneratedSource, - HIRFunction, - IdentifierId, - Instruction, - InstructionId, - Place, - isExpressionBlockKind, - makeInstructionId, - markInstructionIds, -} from '../HIR'; -import {printInstruction} from '../HIR/PrintHIR'; -import { - eachInstructionLValue, - eachInstructionValueLValue, - eachInstructionValueOperand, - eachTerminalOperand, -} from '../HIR/visitors'; -import {getOrInsertWith} from '../Utils/utils'; - -/** - * This pass implements conservative instruction reordering to move instructions closer to - * to where their produced values are consumed. The goal is to group instructions in a way that - * is more optimal for future optimizations. Notably, MergeReactiveScopesThatAlwaysInvalidateTogether - * can only merge two candidate scopes if there are no intervenining instructions that are used by - * some later code: instruction reordering can move those intervening instructions later in many cases, - * thereby allowing more scopes to merge together. - * - * The high-level approach is to build a dependency graph where nodes correspond either to - * instructions OR to a particular lvalue assignment of another instruction. So - * `Destructure [x, y] = z` creates 3 nodes: one for the instruction, and one each for x and y. - * The lvalue nodes depend on the instruction node that assigns them. - * - * Dependency edges are added for all the lvalues and rvalues of each instruction, so for example - * the node for `t$2 = CallExpression t$0 ( t$1 )` will take dependencies on the nodes for t$0 and t$1. - * - * Individual instructions are grouped into two categories: - * - "Reorderable" instructions include a safe set of instructions that we know are fine to reorder. - * This includes JSX elements/fragments/text, primitives, template literals, and globals. - * These instructions are never emitted until they are referenced, and can even be moved across - * basic blocks until they are used. - * - All other instructions are non-reorderable, and take an explicit dependency on the last such - * non-reorderable instruction in their block. This largely ensures that mutations are serialized, - * since all potentially mutating instructions are in this category. - * - * The only remaining mutation not handled by the above is variable reassignment. To ensure that all - * reads/writes of a variable access the correct version, all references (lvalues and rvalues) to - * each named variable are serialized. Thus `x = 1; y = x; x = 2; z = x` will establish a chain - * of dependencies and retain the correct ordering. - * - * The algorithm proceeds one basic block at a time, first building up the dependnecy graph and then - * reordering. - * - * The reordering weights nodes according to their transitive dependencies, and whether a particular node - * needs memoization or not. Larger dependencies go first, followed by smaller dependencies, which in - * testing seems to allow scopes to merge more effectively. Over time we can likely continue to improve - * the reordering heuristic. - * - * An obvious area for improvement is to allow reordering of LoadLocals that occur after the last write - * of the named variable. We can add this in a follow-up. - */ -export function instructionReordering(fn: HIRFunction): void { - // Shared nodes are emitted when they are first used - const shared: Nodes = new Map(); - const references = findReferencedRangeOfTemporaries(fn); - for (const [, block] of fn.body.blocks) { - reorderBlock(fn.env, block, shared, references); - } - CompilerError.invariant(shared.size === 0, { - reason: `InstructionReordering: expected all reorderable nodes to have been emitted`, - loc: - [...shared.values()] - .map(node => node.instruction?.loc) - .filter(loc => loc != null)[0] ?? GeneratedSource, - }); - markInstructionIds(fn.body); -} - -const DEBUG = false; - -type Nodes = Map; -type Node = { - instruction: Instruction | null; - dependencies: Set; - reorderability: Reorderability; - depth: number | null; -}; - -// Inclusive start and end -type References = { - singleUseIdentifiers: SingleUseIdentifiers; - lastAssignments: LastAssignments; -}; -type LastAssignments = Map; -type SingleUseIdentifiers = Set; -enum ReferenceKind { - Read, - Write, -} -function findReferencedRangeOfTemporaries(fn: HIRFunction): References { - const singleUseIdentifiers = new Map(); - const lastAssignments: LastAssignments = new Map(); - function reference( - instr: InstructionId, - place: Place, - kind: ReferenceKind, - ): void { - if ( - place.identifier.name !== null && - place.identifier.name.kind === 'named' - ) { - if (kind === ReferenceKind.Write) { - const name = place.identifier.name.value; - const previous = lastAssignments.get(name); - if (previous === undefined) { - lastAssignments.set(name, instr); - } else { - lastAssignments.set( - name, - makeInstructionId(Math.max(previous, instr)), - ); - } - } - return; - } else if (kind === ReferenceKind.Read) { - const previousCount = singleUseIdentifiers.get(place.identifier.id) ?? 0; - singleUseIdentifiers.set(place.identifier.id, previousCount + 1); - } - } - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - for (const operand of eachInstructionValueLValue(instr.value)) { - reference(instr.id, operand, ReferenceKind.Read); - } - for (const lvalue of eachInstructionLValue(instr)) { - reference(instr.id, lvalue, ReferenceKind.Write); - } - } - for (const operand of eachTerminalOperand(block.terminal)) { - reference(block.terminal.id, operand, ReferenceKind.Read); - } - } - return { - singleUseIdentifiers: new Set( - [...singleUseIdentifiers] - .filter(([, count]) => count === 1) - .map(([id]) => id), - ), - lastAssignments, - }; -} - -function reorderBlock( - env: Environment, - block: BasicBlock, - shared: Nodes, - references: References, -): void { - const locals: Nodes = new Map(); - const named: Map = new Map(); - let previous: IdentifierId | null = null; - for (const instr of block.instructions) { - const {lvalue, value} = instr; - // Get or create a node for this lvalue - const reorderability = getReorderability(instr, references); - const node = getOrInsertWith( - locals, - lvalue.identifier.id, - () => - ({ - instruction: instr, - dependencies: new Set(), - reorderability, - depth: null, - }) as Node, - ); - /** - * Ensure non-reoderable instructions have their order retained by - * adding explicit dependencies to the previous such instruction. - */ - if (reorderability === Reorderability.Nonreorderable) { - if (previous !== null) { - node.dependencies.add(previous); - } - previous = lvalue.identifier.id; - } - /** - * Establish dependencies on operands - */ - for (const operand of eachInstructionValueOperand(value)) { - const {name, id} = operand.identifier; - if (name !== null && name.kind === 'named') { - // Serialize all accesses to named variables - const previous = named.get(name.value); - if (previous !== undefined) { - node.dependencies.add(previous); - } - named.set(name.value, lvalue.identifier.id); - } else if (locals.has(id) || shared.has(id)) { - node.dependencies.add(id); - } - } - /** - * Establish nodes for lvalues, with dependencies on the node - * for the instruction itself. This ensures that any consumers - * of the lvalue will take a dependency through to the original - * instruction. - */ - for (const lvalueOperand of eachInstructionValueLValue(value)) { - const lvalueNode = getOrInsertWith( - locals, - lvalueOperand.identifier.id, - () => - ({ - instruction: null, - dependencies: new Set(), - depth: null, - }) as Node, - ); - lvalueNode.dependencies.add(lvalue.identifier.id); - const name = lvalueOperand.identifier.name; - if (name !== null && name.kind === 'named') { - const previous = named.get(name.value); - if (previous !== undefined) { - node.dependencies.add(previous); - } - named.set(name.value, lvalue.identifier.id); - } - } - } - - const nextInstructions: Array = []; - const seen = new Set(); - - DEBUG && console.log(`bb${block.id}`); - - /** - * The ideal order for emitting instructions may change the final instruction, - * but value blocks have special semantics for the final instruction of a block - - * that's the expression's value!. So we choose between a less optimal strategy - * for value blocks which preserves the final instruction order OR a more optimal - * ordering for statement-y blocks. - */ - if (isExpressionBlockKind(block.kind)) { - // First emit everything that can't be reordered - if (previous !== null) { - DEBUG && console.log(`(last non-reorderable instruction)`); - DEBUG && print(env, locals, shared, seen, previous); - emit(env, locals, shared, nextInstructions, previous); - } - /* - * For "value" blocks the final instruction represents its value, so we have to be - * careful to not change the ordering. Emit the last instruction explicitly. - * Any non-reorderable instructions will get emitted first, and any unused - * reorderable instructions can be deferred to the shared node list. - */ - if (block.instructions.length !== 0) { - DEBUG && console.log(`(block value)`); - DEBUG && - print( - env, - locals, - shared, - seen, - block.instructions.at(-1)!.lvalue.identifier.id, - ); - emit( - env, - locals, - shared, - nextInstructions, - block.instructions.at(-1)!.lvalue.identifier.id, - ); - } - /* - * Then emit the dependencies of the terminal operand. In many cases they will have - * already been emitted in the previous step and this is a no-op. - * TODO: sort the dependencies based on weight, like we do for other nodes. Not a big - * deal though since most terminals have a single operand - */ - for (const operand of eachTerminalOperand(block.terminal)) { - DEBUG && console.log(`(terminal operand)`); - DEBUG && print(env, locals, shared, seen, operand.identifier.id); - emit(env, locals, shared, nextInstructions, operand.identifier.id); - } - // Anything not emitted yet is globally reorderable - for (const [id, node] of locals) { - if (node.instruction == null) { - continue; - } - CompilerError.invariant( - node.reorderability === Reorderability.Reorderable, - { - reason: `Expected all remaining instructions to be reorderable`, - description: - node.instruction != null - ? `Instruction [${node.instruction.id}] was not emitted yet but is not reorderable` - : `Lvalue $${id} was not emitted yet but is not reorderable`, - loc: node.instruction?.loc ?? block.terminal.loc, - }, - ); - - DEBUG && console.log(`save shared: $${id}`); - shared.set(id, node); - } - } else { - /** - * If this is not a value block, then the order within the block doesn't matter - * and we can optimize more. The observation is that blocks often have instructions - * such as: - * - * ``` - * t$0 = nonreorderable - * t$1 = nonreorderable <-- this gets in the way of merging t$0 and t$2 - * t$2 = reorderable deps[ t$0 ] - * return t$2 - * ``` - * - * Ie where there is some pair of nonreorderable+reorderable values, with some intervening - * also non-reorderable instruction. If we emit all non-reorderable instructions first, - * then we'll keep the original order. But reordering instructions doesn't just mean moving - * them later: we can also move them _earlier_. By starting from terminal operands we - * end up emitting: - * - * ``` - * t$0 = nonreorderable // dep of t$2 - * t$2 = reorderable deps[ t$0 ] - * t$1 = nonreorderable <-- not in the way of merging anymore! - * return t$2 - * ``` - * - * Ie all nonreorderable transitive deps of the terminal operands will get emitted first, - * but we'll be able to intersperse the depending reorderable instructions in between - * them in a way that works better with scope merging. - */ - for (const operand of eachTerminalOperand(block.terminal)) { - DEBUG && console.log(`(terminal operand)`); - DEBUG && print(env, locals, shared, seen, operand.identifier.id); - emit(env, locals, shared, nextInstructions, operand.identifier.id); - } - // Anything not emitted yet is globally reorderable - for (const id of Array.from(locals.keys()).reverse()) { - const node = locals.get(id); - if (node === undefined) { - continue; - } - if (node.reorderability === Reorderability.Reorderable) { - DEBUG && console.log(`save shared: $${id}`); - shared.set(id, node); - } else { - DEBUG && console.log('leftover'); - DEBUG && print(env, locals, shared, seen, id); - emit(env, locals, shared, nextInstructions, id); - } - } - } - - block.instructions = nextInstructions; - DEBUG && console.log(); -} - -function getDepth(env: Environment, nodes: Nodes, id: IdentifierId): number { - const node = nodes.get(id)!; - if (node == null) { - return 0; - } - if (node.depth != null) { - return node.depth; - } - node.depth = 0; // in case of cycles - let depth = node.reorderability === Reorderability.Reorderable ? 1 : 10; - for (const dep of node.dependencies) { - depth += getDepth(env, nodes, dep); - } - node.depth = depth; - return depth; -} - -function print( - env: Environment, - locals: Nodes, - shared: Nodes, - seen: Set, - id: IdentifierId, - depth: number = 0, -): void { - if (seen.has(id)) { - DEBUG && console.log(`${'| '.repeat(depth)}$${id} `); - return; - } - seen.add(id); - const node = locals.get(id) ?? shared.get(id); - if (node == null) { - return; - } - const deps = [...node.dependencies]; - deps.sort((a, b) => { - const aDepth = getDepth(env, locals, a); - const bDepth = getDepth(env, locals, b); - return bDepth - aDepth; - }); - for (const dep of deps) { - print(env, locals, shared, seen, dep, depth + 1); - } - DEBUG && - console.log( - `${'| '.repeat(depth)}$${id} ${printNode(node)} deps=[${deps - .map(x => `$${x}`) - .join(', ')}] depth=${node.depth}`, - ); -} - -function printNode(node: Node): string { - const {instruction} = node; - if (instruction === null) { - return ''; - } - switch (instruction.value.kind) { - case 'FunctionExpression': - case 'ObjectMethod': { - return `[${instruction.id}] ${instruction.value.kind}`; - } - default: { - return printInstruction(instruction); - } - } -} - -function emit( - env: Environment, - locals: Nodes, - shared: Nodes, - instructions: Array, - id: IdentifierId, -): void { - const node = locals.get(id) ?? shared.get(id); - if (node == null) { - return; - } - locals.delete(id); - shared.delete(id); - const deps = [...node.dependencies]; - deps.sort((a, b) => { - const aDepth = getDepth(env, locals, a); - const bDepth = getDepth(env, locals, b); - return bDepth - aDepth; - }); - for (const dep of deps) { - emit(env, locals, shared, instructions, dep); - } - if (node.instruction !== null) { - instructions.push(node.instruction); - } -} - -enum Reorderability { - Reorderable, - Nonreorderable, -} -function getReorderability( - instr: Instruction, - references: References, -): Reorderability { - switch (instr.value.kind) { - case 'JsxExpression': - case 'JsxFragment': - case 'JSXText': - case 'LoadGlobal': - case 'Primitive': - case 'TemplateLiteral': - case 'BinaryExpression': - case 'UnaryExpression': { - return Reorderability.Reorderable; - } - case 'LoadLocal': { - const name = instr.value.place.identifier.name; - if (name !== null && name.kind === 'named') { - const lastAssignment = references.lastAssignments.get(name.value); - if ( - lastAssignment !== undefined && - lastAssignment < instr.id && - references.singleUseIdentifiers.has(instr.lvalue.identifier.id) - ) { - return Reorderability.Reorderable; - } - } - return Reorderability.Nonreorderable; - } - default: { - return Reorderability.Nonreorderable; - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts deleted file mode 100644 index 50f00427205b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - ArrayExpression, - BasicBlock, - CallExpression, - Destructure, - Environment, - ExternalFunction, - GeneratedSource, - HIRFunction, - IdentifierId, - Instruction, - LoadGlobal, - LoadLocal, - NonLocalImportSpecifier, - Place, - PropertyLoad, - isUseContextHookType, - makeBlockId, - makeInstructionId, - makePropertyLiteral, - markInstructionIds, - promoteTemporary, - reversePostorderBlocks, -} from '../HIR'; -import {createTemporaryPlace} from '../HIR/HIRBuilder'; -import {enterSSA} from '../SSA'; -import {inferTypes} from '../TypeInference'; - -export function lowerContextAccess( - fn: HIRFunction, - loweredContextCalleeConfig: ExternalFunction, -): void { - const contextAccess: Map = new Map(); - const contextKeys: Map> = new Map(); - - // collect context access and keys - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - const {value, lvalue} = instr; - - if ( - value.kind === 'CallExpression' && - isUseContextHookType(value.callee.identifier) - ) { - contextAccess.set(lvalue.identifier.id, value); - continue; - } - - if (value.kind !== 'Destructure') { - continue; - } - - const destructureId = value.value.identifier.id; - if (!contextAccess.has(destructureId)) { - continue; - } - - const keys = getContextKeys(value); - if (keys === null) { - return; - } - - if (contextKeys.has(destructureId)) { - /* - * TODO(gsn): Add support for accessing context over multiple - * statements. - */ - return; - } else { - contextKeys.set(destructureId, keys); - } - } - } - - let importLoweredContextCallee: NonLocalImportSpecifier | null = null; - - if (contextAccess.size > 0 && contextKeys.size > 0) { - for (const [, block] of fn.body.blocks) { - let nextInstructions: Array | null = null; - - for (let i = 0; i < block.instructions.length; i++) { - const instr = block.instructions[i]; - const {lvalue, value} = instr; - if ( - value.kind === 'CallExpression' && - isUseContextHookType(value.callee.identifier) && - contextKeys.has(lvalue.identifier.id) - ) { - importLoweredContextCallee ??= - fn.env.programContext.addImportSpecifier( - loweredContextCalleeConfig, - ); - const loweredContextCalleeInstr = emitLoadLoweredContextCallee( - fn.env, - importLoweredContextCallee, - ); - - if (nextInstructions === null) { - nextInstructions = block.instructions.slice(0, i); - } - nextInstructions.push(loweredContextCalleeInstr); - - const keys = contextKeys.get(lvalue.identifier.id)!; - const selectorFnInstr = emitSelectorFn(fn.env, keys); - nextInstructions.push(selectorFnInstr); - - const lowerContextCallId = loweredContextCalleeInstr.lvalue; - value.callee = lowerContextCallId; - - const selectorFn = selectorFnInstr.lvalue; - value.args.push(selectorFn); - } - - if (nextInstructions) { - nextInstructions.push(instr); - } - } - if (nextInstructions) { - block.instructions = nextInstructions; - } - } - markInstructionIds(fn.body); - inferTypes(fn); - } -} - -function emitLoadLoweredContextCallee( - env: Environment, - importedLowerContextCallee: NonLocalImportSpecifier, -): Instruction { - const loadGlobal: LoadGlobal = { - kind: 'LoadGlobal', - binding: {...importedLowerContextCallee}, - loc: GeneratedSource, - }; - - return { - id: makeInstructionId(0), - loc: GeneratedSource, - lvalue: createTemporaryPlace(env, GeneratedSource), - effects: null, - value: loadGlobal, - }; -} - -function getContextKeys(value: Destructure): Array | null { - const keys = []; - const pattern = value.lvalue.pattern; - - switch (pattern.kind) { - case 'ArrayPattern': { - return null; - } - - case 'ObjectPattern': { - for (const place of pattern.properties) { - if ( - place.kind !== 'ObjectProperty' || - place.type !== 'property' || - place.key.kind !== 'identifier' || - place.place.identifier.name === null || - place.place.identifier.name.kind !== 'named' - ) { - return null; - } - keys.push(place.key.name); - } - return keys; - } - } -} - -function emitPropertyLoad( - env: Environment, - obj: Place, - property: string, -): {instructions: Array; element: Place} { - const loadObj: LoadLocal = { - kind: 'LoadLocal', - place: obj, - loc: GeneratedSource, - }; - const object: Place = createTemporaryPlace(env, GeneratedSource); - const loadLocalInstr: Instruction = { - lvalue: object, - value: loadObj, - id: makeInstructionId(0), - effects: null, - loc: GeneratedSource, - }; - - const loadProp: PropertyLoad = { - kind: 'PropertyLoad', - object, - property: makePropertyLiteral(property), - loc: GeneratedSource, - }; - const element: Place = createTemporaryPlace(env, GeneratedSource); - const loadPropInstr: Instruction = { - lvalue: element, - value: loadProp, - id: makeInstructionId(0), - effects: null, - loc: GeneratedSource, - }; - return { - instructions: [loadLocalInstr, loadPropInstr], - element: element, - }; -} - -function emitSelectorFn(env: Environment, keys: Array): Instruction { - const obj: Place = createTemporaryPlace(env, GeneratedSource); - promoteTemporary(obj.identifier); - const instr: Array = []; - const elements = []; - for (const key of keys) { - const {instructions, element: prop} = emitPropertyLoad(env, obj, key); - instr.push(...instructions); - elements.push(prop); - } - - const arrayInstr = emitArrayInstr(elements, env); - instr.push(arrayInstr); - - const block: BasicBlock = { - kind: 'block', - id: makeBlockId(0), - instructions: instr, - terminal: { - id: makeInstructionId(0), - kind: 'return', - returnVariant: 'Explicit', - loc: GeneratedSource, - value: arrayInstr.lvalue, - effects: null, - }, - preds: new Set(), - phis: new Set(), - }; - - const fn: HIRFunction = { - loc: GeneratedSource, - id: null, - nameHint: null, - fnType: 'Other', - env, - params: [obj], - returnTypeAnnotation: null, - returns: createTemporaryPlace(env, GeneratedSource), - context: [], - body: { - entry: block.id, - blocks: new Map([[block.id, block]]), - }, - generator: false, - async: false, - directives: [], - aliasingEffects: [], - }; - - reversePostorderBlocks(fn.body); - markInstructionIds(fn.body); - enterSSA(fn); - inferTypes(fn); - - const fnInstr: Instruction = { - id: makeInstructionId(0), - value: { - kind: 'FunctionExpression', - name: null, - nameHint: null, - loweredFunc: { - func: fn, - }, - type: 'ArrowFunctionExpression', - loc: GeneratedSource, - }, - lvalue: createTemporaryPlace(env, GeneratedSource), - effects: null, - loc: GeneratedSource, - }; - return fnInstr; -} - -function emitArrayInstr(elements: Array, env: Environment): Instruction { - const array: ArrayExpression = { - kind: 'ArrayExpression', - elements, - loc: GeneratedSource, - }; - const arrayLvalue: Place = createTemporaryPlace(env, GeneratedSource); - const arrayInstr: Instruction = { - id: makeInstructionId(0), - value: array, - lvalue: arrayLvalue, - effects: null, - loc: GeneratedSource, - }; - return arrayInstr; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts index bb060b8dc285..722b05a80996 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts @@ -8,4 +8,3 @@ export {constantPropagation} from './ConstantPropagation'; export {deadCodeElimination} from './DeadCodeElimination'; export {pruneMaybeThrows} from './PruneMaybeThrows'; -export {inlineJsxTransform} from './InlineJsxTransform'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index c60e8fb95967..c44e3b83fefd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -52,7 +52,7 @@ import {assertExhaustive} from '../Utils/utils'; import {buildReactiveFunction} from './BuildReactiveFunction'; import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; -import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {ProgramContext} from '../Entrypoint'; export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel'; @@ -100,17 +100,6 @@ export type CodegenFunction = { fn: CodegenFunction; type: ReactFunctionType | null; }>; - - /** - * This is true if the compiler has compiled inferred effect dependencies - */ - hasInferredEffect: boolean; - inferredEffectLocations: Set; - - /** - * This is true if the compiler has compiled a fire to a useFire call - */ - hasFireRewrite: boolean; }; export function codegenFunction( @@ -387,9 +376,6 @@ function codegenReactiveFunction( prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks, prunedMemoValues: countMemoBlockVisitor.prunedMemoValues, outlined: [], - hasFireRewrite: fn.env.hasFireRewrite, - hasInferredEffect: fn.env.hasInferredEffect, - inferredEffectLocations: fn.env.inferredEffectLocations, }); } @@ -574,30 +560,6 @@ function codegenBlockNoReset( return t.blockStatement(statements); } -function wrapCacheDep(cx: Context, value: t.Expression): t.Expression { - if ( - cx.env.config.enableEmitFreeze != null && - cx.env.outputMode === 'client' - ) { - const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier( - cx.env.config.enableEmitFreeze, - ).name; - cx.env.programContext - .assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope) - .unwrap(); - return t.conditionalExpression( - t.identifier(EMIT_FREEZE_GLOBAL_GATING), - t.callExpression(t.identifier(emitFreezeIdentifier), [ - value, - t.stringLiteral(cx.fnName), - ]), - value, - ); - } else { - return value; - } -} - function codegenReactiveScope( cx: Context, statements: Array, @@ -612,12 +574,9 @@ function codegenReactiveScope( value: t.Expression; }> = []; const changeExpressions: Array = []; - const changeExpressionComments: Array = []; - const outputComments: Array = []; for (const dep of [...scope.dependencies].sort(compareScopeDependency)) { const index = cx.nextCacheIndex; - changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( '!==', t.memberExpression( @@ -627,18 +586,7 @@ function codegenReactiveScope( ), codegenDependency(cx, dep), ); - - if (cx.env.config.enableChangeVariableCodegen) { - const changeIdentifier = t.identifier(cx.synthesizeName(`c_${index}`)); - statements.push( - t.variableDeclaration('const', [ - t.variableDeclarator(changeIdentifier, comparison), - ]), - ); - changeExpressions.push(changeIdentifier); - } else { - changeExpressions.push(comparison); - } + changeExpressions.push(comparison); /* * Adding directly to cacheStoreStatements rather than cacheLoads, because there * is no corresponding cacheLoadStatement for dependencies @@ -676,13 +624,12 @@ function codegenReactiveScope( }); const name = convertIdentifier(identifier); - outputComments.push(name.name); if (!cx.hasDeclared(identifier)) { statements.push( t.variableDeclaration('let', [createVariableDeclarator(name, null)]), ); } - cacheLoads.push({name, index, value: wrapCacheDep(cx, name)}); + cacheLoads.push({name, index, value: name}); cx.declare(identifier); } for (const reassignment of scope.reassignments) { @@ -691,8 +638,7 @@ function codegenReactiveScope( firstOutputIndex = index; } const name = convertIdentifier(reassignment); - outputComments.push(name.name); - cacheLoads.push({name, index, value: wrapCacheDep(cx, name)}); + cacheLoads.push({name, index, value: name}); } let testCondition = (changeExpressions as Array).reduce( @@ -724,187 +670,44 @@ function codegenReactiveScope( ); } - if (cx.env.config.disableMemoizationForDebugging) { - CompilerError.invariant( - cx.env.config.enableChangeDetectionForDebugging == null, - { - reason: `Expected to not have both change detection enabled and memoization disabled`, - description: `Incompatible config options`, - loc: GeneratedSource, - }, - ); - testCondition = t.logicalExpression( - '||', - testCondition, - t.booleanLiteral(true), - ); - } let computationBlock = codegenBlock(cx, block); let memoStatement; - const detectionFunction = cx.env.config.enableChangeDetectionForDebugging; - if (detectionFunction != null && changeExpressions.length > 0) { - const loc = - typeof scope.loc === 'symbol' - ? 'unknown location' - : `(${scope.loc.start.line}:${scope.loc.end.line})`; - const importedDetectionFunctionIdentifier = - cx.env.programContext.addImportSpecifier(detectionFunction).name; - const cacheLoadOldValueStatements: Array = []; - const changeDetectionStatements: Array = []; - const idempotenceDetectionStatements: Array = []; - - for (const {name, index, value} of cacheLoads) { - const loadName = cx.synthesizeName(`old$${name.name}`); - const slot = t.memberExpression( - t.identifier(cx.synthesizeName('$')), - t.numericLiteral(index), - true, - ); - cacheStoreStatements.push( - t.expressionStatement(t.assignmentExpression('=', slot, value)), - ); - cacheLoadOldValueStatements.push( - t.variableDeclaration('let', [ - t.variableDeclarator(t.identifier(loadName), slot), - ]), - ); - changeDetectionStatements.push( - t.expressionStatement( - t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [ - t.identifier(loadName), - t.cloneNode(name, true), - t.stringLiteral(name.name), - t.stringLiteral(cx.fnName), - t.stringLiteral('cached'), - t.stringLiteral(loc), - ]), - ), - ); - idempotenceDetectionStatements.push( - t.expressionStatement( - t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [ - t.cloneNode(slot, true), - t.cloneNode(name, true), - t.stringLiteral(name.name), - t.stringLiteral(cx.fnName), - t.stringLiteral('recomputed'), - t.stringLiteral(loc), - ]), - ), - ); - idempotenceDetectionStatements.push( - t.expressionStatement(t.assignmentExpression('=', name, slot)), - ); - } - const condition = cx.synthesizeName('condition'); - const recomputationBlock = t.cloneNode(computationBlock, true); - memoStatement = t.blockStatement([ - ...computationBlock.body, - t.variableDeclaration('let', [ - t.variableDeclarator(t.identifier(condition), testCondition), - ]), - t.ifStatement( - t.unaryExpression('!', t.identifier(condition)), - t.blockStatement([ - ...cacheLoadOldValueStatements, - ...changeDetectionStatements, - ]), - ), - ...cacheStoreStatements, - t.ifStatement( - t.identifier(condition), - t.blockStatement([ - ...recomputationBlock.body, - ...idempotenceDetectionStatements, - ]), - ), - ]); - } else { - for (const {name, index, value} of cacheLoads) { - cacheStoreStatements.push( - t.expressionStatement( - t.assignmentExpression( - '=', - t.memberExpression( - t.identifier(cx.synthesizeName('$')), - t.numericLiteral(index), - true, - ), - value, + for (const {name, index, value} of cacheLoads) { + cacheStoreStatements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + t.identifier(cx.synthesizeName('$')), + t.numericLiteral(index), + true, ), + value, ), - ); - cacheLoadStatements.push( - t.expressionStatement( - t.assignmentExpression( - '=', - name, - t.memberExpression( - t.identifier(cx.synthesizeName('$')), - t.numericLiteral(index), - true, - ), + ), + ); + cacheLoadStatements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + name, + t.memberExpression( + t.identifier(cx.synthesizeName('$')), + t.numericLiteral(index), + true, ), ), - ); - } - computationBlock.body.push(...cacheStoreStatements); - memoStatement = t.ifStatement( - testCondition, - computationBlock, - t.blockStatement(cacheLoadStatements), + ), ); } + computationBlock.body.push(...cacheStoreStatements); + memoStatement = t.ifStatement( + testCondition, + computationBlock, + t.blockStatement(cacheLoadStatements), + ); - if (cx.env.config.enableMemoizationComments) { - if (changeExpressionComments.length) { - t.addComment( - memoStatement, - 'leading', - ` check if ${printDelimitedCommentList( - changeExpressionComments, - 'or', - )} changed`, - true, - ); - t.addComment( - memoStatement, - 'leading', - ` "useMemo" for ${printDelimitedCommentList(outputComments, 'and')}:`, - true, - ); - } else { - t.addComment( - memoStatement, - 'leading', - ' cache value with no dependencies', - true, - ); - t.addComment( - memoStatement, - 'leading', - ` "useMemo" for ${printDelimitedCommentList(outputComments, 'and')}:`, - true, - ); - } - if (computationBlock.body.length > 0) { - t.addComment( - computationBlock.body[0]!, - 'leading', - ` Inputs changed, recompute`, - true, - ); - } - if (cacheLoadStatements.length > 0) { - t.addComment( - cacheLoadStatements[0]!, - 'leading', - ` Inputs did not change, use cached value`, - true, - ); - } - } statements.push(memoStatement); const earlyReturnValue = scope.earlyReturnValue; @@ -1431,41 +1234,6 @@ function codegenForInit( } } -function printDependencyComment(dependency: ReactiveScopeDependency): string { - const identifier = convertIdentifier(dependency.identifier); - let name = identifier.name; - if (dependency.path !== null) { - for (const path of dependency.path) { - name += `.${path.property}`; - } - } - return name; -} - -function printDelimitedCommentList( - items: Array, - finalCompletion: string, -): string { - if (items.length === 2) { - return items.join(` ${finalCompletion} `); - } else if (items.length <= 1) { - return items.join(''); - } - - let output = []; - for (let i = 0; i < items.length; i++) { - const item = items[i]!; - if (i < items.length - 2) { - output.push(`${item}, `); - } else if (i === items.length - 2) { - output.push(`${item}, ${finalCompletion} `); - } else { - output.push(item); - } - } - return output.join(''); -} - function codegenDependency( cx: Context, dependency: ReactiveScopeDependency, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts deleted file mode 100644 index 9ef9d382c283..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts +++ /dev/null @@ -1,294 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import { - Environment, - Identifier, - IdentifierId, - InstructionId, - Place, - PropertyLiteral, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScopeBlock, - ReactiveTerminalStatement, - getHookKind, - isUseRefType, - isUseStateType, -} from '../HIR'; -import {eachCallArgument, eachInstructionLValue} from '../HIR/visitors'; -import DisjointSet from '../Utils/DisjointSet'; -import {assertExhaustive} from '../Utils/utils'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/** - * This pass is built based on the observation by @jbrown215 that arguments - * to useState and useRef are only used the first time a component is rendered. - * Any subsequent times, the arguments will be evaluated but ignored. In this pass, - * we use this fact to improve the output of the compiler by not recomputing values that - * are only used as arguments (or inputs to arguments to) useState and useRef. - * - * This pass isn't yet stress-tested so it's not enabled by default. It's only enabled - * to support certain debug modes that detect non-idempotent code, since non-idempotent - * code can "safely" be used if its only passed to useState and useRef. We plan to rewrite - * this pass in HIR and enable it as an optimization in the future. - * - * Algorithm: - * We take two passes over the reactive function AST. In the first pass, we gather - * aliases and build relationships between property accesses--the key thing we need - * to do here is to find that, e.g., $0.x and $1 refer to the same value if - * $1 = PropertyLoad $0.x. - * - * In the second pass, we traverse the AST in reverse order and track how each place - * is used. If a place is read from in any Terminal, we mark the place as "Update", meaning - * it is used whenever the component is updated/re-rendered. If a place is read from in - * a useState or useRef hook call, we mark it as "Create", since it is only used when the - * component is created. In other instructions, we propagate the inferred place for the - * instructions lvalues onto any other instructions that are read. - * - * Whenever we finish this reverse pass over a reactive block, we can look at the blocks - * dependencies and see whether the dependencies are used in an "Update" context or only - * in a "Create" context. If a dependency is create-only, then we can remove that dependency - * from the block. - */ - -type CreateUpdate = 'Create' | 'Update' | 'Unknown'; - -type KindMap = Map; - -class Visitor extends ReactiveFunctionVisitor { - map: KindMap = new Map(); - aliases: DisjointSet; - paths: Map>; - env: Environment; - - constructor( - env: Environment, - aliases: DisjointSet, - paths: Map>, - ) { - super(); - this.aliases = aliases; - this.paths = paths; - this.env = env; - } - - join(values: Array): CreateUpdate { - function join2(l: CreateUpdate, r: CreateUpdate): CreateUpdate { - if (l === 'Update' || r === 'Update') { - return 'Update'; - } else if (l === 'Create' || r === 'Create') { - return 'Create'; - } else if (l === 'Unknown' || r === 'Unknown') { - return 'Unknown'; - } - assertExhaustive(r, `Unhandled variable kind ${r}`); - } - return values.reduce(join2, 'Unknown'); - } - - isCreateOnlyHook(id: Identifier): boolean { - return isUseStateType(id) || isUseRefType(id); - } - - override visitPlace( - _: InstructionId, - place: Place, - state: CreateUpdate, - ): void { - this.map.set( - place.identifier.id, - this.join([state, this.map.get(place.identifier.id) ?? 'Unknown']), - ); - } - - override visitBlock(block: ReactiveBlock, state: CreateUpdate): void { - super.visitBlock([...block].reverse(), state); - } - - override visitInstruction(instruction: ReactiveInstruction): void { - const state = this.join( - [...eachInstructionLValue(instruction)].map( - operand => this.map.get(operand.identifier.id) ?? 'Unknown', - ), - ); - - const visitCallOrMethodNonArgs = (): void => { - switch (instruction.value.kind) { - case 'CallExpression': { - this.visitPlace(instruction.id, instruction.value.callee, state); - break; - } - case 'MethodCall': { - this.visitPlace(instruction.id, instruction.value.property, state); - this.visitPlace(instruction.id, instruction.value.receiver, state); - break; - } - } - }; - - const isHook = (): boolean => { - let callee = null; - switch (instruction.value.kind) { - case 'CallExpression': { - callee = instruction.value.callee.identifier; - break; - } - case 'MethodCall': { - callee = instruction.value.property.identifier; - break; - } - } - return callee != null && getHookKind(this.env, callee) != null; - }; - - switch (instruction.value.kind) { - case 'CallExpression': - case 'MethodCall': { - if ( - instruction.lvalue && - this.isCreateOnlyHook(instruction.lvalue.identifier) - ) { - [...eachCallArgument(instruction.value.args)].forEach(operand => - this.visitPlace(instruction.id, operand, 'Create'), - ); - visitCallOrMethodNonArgs(); - } else { - this.traverseInstruction(instruction, isHook() ? 'Update' : state); - } - break; - } - default: { - this.traverseInstruction(instruction, state); - } - } - } - - override visitScope(scope: ReactiveScopeBlock): void { - const state = this.join( - [ - ...scope.scope.declarations.keys(), - ...[...scope.scope.reassignments.values()].map(ident => ident.id), - ].map(id => this.map.get(id) ?? 'Unknown'), - ); - super.visitScope(scope, state); - [...scope.scope.dependencies].forEach(ident => { - let target: undefined | IdentifierId = - this.aliases.find(ident.identifier.id) ?? ident.identifier.id; - ident.path.forEach(token => { - target &&= this.paths.get(target)?.get(token.property); - }); - if (target && this.map.get(target) === 'Create') { - scope.scope.dependencies.delete(ident); - } - }); - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - state: CreateUpdate, - ): void { - CompilerError.invariant(state !== 'Create', { - reason: "Visiting a terminal statement with state 'Create'", - loc: stmt.terminal.loc, - }); - super.visitTerminal(stmt, state); - } - - override visitReactiveFunctionValue( - _id: InstructionId, - _dependencies: Array, - fn: ReactiveFunction, - state: CreateUpdate, - ): void { - visitReactiveFunction(fn, this, state); - } -} - -export default function pruneInitializationDependencies( - fn: ReactiveFunction, -): void { - const [aliases, paths] = getAliases(fn); - visitReactiveFunction(fn, new Visitor(fn.env, aliases, paths), 'Update'); -} - -function update( - map: Map>, - key: IdentifierId, - path: PropertyLiteral, - value: IdentifierId, -): void { - const inner = map.get(key) ?? new Map(); - inner.set(path, value); - map.set(key, inner); -} - -class AliasVisitor extends ReactiveFunctionVisitor { - scopeIdentifiers: DisjointSet = new DisjointSet(); - scopePaths: Map> = new Map(); - - override visitInstruction(instr: ReactiveInstruction): void { - if ( - instr.value.kind === 'StoreLocal' || - instr.value.kind === 'StoreContext' - ) { - this.scopeIdentifiers.union([ - instr.value.lvalue.place.identifier.id, - instr.value.value.identifier.id, - ]); - } else if ( - instr.value.kind === 'LoadLocal' || - instr.value.kind === 'LoadContext' - ) { - instr.lvalue && - this.scopeIdentifiers.union([ - instr.lvalue.identifier.id, - instr.value.place.identifier.id, - ]); - } else if (instr.value.kind === 'PropertyLoad') { - instr.lvalue && - update( - this.scopePaths, - instr.value.object.identifier.id, - instr.value.property, - instr.lvalue.identifier.id, - ); - } else if (instr.value.kind === 'PropertyStore') { - update( - this.scopePaths, - instr.value.object.identifier.id, - instr.value.property, - instr.value.value.identifier.id, - ); - } - } -} - -function getAliases( - fn: ReactiveFunction, -): [ - DisjointSet, - Map>, -] { - const visitor = new AliasVisitor(); - visitReactiveFunction(fn, visitor, null); - let disjoint = visitor.scopeIdentifiers; - let scopePaths = new Map>(); - for (const [key, value] of visitor.scopePaths) { - for (const [path, id] of value) { - update( - scopePaths, - disjoint.find(key) ?? key, - path, - disjoint.find(id) ?? id, - ); - } - } - return [disjoint, scopePaths]; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts deleted file mode 100644 index 6793df5710bf..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ /dev/null @@ -1,739 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError, CompilerErrorDetailOptions, SourceLocation} from '..'; -import { - ArrayExpression, - CallExpression, - Effect, - Environment, - FunctionExpression, - GeneratedSource, - HIRFunction, - Identifier, - IdentifierId, - Instruction, - InstructionId, - InstructionKind, - InstructionValue, - isUseEffectHookType, - LoadLocal, - makeInstructionId, - NonLocalImportSpecifier, - Place, - promoteTemporary, -} from '../HIR'; -import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; -import {getOrInsertWith} from '../Utils/utils'; -import { - BuiltInFireFunctionId, - BuiltInFireId, - DefaultNonmutatingHook, -} from '../HIR/ObjectShape'; -import {eachInstructionOperand} from '../HIR/visitors'; -import {printSourceLocationLine} from '../HIR/PrintHIR'; -import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; -import {ErrorCategory} from '../CompilerError'; - -/* - * TODO(jmbrown): - * - traverse object methods - * - method calls - * - React.useEffect calls - */ - -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - -export function transformFire(fn: HIRFunction): void { - const context = new Context(fn.env); - replaceFireFunctions(fn, context); - if (!context.hasErrors()) { - ensureNoMoreFireUses(fn, context); - } - context.throwIfErrorsFound(); -} - -function replaceFireFunctions(fn: HIRFunction, context: Context): void { - let importedUseFire: NonLocalImportSpecifier | null = null; - let hasRewrite = false; - for (const [, block] of fn.body.blocks) { - const rewriteInstrs = new Map>(); - const deleteInstrs = new Set(); - for (const instr of block.instructions) { - const {value, lvalue} = instr; - if ( - value.kind === 'CallExpression' && - isUseEffectHookType(value.callee.identifier) && - value.args.length > 0 && - value.args[0].kind === 'Identifier' - ) { - const lambda = context.getFunctionExpression( - value.args[0].identifier.id, - ); - if (lambda != null) { - const capturedCallees = - visitFunctionExpressionAndPropagateFireDependencies( - lambda, - context, - true, - ); - - // Add useFire calls for all fire calls in found in the lambda - const newInstrs = []; - for (const [ - fireCalleePlace, - fireCalleeInfo, - ] of capturedCallees.entries()) { - if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) { - context.addCalleeWithInsertedFire(fireCalleePlace); - - importedUseFire ??= fn.env.programContext.addImportSpecifier({ - source: fn.env.programContext.reactRuntimeModule, - importSpecifierName: USE_FIRE_FUNCTION_NAME, - }); - const loadUseFireInstr = makeLoadUseFireInstruction( - fn.env, - importedUseFire, - ); - const loadFireCalleeInstr = makeLoadFireCalleeInstruction( - fn.env, - fireCalleeInfo.capturedCalleeIdentifier, - ); - const callUseFireInstr = makeCallUseFireInstruction( - fn.env, - loadUseFireInstr.lvalue, - loadFireCalleeInstr.lvalue, - ); - const storeUseFireInstr = makeStoreUseFireInstruction( - fn.env, - callUseFireInstr.lvalue, - fireCalleeInfo.fireFunctionBinding, - ); - newInstrs.push( - loadUseFireInstr, - loadFireCalleeInstr, - callUseFireInstr, - storeUseFireInstr, - ); - - // We insert all of these instructions before the useEffect is loaded - const loadUseEffectInstrId = context.getLoadGlobalInstrId( - value.callee.identifier.id, - ); - if (loadUseEffectInstrId == null) { - context.pushError({ - loc: value.loc, - description: null, - category: ErrorCategory.Invariant, - reason: '[InsertFire] No LoadGlobal found for useEffect call', - suggestions: null, - }); - continue; - } - rewriteInstrs.set(loadUseEffectInstrId, newInstrs); - } - } - ensureNoRemainingCalleeCaptures( - lambda.loweredFunc.func, - context, - capturedCallees, - ); - - if ( - value.args.length > 1 && - value.args[1] != null && - value.args[1].kind === 'Identifier' - ) { - const depArray = value.args[1]; - const depArrayExpression = context.getArrayExpression( - depArray.identifier.id, - ); - if (depArrayExpression != null) { - for (const dependency of depArrayExpression.elements) { - if (dependency.kind === 'Identifier') { - const loadOfDependency = context.getLoadLocalInstr( - dependency.identifier.id, - ); - if (loadOfDependency != null) { - const replacedDepArrayItem = capturedCallees.get( - loadOfDependency.place.identifier.id, - ); - if (replacedDepArrayItem != null) { - loadOfDependency.place = - replacedDepArrayItem.fireFunctionBinding; - } - } - } - } - } else { - context.pushError({ - loc: value.args[1].loc, - description: - 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - category: ErrorCategory.Fire, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, - }); - } - } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { - context.pushError({ - loc: value.args[1].place.loc, - description: - 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - category: ErrorCategory.Fire, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, - }); - } - } - } else if ( - value.kind === 'CallExpression' && - value.callee.identifier.type.kind === 'Function' && - value.callee.identifier.type.shapeId === BuiltInFireId && - context.inUseEffectLambda() - ) { - /* - * We found a fire(callExpr()) call. We remove the `fire()` call and replace the callExpr() - * with a freshly generated fire function binding. We'll insert the useFire call before the - * useEffect call, which happens in the CallExpression (useEffect) case above. - */ - - /* - * We only allow fire to be called with a CallExpression: `fire(f())` - * TODO: add support for method calls: `fire(this.method())` - */ - if (value.args.length === 1 && value.args[0].kind === 'Identifier') { - const callExpr = context.getCallExpression( - value.args[0].identifier.id, - ); - - if (callExpr != null) { - const calleeId = callExpr.callee.identifier.id; - const loadLocal = context.getLoadLocalInstr(calleeId); - if (loadLocal == null) { - context.pushError({ - loc: value.loc, - description: null, - category: ErrorCategory.Invariant, - reason: - '[InsertFire] No loadLocal found for fire call argument', - suggestions: null, - }); - continue; - } - - const fireFunctionBinding = - context.getOrGenerateFireFunctionBinding( - loadLocal.place, - value.loc, - ); - - loadLocal.place = {...fireFunctionBinding}; - - // Delete the fire call expression - deleteInstrs.add(instr.id); - } else { - context.pushError({ - loc: value.loc, - description: - '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - category: ErrorCategory.Fire, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, - }); - } - } else { - let description: string = - 'fire() can only take in a single call expression as an argument'; - if (value.args.length === 0) { - description += ' but received none'; - } else if (value.args.length > 1) { - description += ' but received multiple arguments'; - } else if (value.args[0].kind === 'Spread') { - description += ' but received a spread argument'; - } - context.pushError({ - loc: value.loc, - description, - category: ErrorCategory.Fire, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, - }); - } - } else if (value.kind === 'CallExpression') { - context.addCallExpression(lvalue.identifier.id, value); - } else if ( - value.kind === 'FunctionExpression' && - context.inUseEffectLambda() - ) { - visitFunctionExpressionAndPropagateFireDependencies( - value, - context, - false, - ); - } else if (value.kind === 'FunctionExpression') { - context.addFunctionExpression(lvalue.identifier.id, value); - } else if (value.kind === 'LoadLocal') { - context.addLoadLocalInstr(lvalue.identifier.id, value); - } else if ( - value.kind === 'LoadGlobal' && - value.binding.kind === 'ImportSpecifier' && - value.binding.module === 'react' && - value.binding.imported === 'fire' && - context.inUseEffectLambda() - ) { - deleteInstrs.add(instr.id); - } else if (value.kind === 'LoadGlobal') { - context.addLoadGlobalInstrId(lvalue.identifier.id, instr.id); - } else if (value.kind === 'ArrayExpression') { - context.addArrayExpression(lvalue.identifier.id, value); - } - } - block.instructions = rewriteInstructions(rewriteInstrs, block.instructions); - block.instructions = deleteInstructions(deleteInstrs, block.instructions); - - if (rewriteInstrs.size > 0 || deleteInstrs.size > 0) { - hasRewrite = true; - fn.env.hasFireRewrite = true; - } - } - - if (hasRewrite) { - markInstructionIds(fn.body); - } -} - -/** - * Traverses a function expression to find fire calls fire(foo()) and replaces them with - * fireFoo(). - * - * When a function captures a fire call we need to update its context to reflect the newly created - * fire function bindings and update the LoadLocals referenced by the function's dependencies. - * - * @param isUseEffect is necessary so we can keep track of when we should additionally insert - * useFire hooks calls. - */ -function visitFunctionExpressionAndPropagateFireDependencies( - fnExpr: FunctionExpression, - context: Context, - enteringUseEffect: boolean, -): FireCalleesToFireFunctionBinding { - let withScope = enteringUseEffect - ? context.withUseEffectLambdaScope.bind(context) - : context.withFunctionScope.bind(context); - - const calleesCapturedByFnExpression = withScope(() => - replaceFireFunctions(fnExpr.loweredFunc.func, context), - ); - - // For each replaced callee, update the context of the function expression to track it - for ( - let contextIdx = 0; - contextIdx < fnExpr.loweredFunc.func.context.length; - contextIdx++ - ) { - const contextItem = fnExpr.loweredFunc.func.context[contextIdx]; - const replacedCallee = calleesCapturedByFnExpression.get( - contextItem.identifier.id, - ); - if (replacedCallee != null) { - fnExpr.loweredFunc.func.context[contextIdx] = { - ...replacedCallee.fireFunctionBinding, - }; - } - } - - context.mergeCalleesFromInnerScope(calleesCapturedByFnExpression); - - return calleesCapturedByFnExpression; -} - -/* - * eachInstructionOperand is not sufficient for our cases because: - * 1. fire is a global, which will not appear - * 2. The HIR may be malformed, so can't rely on function deps and must - * traverse the whole function. - */ -function* eachReachablePlace(fn: HIRFunction): Iterable { - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - if ( - instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod' - ) { - yield* eachReachablePlace(instr.value.loweredFunc.func); - } else { - yield* eachInstructionOperand(instr); - } - } - } -} - -function ensureNoRemainingCalleeCaptures( - fn: HIRFunction, - context: Context, - capturedCallees: FireCalleesToFireFunctionBinding, -): void { - for (const place of eachReachablePlace(fn)) { - const calleeInfo = capturedCallees.get(place.identifier.id); - if (calleeInfo != null) { - const calleeName = - calleeInfo.capturedCalleeIdentifier.name?.kind === 'named' - ? calleeInfo.capturedCalleeIdentifier.name.value - : ''; - context.pushError({ - loc: place.loc, - description: `All uses of ${calleeName} must be either used with a fire() call in \ -this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ -${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - category: ErrorCategory.Fire, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, - }); - } - } -} - -function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { - for (const place of eachReachablePlace(fn)) { - if ( - place.identifier.type.kind === 'Function' && - place.identifier.type.shapeId === BuiltInFireId - ) { - context.pushError({ - loc: place.identifier.loc, - description: 'Cannot use `fire` outside of a useEffect function', - category: ErrorCategory.Fire, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, - }); - } - } -} - -function makeLoadUseFireInstruction( - env: Environment, - importedLoadUseFire: NonLocalImportSpecifier, -): Instruction { - const useFirePlace = createTemporaryPlace(env, GeneratedSource); - useFirePlace.effect = Effect.Read; - useFirePlace.identifier.type = DefaultNonmutatingHook; - const instrValue: InstructionValue = { - kind: 'LoadGlobal', - binding: {...importedLoadUseFire}, - loc: GeneratedSource, - }; - return { - id: makeInstructionId(0), - value: instrValue, - lvalue: {...useFirePlace}, - loc: GeneratedSource, - effects: null, - }; -} - -function makeLoadFireCalleeInstruction( - env: Environment, - fireCalleeIdentifier: Identifier, -): Instruction { - const loadedFireCallee = createTemporaryPlace(env, GeneratedSource); - const fireCallee: Place = { - kind: 'Identifier', - identifier: fireCalleeIdentifier, - reactive: false, - effect: Effect.Unknown, - loc: fireCalleeIdentifier.loc, - }; - return { - id: makeInstructionId(0), - value: { - kind: 'LoadLocal', - loc: GeneratedSource, - place: {...fireCallee}, - }, - lvalue: {...loadedFireCallee}, - loc: GeneratedSource, - effects: null, - }; -} - -function makeCallUseFireInstruction( - env: Environment, - useFirePlace: Place, - argPlace: Place, -): Instruction { - const useFireCallResultPlace = createTemporaryPlace(env, GeneratedSource); - useFireCallResultPlace.effect = Effect.Read; - - const useFireCall: CallExpression = { - kind: 'CallExpression', - callee: {...useFirePlace}, - args: [argPlace], - loc: GeneratedSource, - }; - - return { - id: makeInstructionId(0), - value: useFireCall, - lvalue: {...useFireCallResultPlace}, - loc: GeneratedSource, - effects: null, - }; -} - -function makeStoreUseFireInstruction( - env: Environment, - useFireCallResultPlace: Place, - fireFunctionBindingPlace: Place, -): Instruction { - promoteTemporary(fireFunctionBindingPlace.identifier); - - const fireFunctionBindingLValuePlace = createTemporaryPlace( - env, - GeneratedSource, - ); - return { - id: makeInstructionId(0), - value: { - kind: 'StoreLocal', - lvalue: { - kind: InstructionKind.Const, - place: {...fireFunctionBindingPlace}, - }, - value: {...useFireCallResultPlace}, - type: null, - loc: GeneratedSource, - }, - lvalue: fireFunctionBindingLValuePlace, - loc: GeneratedSource, - effects: null, - }; -} - -type FireCalleesToFireFunctionBinding = Map< - IdentifierId, - { - fireFunctionBinding: Place; - capturedCalleeIdentifier: Identifier; - fireLoc: SourceLocation; - } ->; - -class Context { - #env: Environment; - - #errors: CompilerError = new CompilerError(); - - /* - * Used to look up the call expression passed to a `fire(callExpr())`. Gives back - * the `callExpr()`. - */ - #callExpressions = new Map(); - - /* - * We keep track of function expressions so that we can traverse them when - * we encounter a lambda passed to a useEffect call - */ - #functionExpressions = new Map(); - - /* - * Mapping from lvalue ids to the LoadLocal for it. Allows us to replace dependency LoadLocals. - */ - #loadLocals = new Map(); - - /* - * Maps all of the fire callees found in a component/hook to the generated fire function places - * we create for them. Allows us to reuse already-inserted useFire results - */ - #fireCalleesToFireFunctions: Map = new Map(); - - /* - * The callees for which we have already created fire bindings. Used to skip inserting a new - * useFire call for a fire callee if one has already been created. - */ - #calleesWithInsertedFire = new Set(); - - /* - * A mapping from fire callees to the created fire function bindings that are reachable from this - * scope. - * - * We additionally keep track of the captured callee identifier so that we can properly reference - * it in the place where we LoadLocal the callee as an argument to useFire. - */ - #capturedCalleeIdentifierIds: FireCalleesToFireFunctionBinding = new Map(); - - /* - * We only transform fire calls if we're syntactically within a useEffect lambda (for now) - */ - #inUseEffectLambda = false; - - /* - * Mapping from useEffect callee identifier ids to the instruction id of the - * load global instruction for the useEffect call. We use this to insert the - * useFire calls before the useEffect call - */ - #loadGlobalInstructionIds = new Map(); - - constructor(env: Environment) { - this.#env = env; - } - - /* - * We keep track of array expressions so we can rewrite dependency arrays passed to useEffect - * to use the fire functions - */ - #arrayExpressions = new Map(); - - pushError(error: CompilerErrorDetailOptions): void { - this.#errors.push(error); - } - - withFunctionScope(fn: () => void): FireCalleesToFireFunctionBinding { - fn(); - return this.#capturedCalleeIdentifierIds; - } - - withUseEffectLambdaScope(fn: () => void): FireCalleesToFireFunctionBinding { - const capturedCalleeIdentifierIds = this.#capturedCalleeIdentifierIds; - const inUseEffectLambda = this.#inUseEffectLambda; - - this.#capturedCalleeIdentifierIds = new Map(); - this.#inUseEffectLambda = true; - - const resultCapturedCalleeIdentifierIds = this.withFunctionScope(fn); - - this.#capturedCalleeIdentifierIds = capturedCalleeIdentifierIds; - this.#inUseEffectLambda = inUseEffectLambda; - - return resultCapturedCalleeIdentifierIds; - } - - addCallExpression(id: IdentifierId, callExpr: CallExpression): void { - this.#callExpressions.set(id, callExpr); - } - - getCallExpression(id: IdentifierId): CallExpression | undefined { - return this.#callExpressions.get(id); - } - - addLoadLocalInstr(id: IdentifierId, loadLocal: LoadLocal): void { - this.#loadLocals.set(id, loadLocal); - } - - getLoadLocalInstr(id: IdentifierId): LoadLocal | undefined { - return this.#loadLocals.get(id); - } - getOrGenerateFireFunctionBinding( - callee: Place, - fireLoc: SourceLocation, - ): Place { - const fireFunctionBinding = getOrInsertWith( - this.#fireCalleesToFireFunctions, - callee.identifier.id, - () => createTemporaryPlace(this.#env, GeneratedSource), - ); - - fireFunctionBinding.identifier.type = { - kind: 'Function', - shapeId: BuiltInFireFunctionId, - return: {kind: 'Poly'}, - isConstructor: false, - }; - - this.#capturedCalleeIdentifierIds.set(callee.identifier.id, { - fireFunctionBinding, - capturedCalleeIdentifier: callee.identifier, - fireLoc, - }); - - return fireFunctionBinding; - } - - mergeCalleesFromInnerScope( - innerCallees: FireCalleesToFireFunctionBinding, - ): void { - for (const [id, calleeInfo] of innerCallees.entries()) { - this.#capturedCalleeIdentifierIds.set(id, calleeInfo); - } - } - - addCalleeWithInsertedFire(id: IdentifierId): void { - this.#calleesWithInsertedFire.add(id); - } - - hasCalleeWithInsertedFire(id: IdentifierId): boolean { - return this.#calleesWithInsertedFire.has(id); - } - - inUseEffectLambda(): boolean { - return this.#inUseEffectLambda; - } - - addFunctionExpression(id: IdentifierId, fn: FunctionExpression): void { - this.#functionExpressions.set(id, fn); - } - - getFunctionExpression(id: IdentifierId): FunctionExpression | undefined { - return this.#functionExpressions.get(id); - } - - addLoadGlobalInstrId(id: IdentifierId, instrId: InstructionId): void { - this.#loadGlobalInstructionIds.set(id, instrId); - } - - getLoadGlobalInstrId(id: IdentifierId): InstructionId | undefined { - return this.#loadGlobalInstructionIds.get(id); - } - - addArrayExpression(id: IdentifierId, array: ArrayExpression): void { - this.#arrayExpressions.set(id, array); - } - - getArrayExpression(id: IdentifierId): ArrayExpression | undefined { - return this.#arrayExpressions.get(id); - } - - hasErrors(): boolean { - return this.#errors.hasAnyErrors(); - } - - throwIfErrorsFound(): void { - if (this.hasErrors()) throw this.#errors; - } -} - -function deleteInstructions( - deleteInstrs: Set, - instructions: Array, -): Array { - if (deleteInstrs.size > 0) { - const newInstrs = instructions.filter(instr => !deleteInstrs.has(instr.id)); - return newInstrs; - } - return instructions; -} - -function rewriteInstructions( - rewriteInstrs: Map>, - instructions: Array, -): Array { - if (rewriteInstrs.size > 0) { - const newInstrs = []; - for (const instr of instructions) { - const newInstrsAtId = rewriteInstrs.get(instr.id); - if (newInstrsAtId != null) { - newInstrs.push(...newInstrsAtId, instr); - } else { - newInstrs.push(instr); - } - } - - return newInstrs; - } - - return instructions; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/index.ts index 4f142104f210..a265a953eed3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/index.ts @@ -4,5 +4,3 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -export {transformFire} from './TransformFire'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts index 50fdfb356cd9..190e3d3a7a3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts @@ -8,7 +8,6 @@ import * as t from '@babel/types'; import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; -import {lowerType} from '../HIR/BuildHIR'; import { GeneratedSource, HIRFunction, @@ -26,7 +25,6 @@ import { } from '../HIR/HIR'; import { BuiltInArrayId, - BuiltInEventHandlerId, BuiltInFunctionId, BuiltInJsxId, BuiltInMixedReadonlyId, @@ -223,22 +221,11 @@ function* generateInstructionTypes( } case 'StoreLocal': { - if (env.config.enableUseTypeAnnotations) { - yield equation( - value.lvalue.place.identifier.type, - value.value.identifier.type, - ); - const valueType = - value.type === null ? makeType() : lowerType(value.type); - yield equation(valueType, value.lvalue.place.identifier.type); - yield equation(left, valueType); - } else { - yield equation(left, value.value.identifier.type); - yield equation( - value.lvalue.place.identifier.type, - value.value.identifier.type, - ); - } + yield equation(left, value.value.identifier.type); + yield equation( + value.lvalue.place.identifier.type, + value.value.identifier.type, + ); break; } @@ -422,12 +409,7 @@ function* generateInstructionTypes( } case 'TypeCastExpression': { - if (env.config.enableUseTypeAnnotations) { - yield equation(value.type, value.value.identifier.type); - yield equation(left, value.type); - } else { - yield equation(left, value.value.identifier.type); - } + yield equation(left, value.value.identifier.type); break; } @@ -473,41 +455,6 @@ function* generateInstructionTypes( } } } - if (env.config.enableInferEventHandlers) { - if ( - value.kind === 'JsxExpression' && - value.tag.kind === 'BuiltinTag' && - !value.tag.name.includes('-') - ) { - /* - * Infer event handler types for built-in DOM elements. - * Props starting with "on" (e.g., onClick, onSubmit) on primitive tags - * are inferred as event handlers. This allows functions with ref access - * to be passed to these props, since DOM event handlers are guaranteed - * by React to only execute in response to events, never during render. - * - * We exclude tags with hyphens to avoid web components (custom elements), - * which are required by the HTML spec to contain a hyphen. Web components - * may call event handler props during their lifecycle methods (e.g., - * connectedCallback), which would be unsafe for ref access. - */ - for (const prop of value.props) { - if ( - prop.kind === 'JsxAttribute' && - prop.name.startsWith('on') && - prop.name.length > 2 && - prop.name[2] === prop.name[2].toUpperCase() - ) { - yield equation(prop.place.identifier.type, { - kind: 'Function', - shapeId: BuiltInEventHandlerId, - return: makeType(), - isConstructor: false, - }); - } - } - } - } yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId}); break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index 68f73c7f9972..ac5f40e00d9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -39,14 +39,6 @@ function tryParseTestPragmaValue(val: string): Result { const testComplexConfigDefaults: PartialEnvironmentConfig = { validateNoCapitalizedCalls: [], - enableChangeDetectionForDebugging: { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }, - enableEmitFreeze: { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }, enableEmitInstrumentForget: { fn: { source: 'react-compiler-runtime', @@ -62,37 +54,6 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { source: 'react-compiler-runtime', importSpecifierName: '$dispatcherGuard', }, - inlineJsxTransform: { - elementSymbol: 'react.transitional.element', - globalDevVar: 'DEV', - }, - lowerContextAccess: { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }, - inferEffectDependencies: [ - { - function: { - source: 'react', - importSpecifierName: 'useEffect', - }, - autodepsIndex: 1, - }, - { - function: { - source: 'shared-runtime', - importSpecifierName: 'useSpecialEffect', - }, - autodepsIndex: 2, - }, - { - function: { - source: 'useEffectWrapper', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], }; function* splitPragma( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts index bfde19991e9f..54aacb45c9ec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts @@ -29,6 +29,9 @@ import { isStableType, isSubPath, isSubPathIgnoringOptionals, + isUseEffectHookType, + isUseInsertionEffectHookType, + isUseLayoutEffectHookType, isUseRefType, LoadGlobal, ManualMemoDependency, @@ -43,7 +46,6 @@ import { } from '../HIR/visitors'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; -import {isEffectHook} from './ValidateMemoizedEffectDependencies'; const DEBUG = false; @@ -1111,3 +1113,11 @@ function createDiagnostic( suggestions: suggestion != null ? [suggestion] : null, }); } + +export function isEffectHook(identifier: Identifier): boolean { + return ( + isUseEffectHookType(identifier) || + isUseLayoutEffectHookType(identifier) || + isUseInsertionEffectHookType(identifier) + ); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts deleted file mode 100644 index 15d0be57fcf7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '..'; -import {ErrorCategory} from '../CompilerError'; -import { - Identifier, - Instruction, - ReactiveFunction, - ReactiveInstruction, - ReactiveScopeBlock, - ScopeId, - isUseEffectHookType, - isUseInsertionEffectHookType, - isUseLayoutEffectHookType, -} from '../HIR'; -import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables'; -import { - ReactiveFunctionVisitor, - visitReactiveFunction, -} from '../ReactiveScopes/visitors'; -import {Result} from '../Utils/Result'; - -/** - * Validates that all known effect dependencies are memoized. The algorithm checks two things: - * - Disallow effect dependencies that should be memoized (have a reactive scope assigned) but - * where that reactive scope does not exist. This checks for cases where a reactive scope was - * pruned for some reason, such as spanning a hook. - * - Disallow effect dependencies whose a mutable range that encompasses the effect call. - * - * This latter check corresponds to any values which Forget knows may be mutable and may be mutated - * after the effect. Note that it's possible Forget may miss not memoize a value for some other reason, - * but in general this is a bug. The only reason Forget would _choose_ to skip memoization of an - * effect dependency is because it's mutated later. - * - * Example: - * - * ```javascript - * const object = {}; // mutable range starts here... - * - * useEffect(() => { - * console.log('hello'); - * }, [object]); // the dependency array picks up the mutable range of its mutable contents - * - * mutate(object); // ... mutable range ends here after this mutation - * ``` - */ -export function validateMemoizedEffectDependencies( - fn: ReactiveFunction, -): Result { - const errors = new CompilerError(); - visitReactiveFunction(fn, new Visitor(), errors); - return errors.asResult(); -} - -class Visitor extends ReactiveFunctionVisitor { - scopes: Set = new Set(); - - override visitScope( - scopeBlock: ReactiveScopeBlock, - state: CompilerError, - ): void { - this.traverseScope(scopeBlock, state); - - /* - * Record scopes that exist in the AST so we can later check to see if - * effect dependencies which should be memoized (have a scope assigned) - * actually are memoized (that scope exists). - * However, we only record scopes if *their* dependencies are also - * memoized, allowing a transitive memoization check. - */ - let areDependenciesMemoized = true; - for (const dep of scopeBlock.scope.dependencies) { - if (isUnmemoized(dep.identifier, this.scopes)) { - areDependenciesMemoized = false; - break; - } - } - if (areDependenciesMemoized) { - this.scopes.add(scopeBlock.scope.id); - for (const id of scopeBlock.scope.merged) { - this.scopes.add(id); - } - } - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: CompilerError, - ): void { - this.traverseInstruction(instruction, state); - if ( - instruction.value.kind === 'CallExpression' && - isEffectHook(instruction.value.callee.identifier) && - instruction.value.args.length >= 2 - ) { - const deps = instruction.value.args[1]!; - if ( - deps.kind === 'Identifier' && - /* - * TODO: isMutable is not safe to call here as it relies on identifier mutableRange which is no longer valid at this point - * in the pipeline - */ - (isMutable(instruction as Instruction, deps) || - isUnmemoized(deps.identifier, this.scopes)) - ) { - state.push({ - category: ErrorCategory.EffectDependencies, - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, - }); - } - } - } -} - -function isUnmemoized(operand: Identifier, scopes: Set): boolean { - return operand.scope != null && !scopes.has(operand.scope.id); -} - -export function isEffectHook(identifier: Identifier): boolean { - return ( - isUseEffectHookType(identifier) || - isUseLayoutEffectHookType(identifier) || - isUseInsertionEffectHookType(identifier) - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 0d0242d25c9d..db8e454f4c1d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -19,16 +19,8 @@ export function validateNoCapitalizedCalls( ...DEFAULT_GLOBALS.keys(), ...(envConfig.validateNoCapitalizedCalls ?? []), ]); - /* - * The hook pattern may allow uppercase names, like React$useState, so we need to be sure that we - * do not error in those cases - */ - const hookPattern = - envConfig.hookPattern != null ? new RegExp(envConfig.hookPattern) : null; const isAllowed = (name: string): boolean => { - return ( - ALLOW_LIST.has(name) || (hookPattern != null && hookPattern.test(name)) - ); + return ALLOW_LIST.has(name); }; const errors = new CompilerError(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index dd7b04a11d69..97ac4b31d291 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -15,14 +15,12 @@ import { GeneratedSource, HIRFunction, IdentifierId, - Identifier, Place, SourceLocation, getHookKindForType, isRefValueType, isUseRefType, } from '../HIR'; -import {BuiltInEventHandlerId} from '../HIR/ObjectShape'; import { eachInstructionOperand, eachInstructionValueOperand, @@ -178,11 +176,6 @@ function refTypeOfType(place: Place): RefAccessType { } } -function isEventHandlerType(identifier: Identifier): boolean { - const type = identifier.type; - return type.kind === 'Function' && type.shapeId === BuiltInEventHandlerId; -} - function tyEqual(a: RefAccessType, b: RefAccessType): boolean { if (a.kind !== b.kind) { return false; @@ -491,9 +484,6 @@ function validateNoRefAccessInRenderImpl( */ if (!didError) { const isRefLValue = isUseRefType(instr.lvalue.identifier); - const isEventHandlerLValue = isEventHandlerType( - instr.lvalue.identifier, - ); for (const operand of eachInstructionValueOperand(instr.value)) { /** * By default we check that function call operands are not refs, @@ -501,7 +491,6 @@ function validateNoRefAccessInRenderImpl( */ if ( isRefLValue || - isEventHandlerLValue || (hookKind != null && hookKind !== 'useState' && hookKind !== 'useReducer') @@ -509,8 +498,7 @@ function validateNoRefAccessInRenderImpl( /** * Allow passing refs or ref-accessing functions when: * 1. lvalue is a ref (mergeRefs pattern: `mergeRefs(ref1, ref2)`) - * 2. lvalue is an event handler (DOM events execute outside render) - * 3. calling hooks (independently validated for ref safety) + * 2. calling hooks (independently validated for ref safety) */ validateNoDirectRefValueAccess(errors, operand, env); } else if (interpolatedAsJsx.has(instr.lvalue.identifier.id)) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/index.ts index 44abf3241d35..3169bd63558a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/index.ts @@ -7,7 +7,6 @@ export {validateContextVariableLValues} from './ValidateContextVariableLValues'; export {validateHooksUsage} from './ValidateHooksUsage'; -export {validateMemoizedEffectDependencies} from './ValidateMemoizedEffectDependencies'; export {validateNoCapitalizedCalls} from './ValidateNoCapitalizedCalls'; export {validateNoRefAccessInRender} from './ValidateNoRefAccessInRender'; export {validateNoSetStateInRender} from './ValidateNoSetStateInRender'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts index 933990b6bdd9..406b5cd3c422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts @@ -24,24 +24,6 @@ describe('parseConfigPragma()', () => { ); }); - it('effect autodeps config must have at least 1 required argument', () => { - expect(() => { - validateEnvironmentConfig({ - inferEffectDependencies: [ - { - function: { - source: 'react', - importSpecifierName: 'useEffect', - }, - autodepsIndex: 0, - }, - ], - } as any); - }).toThrowErrorMatchingInlineSnapshot( - `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: AutodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`, - ); - }); - it('can parse stringy enums', () => { const stringyHook = { effectKind: 'freeze', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-async-event-handler-wrapper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-async-event-handler-wrapper.expect.md deleted file mode 100644 index 5cee9a68ceba..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-async-event-handler-wrapper.expect.md +++ /dev/null @@ -1,148 +0,0 @@ - -## Input - -```javascript -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates react-hook-form's handleSubmit -function handleSubmit(callback: (data: T) => void | Promise) { - return (event: any) => { - event.preventDefault(); - callback({} as T); - }; -} - -// Simulates an upload function -async function upload(file: any): Promise<{blob: {url: string}}> { - return {blob: {url: 'https://example.com/file.jpg'}}; -} - -interface SignatureRef { - toFile(): any; -} - -function Component() { - const ref = useRef(null); - - const onSubmit = async (value: any) => { - // This should be allowed: accessing ref.current in an async event handler - // that's wrapped and passed to onSubmit prop - let sigUrl: string; - if (value.hasSignature) { - const {blob} = await upload(ref.current?.toFile()); - sigUrl = blob?.url || ''; - } else { - sigUrl = value.signature; - } - console.log('Signature URL:', sigUrl); - }; - - return ( -
- - -
- ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableInferEventHandlers -import { useRef } from "react"; - -// Simulates react-hook-form's handleSubmit -function handleSubmit(callback) { - const $ = _c(2); - let t0; - if ($[0] !== callback) { - t0 = (event) => { - event.preventDefault(); - callback({} as T); - }; - $[0] = callback; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} - -// Simulates an upload function -async function upload(file) { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = { blob: { url: "https://example.com/file.jpg" } }; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -interface SignatureRef { - toFile(): any; -} - -function Component() { - const $ = _c(4); - const ref = useRef(null); - - const onSubmit = async (value) => { - let sigUrl; - if (value.hasSignature) { - const { blob } = await upload(ref.current?.toFile()); - sigUrl = blob?.url || ""; - } else { - sigUrl = value.signature; - } - - console.log("Signature URL:", sigUrl); - }; - - const t0 = handleSubmit(onSubmit); - let t1; - let t2; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; - t2 = ; - $[0] = t1; - $[1] = t2; - } else { - t1 = $[0]; - t2 = $[1]; - } - let t3; - if ($[2] !== t0) { - t3 = ( -
- {t1} - {t2} -
- ); - $[2] = t0; - $[3] = t3; - } else { - t3 = $[3]; - } - return t3; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - -### Eval output -(kind: ok)
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-async-event-handler-wrapper.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-async-event-handler-wrapper.tsx deleted file mode 100644 index be6f6656e18f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-async-event-handler-wrapper.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates react-hook-form's handleSubmit -function handleSubmit(callback: (data: T) => void | Promise) { - return (event: any) => { - event.preventDefault(); - callback({} as T); - }; -} - -// Simulates an upload function -async function upload(file: any): Promise<{blob: {url: string}}> { - return {blob: {url: 'https://example.com/file.jpg'}}; -} - -interface SignatureRef { - toFile(): any; -} - -function Component() { - const ref = useRef(null); - - const onSubmit = async (value: any) => { - // This should be allowed: accessing ref.current in an async event handler - // that's wrapped and passed to onSubmit prop - let sigUrl: string; - if (value.hasSignature) { - const {blob} = await upload(ref.current?.toFile()); - sigUrl = blob?.url || ''; - } else { - sigUrl = value.signature; - } - console.log('Signature URL:', sigUrl); - }; - - return ( -
- - -
- ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-event-handler-wrapper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-event-handler-wrapper.expect.md deleted file mode 100644 index 5a2727965879..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-event-handler-wrapper.expect.md +++ /dev/null @@ -1,100 +0,0 @@ - -## Input - -```javascript -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates react-hook-form's handleSubmit or similar event handler wrappers -function handleSubmit(callback: (data: T) => void) { - return (event: any) => { - event.preventDefault(); - callback({} as T); - }; -} - -function Component() { - const ref = useRef(null); - - const onSubmit = (data: any) => { - // This should be allowed: accessing ref.current in an event handler - // that's wrapped by handleSubmit and passed to onSubmit prop - if (ref.current !== null) { - console.log(ref.current.value); - } - }; - - return ( - <> - -
- -
- - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableInferEventHandlers -import { useRef } from "react"; - -// Simulates react-hook-form's handleSubmit or similar event handler wrappers -function handleSubmit(callback) { - const $ = _c(2); - let t0; - if ($[0] !== callback) { - t0 = (event) => { - event.preventDefault(); - callback({} as T); - }; - $[0] = callback; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} - -function Component() { - const $ = _c(1); - const ref = useRef(null); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onSubmit = (data) => { - if (ref.current !== null) { - console.log(ref.current.value); - } - }; - t0 = ( - <> - -
- -
- - ); - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - -### Eval output -(kind: ok)
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-event-handler-wrapper.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-event-handler-wrapper.tsx deleted file mode 100644 index f305a1f9ac6e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-event-handler-wrapper.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates react-hook-form's handleSubmit or similar event handler wrappers -function handleSubmit(callback: (data: T) => void) { - return (event: any) => { - event.preventDefault(); - callback({} as T); - }; -} - -function Component() { - const ref = useRef(null); - - const onSubmit = (data: any) => { - // This should be allowed: accessing ref.current in an event handler - // that's wrapped by handleSubmit and passed to onSubmit prop - if (ref.current !== null) { - console.log(ref.current.value); - } - }; - - return ( - <> - -
- -
- - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capitalized-function-allowlist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capitalized-function-allowlist.expect.md deleted file mode 100644 index 1d95318e5c08..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capitalized-function-allowlist.expect.md +++ /dev/null @@ -1,53 +0,0 @@ - -## Input - -```javascript -// @validateNoCapitalizedCalls @hookPattern:".*\b(use[^$]+)$" -import * as React from 'react'; -const React$useState = React.useState; -const THIS_IS_A_CONSTANT = () => {}; -function Component() { - const b = Boolean(true); // OK - const n = Number(3); // OK - const s = String('foo'); // OK - const [state, setState] = React$useState(0); // OK - const [state2, setState2] = React.useState(1); // OK - const constant = THIS_IS_A_CONSTANT(); // OK - return 3; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [], - isComponent: true, -}; - -``` - -## Code - -```javascript -// @validateNoCapitalizedCalls @hookPattern:".*\b(use[^$]+)$" -import * as React from "react"; -const React$useState = React.useState; -const THIS_IS_A_CONSTANT = () => {}; -function Component() { - Boolean(true); - Number(3); - String("foo"); - React$useState(0); - React.useState(1); - THIS_IS_A_CONSTANT(); - return 3; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [], - isComponent: true, -}; - -``` - -### Eval output -(kind: ok) 3 \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capitalized-function-allowlist.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capitalized-function-allowlist.js deleted file mode 100644 index 59fdf7c5d07f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capitalized-function-allowlist.js +++ /dev/null @@ -1,19 +0,0 @@ -// @validateNoCapitalizedCalls @hookPattern:".*\b(use[^$]+)$" -import * as React from 'react'; -const React$useState = React.useState; -const THIS_IS_A_CONSTANT = () => {}; -function Component() { - const b = Boolean(true); // OK - const n = Number(3); // OK - const s = String('foo'); // OK - const [state, setState] = React$useState(0); // OK - const [state2, setState2] = React.useState(1); // OK - const constant = THIS_IS_A_CONSTANT(); // OK - return 3; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [], - isComponent: true, -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md deleted file mode 100644 index cc2e8ee27b25..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @enableChangeDetectionForDebugging -function Component(props) { - let x = null; - if (props.cond) { - x = []; - x.push(props.value); - } - return x; -} - -``` - -## Code - -```javascript -import { $structuralCheck } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableChangeDetectionForDebugging -function Component(props) { - const $ = _c(2); - let x = null; - if (props.cond) { - { - x = []; - x.push(props.value); - let condition = $[0] !== props.value; - if (!condition) { - let old$x = $[1]; - $structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)"); - } - $[0] = props.value; - $[1] = x; - if (condition) { - x = []; - x.push(props.value); - $structuralCheck($[1], x, "x", "Component", "recomputed", "(3:6)"); - x = $[1]; - } - } - } - - return x; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js deleted file mode 100644 index 8ccc3d30f091..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/change-detect-reassign.js +++ /dev/null @@ -1,9 +0,0 @@ -// @enableChangeDetectionForDebugging -function Component(props) { - let x = null; - if (props.cond) { - x = []; - x.push(props.value); - } - return x; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md deleted file mode 100644 index 6ec8520f9ec9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// @enableEmitFreeze @enableEmitInstrumentForget - -function useFoo(props) { - return foo(props.x); -} - -``` - -## Code - -```javascript -import { - makeReadOnly, - shouldInstrument, - useRenderCounter, -} from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget - -function useFoo(props) { - if (DEV && shouldInstrument) - useRenderCounter("useFoo", "/codegen-emit-imports-same-source.ts"); - const $ = _c(2); - let t0; - if ($[0] !== props.x) { - t0 = foo(props.x); - $[0] = props.x; - $[1] = __DEV__ ? makeReadOnly(t0, "useFoo") : t0; - } else { - t0 = $[1]; - } - return t0; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js deleted file mode 100644 index bd66353319d6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js +++ /dev/null @@ -1,5 +0,0 @@ -// @enableEmitFreeze @enableEmitInstrumentForget - -function useFoo(props) { - return foo(props.x); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.expect.md deleted file mode 100644 index 602e49672e26..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.expect.md +++ /dev/null @@ -1,44 +0,0 @@ - -## Input - -```javascript -// @enableEmitFreeze true - -function MyComponentName(props) { - let x = {}; - foo(x, props.a); - foo(x, props.b); - - let y = []; - y.push(x); - return y; -} - -``` - -## Code - -```javascript -import { makeReadOnly } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze true - -function MyComponentName(props) { - const $ = _c(3); - let y; - if ($[0] !== props.a || $[1] !== props.b) { - const x = {}; - foo(x, props.a); - foo(x, props.b); - y = []; - y.push(x); - $[0] = props.a; - $[1] = props.b; - $[2] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y; - } else { - y = $[2]; - } - return y; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.js deleted file mode 100644 index 8ad3e859e6ea..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.js +++ /dev/null @@ -1,11 +0,0 @@ -// @enableEmitFreeze true - -function MyComponentName(props) { - let x = {}; - foo(x, props.a); - foo(x, props.b); - - let y = []; - y.push(x); - return y; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md deleted file mode 100644 index 9b30c1b8cbbd..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @enableEmitFreeze @instrumentForget - -let makeReadOnly = 'conflicting identifier'; -function useFoo(props) { - return foo(props.x); -} - -``` - -## Code - -```javascript -import { makeReadOnly as _makeReadOnly } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget - -let makeReadOnly = "conflicting identifier"; -function useFoo(props) { - const $ = _c(2); - let t0; - if ($[0] !== props.x) { - t0 = foo(props.x); - $[0] = props.x; - $[1] = __DEV__ ? _makeReadOnly(t0, "useFoo") : t0; - } else { - t0 = $[1]; - } - return t0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.js deleted file mode 100644 index b30c56eb5537..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.js +++ /dev/null @@ -1,6 +0,0 @@ -// @enableEmitFreeze @instrumentForget - -let makeReadOnly = 'conflicting identifier'; -function useFoo(props) { - return foo(props.x); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md deleted file mode 100644 index ee18d3d1a2d1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -// @enableEmitFreeze @instrumentForget -function useFoo(props) { - return foo(props.x, __DEV__); -} - -``` - -## Code - -```javascript -import { makeReadOnly } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget -function useFoo(props) { - const $ = _c(2); - let t0; - if ($[0] !== props.x) { - t0 = foo(props.x, __DEV__); - $[0] = props.x; - $[1] = __DEV__ ? makeReadOnly(t0, "useFoo") : t0; - } else { - t0 = $[1]; - } - return t0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js deleted file mode 100644 index 62c313b67d33..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js +++ /dev/null @@ -1,4 +0,0 @@ -// @enableEmitFreeze @instrumentForget -function useFoo(props) { - return foo(props.x, __DEV__); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md deleted file mode 100644 index d8436fa2c046..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// @enableEmitFreeze @instrumentForget -function useFoo(props) { - const __DEV__ = 'conflicting global'; - console.log(__DEV__); - return foo(props.x); -} - -``` - - -## Error - -``` -Found 1 error: - -Todo: Encountered conflicting global in generated program - -Conflict from local binding __DEV__. - -error.emit-freeze-conflicting-global.ts:3:8 - 1 | // @enableEmitFreeze @instrumentForget - 2 | function useFoo(props) { -> 3 | const __DEV__ = 'conflicting global'; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Encountered conflicting global in generated program - 4 | console.log(__DEV__); - 5 | return foo(props.x); - 6 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js deleted file mode 100644 index 4391ad76e70a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js +++ /dev/null @@ -1,6 +0,0 @@ -// @enableEmitFreeze @instrumentForget -function useFoo(props) { - const __DEV__ = 'conflicting global'; - console.log(__DEV__); - return foo(props.x); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.expect.md deleted file mode 100644 index 1e13064b7220..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.expect.md +++ /dev/null @@ -1,44 +0,0 @@ - -## Input - -```javascript -// @validateMemoizedEffectDependencies -function Component(props) { - // Items cannot be memoized bc its mutation spans a hook call - const items = [props.value]; - const [state, _setState] = useState(null); - mutate(items); - - // Items is no longer mutable here, but it hasn't been memoized - useEffect(() => { - console.log(items); - }, [items]); - - return [items, state]; -} - -``` - - -## Error - -``` -Found 1 error: - -Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - -error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.ts:9:2 - 7 | - 8 | // Items is no longer mutable here, but it hasn't been memoized -> 9 | useEffect(() => { - | ^^^^^^^^^^^^^^^^^ -> 10 | console.log(items); - | ^^^^^^^^^^^^^^^^^^^^^^^ -> 11 | }, [items]); - | ^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - 12 | - 13 | return [items, state]; - 14 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.js deleted file mode 100644 index 20a0d9b606f2..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.js +++ /dev/null @@ -1,14 +0,0 @@ -// @validateMemoizedEffectDependencies -function Component(props) { - // Items cannot be memoized bc its mutation spans a hook call - const items = [props.value]; - const [state, _setState] = useState(null); - mutate(items); - - // Items is no longer mutable here, but it hasn't been memoized - useEffect(() => { - console.log(items); - }, [items]); - - return [items, state]; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized.expect.md deleted file mode 100644 index 02712424842f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @validateMemoizedEffectDependencies -import {useEffect} from 'react'; - -function Component(props) { - const data = {}; - useEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} - -``` - - -## Error - -``` -Found 1 error: - -Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - -error.invalid-useEffect-dep-not-memoized.ts:6:2 - 4 | function Component(props) { - 5 | const data = {}; -> 6 | useEffect(() => { - | ^^^^^^^^^^^^^^^^^ -> 7 | console.log(props.value); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 8 | }, [data]); - | ^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - 9 | mutate(data); - 10 | return data; - 11 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized.js deleted file mode 100644 index fee0f630a073..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useEffect-dep-not-memoized.js +++ /dev/null @@ -1,11 +0,0 @@ -// @validateMemoizedEffectDependencies -import {useEffect} from 'react'; - -function Component(props) { - const data = {}; - useEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useInsertionEffect-dep-not-memoized.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useInsertionEffect-dep-not-memoized.expect.md deleted file mode 100644 index 9f98a7a2273f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useInsertionEffect-dep-not-memoized.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @validateMemoizedEffectDependencies -import {useInsertionEffect} from 'react'; - -function Component(props) { - const data = {}; - useInsertionEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} - -``` - - -## Error - -``` -Found 1 error: - -Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - -error.invalid-useInsertionEffect-dep-not-memoized.ts:6:2 - 4 | function Component(props) { - 5 | const data = {}; -> 6 | useInsertionEffect(() => { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 7 | console.log(props.value); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 8 | }, [data]); - | ^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - 9 | mutate(data); - 10 | return data; - 11 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useInsertionEffect-dep-not-memoized.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useInsertionEffect-dep-not-memoized.js deleted file mode 100644 index 7d1ed16544b0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useInsertionEffect-dep-not-memoized.js +++ /dev/null @@ -1,11 +0,0 @@ -// @validateMemoizedEffectDependencies -import {useInsertionEffect} from 'react'; - -function Component(props) { - const data = {}; - useInsertionEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useLayoutEffect-dep-not-memoized.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useLayoutEffect-dep-not-memoized.expect.md deleted file mode 100644 index 55ba0876d761..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useLayoutEffect-dep-not-memoized.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @validateMemoizedEffectDependencies -import {useLayoutEffect} from 'react'; - -function Component(props) { - const data = {}; - useLayoutEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} - -``` - - -## Error - -``` -Found 1 error: - -Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - -error.invalid-useLayoutEffect-dep-not-memoized.ts:6:2 - 4 | function Component(props) { - 5 | const data = {}; -> 6 | useLayoutEffect(() => { - | ^^^^^^^^^^^^^^^^^^^^^^^ -> 7 | console.log(props.value); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -> 8 | }, [data]); - | ^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - 9 | mutate(data); - 10 | return data; - 11 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useLayoutEffect-dep-not-memoized.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useLayoutEffect-dep-not-memoized.js deleted file mode 100644 index 3925334795ee..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useLayoutEffect-dep-not-memoized.js +++ /dev/null @@ -1,11 +0,0 @@ -// @validateMemoizedEffectDependencies -import {useLayoutEffect} from 'react'; - -function Component(props) { - const data = {}; - useLayoutEffect(() => { - console.log(props.value); - }, [data]); - mutate(data); - return data; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-component-in-normal-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-component-in-normal-function.expect.md deleted file mode 100644 index 69ce796faed3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-component-in-normal-function.expect.md +++ /dev/null @@ -1,54 +0,0 @@ - -## Input - -```javascript -// @validateNoDynamicallyCreatedComponentsOrHooks -export function getInput(a) { - const Wrapper = () => { - const handleChange = () => { - a.onChange(); - }; - - return ; - }; - - return Wrapper; -} - -export const FIXTURE_ENTRYPOINT = { - fn: getInput, - isComponent: false, - params: [{onChange() {}}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Components and hooks cannot be created dynamically - -The function `Wrapper` appears to be a React component, but it's defined inside `getInput`. Components and Hooks should always be declared at module scope. - -error.nested-component-in-normal-function.ts:2:16 - 1 | // @validateNoDynamicallyCreatedComponentsOrHooks -> 2 | export function getInput(a) { - | ^^^^^^^^ this function dynamically created a component/hook - 3 | const Wrapper = () => { - 4 | const handleChange = () => { - 5 | a.onChange(); - -error.nested-component-in-normal-function.ts:3:8 - 1 | // @validateNoDynamicallyCreatedComponentsOrHooks - 2 | export function getInput(a) { -> 3 | const Wrapper = () => { - | ^^^^^^^ the component is created here - 4 | const handleChange = () => { - 5 | a.onChange(); - 6 | }; -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-component-in-normal-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-component-in-normal-function.js deleted file mode 100644 index 36be05e3ee8b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-component-in-normal-function.js +++ /dev/null @@ -1,18 +0,0 @@ -// @validateNoDynamicallyCreatedComponentsOrHooks -export function getInput(a) { - const Wrapper = () => { - const handleChange = () => { - a.onChange(); - }; - - return ; - }; - - return Wrapper; -} - -export const FIXTURE_ENTRYPOINT = { - fn: getInput, - isComponent: false, - params: [{onChange() {}}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-hook-in-normal-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-hook-in-normal-function.expect.md deleted file mode 100644 index 652fc2feb009..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-hook-in-normal-function.expect.md +++ /dev/null @@ -1,59 +0,0 @@ - -## Input - -```javascript -// @validateNoDynamicallyCreatedComponentsOrHooks -import {useState} from 'react'; - -function createCustomHook(config) { - function useConfiguredState() { - const [state, setState] = useState(0); - - const increment = () => { - setState(state + config.step); - }; - - return [state, increment]; - } - - return useConfiguredState; -} - -export const FIXTURE_ENTRYPOINT = { - fn: createCustomHook, - isComponent: false, - params: [{step: 1}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Components and hooks cannot be created dynamically - -The function `useConfiguredState` appears to be a React hook, but it's defined inside `createCustomHook`. Components and Hooks should always be declared at module scope. - -error.nested-hook-in-normal-function.ts:4:9 - 2 | import {useState} from 'react'; - 3 | -> 4 | function createCustomHook(config) { - | ^^^^^^^^^^^^^^^^ this function dynamically created a component/hook - 5 | function useConfiguredState() { - 6 | const [state, setState] = useState(0); - 7 | - -error.nested-hook-in-normal-function.ts:5:11 - 3 | - 4 | function createCustomHook(config) { -> 5 | function useConfiguredState() { - | ^^^^^^^^^^^^^^^^^^ the component is created here - 6 | const [state, setState] = useState(0); - 7 | - 8 | const increment = () => { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-hook-in-normal-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-hook-in-normal-function.js deleted file mode 100644 index 306e78f7b1c6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nested-hook-in-normal-function.js +++ /dev/null @@ -1,22 +0,0 @@ -// @validateNoDynamicallyCreatedComponentsOrHooks -import {useState} from 'react'; - -function createCustomHook(config) { - function useConfiguredState() { - const [state, setState] = useState(0); - - const increment = () => { - setState(state + config.step); - }; - - return [state, increment]; - } - - return useConfiguredState; -} - -export const FIXTURE_ENTRYPOINT = { - fn: createCustomHook, - isComponent: false, - params: [{step: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md deleted file mode 100644 index c0c369e11d1c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.expect.md +++ /dev/null @@ -1,19 +0,0 @@ - -## Input - -```javascript -// @disableMemoizationForDebugging @enableChangeDetectionForDebugging -function Component(props) {} - -``` - - -## Error - -``` -Found 1 error: - -Error: Invalid environment config: the 'disableMemoizationForDebugging' and 'enableChangeDetectionForDebugging' options cannot be used together -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js deleted file mode 100644 index ce93cd29f1af..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.nomemo-and-change-detect.js +++ /dev/null @@ -1,2 +0,0 @@ -// @disableMemoizationForDebugging @enableChangeDetectionForDebugging -function Component(props) {} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-custom-component-event-handler-wrapper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-custom-component-event-handler-wrapper.expect.md deleted file mode 100644 index 2a3657eda34f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-custom-component-event-handler-wrapper.expect.md +++ /dev/null @@ -1,69 +0,0 @@ - -## Input - -```javascript -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates a custom component wrapper -function CustomForm({onSubmit, children}: any) { - return
{children}
; -} - -// Simulates react-hook-form's handleSubmit -function handleSubmit(callback: (data: T) => void) { - return (event: any) => { - event.preventDefault(); - callback({} as T); - }; -} - -function Component() { - const ref = useRef(null); - - const onSubmit = (data: any) => { - // This should error: passing function with ref access to custom component - // event handler, even though it would be safe on a native
- if (ref.current !== null) { - console.log(ref.current.value); - } - }; - - return ( - <> - - - - - - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot access refs during render - -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). - -error.ref-value-in-custom-component-event-handler-wrapper.ts:31:41 - 29 | <> - 30 | -> 31 | - | ^^^^^^^^ Passing a ref to a function may read its value during render - 32 | - 33 | - 34 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-custom-component-event-handler-wrapper.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-custom-component-event-handler-wrapper.tsx deleted file mode 100644 index b90a12171654..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-custom-component-event-handler-wrapper.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates a custom component wrapper -function CustomForm({onSubmit, children}: any) { - return {children}
; -} - -// Simulates react-hook-form's handleSubmit -function handleSubmit(callback: (data: T) => void) { - return (event: any) => { - event.preventDefault(); - callback({} as T); - }; -} - -function Component() { - const ref = useRef(null); - - const onSubmit = (data: any) => { - // This should error: passing function with ref access to custom component - // event handler, even though it would be safe on a native
- if (ref.current !== null) { - console.log(ref.current.value); - } - }; - - return ( - <> - - - - - - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-event-handler-wrapper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-event-handler-wrapper.expect.md deleted file mode 100644 index 718e2c81419f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-event-handler-wrapper.expect.md +++ /dev/null @@ -1,55 +0,0 @@ - -## Input - -```javascript -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates a handler wrapper -function handleClick(value: any) { - return () => { - console.log(value); - }; -} - -function Component() { - const ref = useRef(null); - - // This should still error: passing ref.current directly to a wrapper - // The ref value is accessed during render, not in the event handler - return ( - <> - - - - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot access refs during render - -React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). - -error.ref-value-in-event-handler-wrapper.ts:19:35 - 17 | <> - 18 | -> 19 | - | ^^^^^^^^^^^ Cannot access ref value during render - 20 | - 21 | ); - 22 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-event-handler-wrapper.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-event-handler-wrapper.tsx deleted file mode 100644 index 58313e560ce7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-value-in-event-handler-wrapper.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// @enableInferEventHandlers -import {useRef} from 'react'; - -// Simulates a handler wrapper -function handleClick(value: any) { - return () => { - console.log(value); - }; -} - -function Component() { - const ref = useRef(null); - - // This should still error: passing ref.current directly to a wrapper - // The ref value is accessed during render, not in the event handler - return ( - <> - - - - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md index 45e3d365a8d3..10af9368c594 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js index 12f8ebf3ce40..ec7c5811d69a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js @@ -1,4 +1,4 @@ -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-memoized-effect-deps-invalidated-dep-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-memoized-effect-deps-invalidated-dep-value.expect.md deleted file mode 100644 index 7fdadfbc896b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-memoized-effect-deps-invalidated-dep-value.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @validateMemoizedEffectDependencies -import {useHook} from 'shared-runtime'; - -function Component(props) { - const x = []; - useHook(); // intersperse a hook call to prevent memoization of x - x.push(props.value); - - const y = [x]; - - useEffect(() => { - console.log(y); - }, [y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{value: 'sathya'}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - -error.validate-memoized-effect-deps-invalidated-dep-value.ts:11:2 - 9 | const y = [x]; - 10 | -> 11 | useEffect(() => { - | ^^^^^^^^^^^^^^^^^ -> 12 | console.log(y); - | ^^^^^^^^^^^^^^^^^^^ -> 13 | }, [y]); - | ^^^^^^^^^^ React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior - 14 | } - 15 | - 16 | export const FIXTURE_ENTRYPOINT = { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-memoized-effect-deps-invalidated-dep-value.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-memoized-effect-deps-invalidated-dep-value.js deleted file mode 100644 index e0944d35e635..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-memoized-effect-deps-invalidated-dep-value.js +++ /dev/null @@ -1,19 +0,0 @@ -// @validateMemoizedEffectDependencies -import {useHook} from 'shared-runtime'; - -function Component(props) { - const x = []; - useHook(); // intersperse a hook call to prevent memoization of x - x.push(props.value); - - const y = [x]; - - useEffect(() => { - console.log(y); - }, [y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{value: 'sathya'}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md deleted file mode 100644 index 5614560c6c4d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ /dev/null @@ -1,67 +0,0 @@ - -## Input - -```javascript -// @enableTreatFunctionDepsAsConditional -import {Stringify} from 'shared-runtime'; - -function Component({props}) { - const f = () => props.a.b; - - return {} : f} />; -} -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{props: null}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional -import { Stringify } from "shared-runtime"; - -function Component(t0) { - const $ = _c(7); - const { props } = t0; - let t1; - if ($[0] !== props) { - t1 = () => props.a.b; - $[0] = props; - $[1] = t1; - } else { - t1 = $[1]; - } - const f = t1; - let t2; - if ($[2] !== f || $[3] !== props) { - t2 = props == null ? _temp : f; - $[2] = f; - $[3] = props; - $[4] = t2; - } else { - t2 = $[4]; - } - let t3; - if ($[5] !== t2) { - t3 = ; - $[5] = t2; - $[6] = t3; - } else { - t3 = $[6]; - } - return t3; -} -function _temp() {} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ props: null }], -}; - -``` - -### Eval output -(kind: ok)
{"f":"[[ function params=0 ]]"}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx deleted file mode 100644 index ab3e00f9ba2b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ /dev/null @@ -1,12 +0,0 @@ -// @enableTreatFunctionDepsAsConditional -import {Stringify} from 'shared-runtime'; - -function Component({props}) { - const f = () => props.a.b; - - return {} : f} />; -} -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{props: null}], -}; diff --git "a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.expect.md" "b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.expect.md" deleted file mode 100644 index c7aa3e3b7544..000000000000 --- "a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.expect.md" +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @enableTreatFunctionDepsAsConditional -function Component(props) { - function getLength() { - return props.bar.length; - } - - return props.bar && getLength(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{bar: null}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional -function Component(props) { - const $ = _c(5); - let t0; - if ($[0] !== props.bar) { - t0 = function getLength() { - return props.bar.length; - }; - $[0] = props.bar; - $[1] = t0; - } else { - t0 = $[1]; - } - const getLength = t0; - let t1; - if ($[2] !== getLength || $[3] !== props.bar) { - t1 = props.bar && getLength(); - $[2] = getLength; - $[3] = props.bar; - $[4] = t1; - } else { - t1 = $[4]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ bar: null }], -}; - -``` - -### Eval output -(kind: ok) null \ No newline at end of file diff --git "a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.js" "b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.js" deleted file mode 100644 index 6e59fb947d15..000000000000 --- "a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr\342\200\223conditional-access.js" +++ /dev/null @@ -1,13 +0,0 @@ -// @enableTreatFunctionDepsAsConditional -function Component(props) { - function getLength() { - return props.bar.length; - } - - return props.bar && getLength(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{bar: null}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md deleted file mode 100644 index 05b7218de741..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.expect.md +++ /dev/null @@ -1,42 +0,0 @@ - -## Input - -```javascript -// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveVariable({propVal}) { - 'use memo if(invalid identifier)'; - const arr = [propVal]; - useEffect(() => print(arr), AUTODEPS); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveVariable, - params: [{}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.dynamic-gating-invalid-identifier-nopanic-required-feature.ts:8:2 - 6 | 'use memo if(invalid identifier)'; - 7 | const arr = [propVal]; -> 8 | useEffect(() => print(arr), AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 9 | } - 10 | - 11 | export const FIXTURE_ENTRYPOINT = { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js deleted file mode 100644 index c753bc263827..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/error.dynamic-gating-invalid-identifier-nopanic-required-feature.js +++ /dev/null @@ -1,14 +0,0 @@ -// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveVariable({propVal}) { - 'use memo if(invalid identifier)'; - const arr = [propVal]; - useEffect(() => print(arr), AUTODEPS); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveVariable, - params: [{}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md deleted file mode 100644 index 1170f6a60a81..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ /dev/null @@ -1,95 +0,0 @@ - -## Input - -```javascript -// @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false - -import * as React from 'react'; -import {makeArray, useHook} from 'shared-runtime'; - -const React$useState = React.useState; -const React$useMemo = React.useMemo; -const Internal$Reassigned$useHook = useHook; - -function Component() { - const [state, setState] = React$useState(0); - const object = Internal$Reassigned$useHook(); - const json = JSON.stringify(object); - const doubledArray = React$useMemo(() => { - return makeArray(state); - }, [state]); - return ( -
- {doubledArray.join('')} - {json} -
- ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false - -import * as React from "react"; -import { makeArray, useHook } from "shared-runtime"; - -const React$useState = React.useState; -const React$useMemo = React.useMemo; -const Internal$Reassigned$useHook = useHook; - -function Component() { - const $ = _c(7); - const [state] = React$useState(0); - const object = Internal$Reassigned$useHook(); - let t0; - if ($[0] !== object) { - t0 = JSON.stringify(object); - $[0] = object; - $[1] = t0; - } else { - t0 = $[1]; - } - const json = t0; - let t1; - if ($[2] !== state) { - const doubledArray = makeArray(state); - t1 = doubledArray.join(""); - $[2] = state; - $[3] = t1; - } else { - t1 = $[3]; - } - let t2; - if ($[4] !== json || $[5] !== t1) { - t2 = ( -
- {t1} - {json} -
- ); - $[4] = json; - $[5] = t1; - $[6] = t2; - } else { - t2 = $[6]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; - -``` - -### Eval output -(kind: ok)
0{"a":0,"b":"value1","c":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js deleted file mode 100644 index 4db8451bc855..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js +++ /dev/null @@ -1,28 +0,0 @@ -// @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false - -import * as React from 'react'; -import {makeArray, useHook} from 'shared-runtime'; - -const React$useState = React.useState; -const React$useMemo = React.useMemo; -const Internal$Reassigned$useHook = useHook; - -function Component() { - const [state, setState] = React$useState(0); - const object = Internal$Reassigned$useHook(); - const json = JSON.stringify(object); - const doubledArray = React$useMemo(() => { - return makeArray(state); - }, [state]); - return ( -
- {doubledArray.join('')} - {json} -
- ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md deleted file mode 100644 index c85f4d4468f9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" -import useMyEffect from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function nonReactFn(arg) { - useMyEffect(() => [1, 2, arg], AUTODEPS); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.callsite-in-non-react-fn-default-import.ts:6:2 - 4 | - 5 | function nonReactFn(arg) { -> 6 | useMyEffect(() => [1, 2, arg], AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 7 | } - 8 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js deleted file mode 100644 index adfe3ffadd59..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js +++ /dev/null @@ -1,7 +0,0 @@ -// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" -import useMyEffect from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function nonReactFn(arg) { - useMyEffect(() => [1, 2, arg], AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md deleted file mode 100644 index a372aeb6b290..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; - -function nonReactFn(arg) { - useEffect(() => [1, 2, arg], AUTODEPS); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.callsite-in-non-react-fn.ts:5:2 - 3 | - 4 | function nonReactFn(arg) { -> 5 | useEffect(() => [1, 2, arg], AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 6 | } - 7 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js deleted file mode 100644 index 9cbc47086b23..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js +++ /dev/null @@ -1,6 +0,0 @@ -// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; - -function nonReactFn(arg) { - useEffect(() => [1, 2, arg], AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md deleted file mode 100644 index ac56c25cb324..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md +++ /dev/null @@ -1,48 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; - -/** - * Error on non-inlined effect functions: - * 1. From the effect hook callee's perspective, it only makes sense - * to either - * (a) never hard error (i.e. failing to infer deps is acceptable) or - * (b) always hard error, - * regardless of whether the callback function is an inline fn. - * 2. (Technical detail) it's harder to support detecting cases in which - * function (pre-Forget transform) was inline but becomes memoized - */ -function Component({foo}) { - function f() { - console.log(foo); - } - - // No inferred dep array, the argument is not a lambda - useEffect(f, AUTODEPS); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.non-inlined-effect-fn.ts:20:2 - 18 | - 19 | // No inferred dep array, the argument is not a lambda -> 20 | useEffect(f, AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 21 | } - 22 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js deleted file mode 100644 index c113fe363c53..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js +++ /dev/null @@ -1,21 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; - -/** - * Error on non-inlined effect functions: - * 1. From the effect hook callee's perspective, it only makes sense - * to either - * (a) never hard error (i.e. failing to infer deps is acceptable) or - * (b) always hard error, - * regardless of whether the callback function is an inline fn. - * 2. (Technical detail) it's harder to support detecting cases in which - * function (pre-Forget transform) was inline but becomes memoized - */ -function Component({foo}) { - function f() { - console.log(foo); - } - - // No inferred dep array, the argument is not a lambda - useEffect(f, AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md deleted file mode 100644 index 251d7c76c900..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.expect.md +++ /dev/null @@ -1,50 +0,0 @@ - -## Input - -```javascript -// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" - -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -/** - * TODO: run the non-forget enabled version through the effect inference - * pipeline. - */ -function Component({foo}) { - 'use memo if(getTrue)'; - const arr = []; - useEffectWrapper(() => arr.push(foo), AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], - sequentialRenders: [{foo: 1}, {foo: 2}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.todo-dynamic-gating.ts:13:2 - 11 | 'use memo if(getTrue)'; - 12 | const arr = []; -> 13 | useEffectWrapper(() => arr.push(foo), AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 14 | arr.push(2); - 15 | return arr; - 16 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js deleted file mode 100644 index 667abfea6f26..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-dynamic-gating.js +++ /dev/null @@ -1,22 +0,0 @@ -// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none" - -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -/** - * TODO: run the non-forget enabled version through the effect inference - * pipeline. - */ -function Component({foo}) { - 'use memo if(getTrue)'; - const arr = []; - useEffectWrapper(() => arr.push(foo), AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], - sequentialRenders: [{foo: 1}, {foo: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md deleted file mode 100644 index 041a0f4e8bc8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.expect.md +++ /dev/null @@ -1,48 +0,0 @@ - -## Input - -```javascript -// @gating @inferEffectDependencies @panicThreshold:"none" -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -/** - * TODO: run the non-forget enabled version through the effect inference - * pipeline. - */ -function Component({foo}) { - const arr = []; - useEffectWrapper(() => arr.push(foo), AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], - sequentialRenders: [{foo: 1}, {foo: 2}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.todo-gating.ts:11:2 - 9 | function Component({foo}) { - 10 | const arr = []; -> 11 | useEffectWrapper(() => arr.push(foo), AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 12 | arr.push(2); - 13 | return arr; - 14 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js deleted file mode 100644 index 60bd9a362e30..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-gating.js +++ /dev/null @@ -1,20 +0,0 @@ -// @gating @inferEffectDependencies @panicThreshold:"none" -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -/** - * TODO: run the non-forget enabled version through the effect inference - * pipeline. - */ -function Component({foo}) { - const arr = []; - useEffectWrapper(() => arr.push(foo), AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], - sequentialRenders: [{foo: 1}, {foo: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md deleted file mode 100644 index 3c7fa047f8b5..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import React from 'react'; - -function NonReactiveDepInEffect() { - const obj = makeObject_Primitives(); - React.useEffect(() => print(obj), React.AUTODEPS); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.todo-import-default-property-useEffect.ts:6:2 - 4 | function NonReactiveDepInEffect() { - 5 | const obj = makeObject_Primitives(); -> 6 | React.useEffect(() => print(obj), React.AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 7 | } - 8 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js deleted file mode 100644 index c3044274cd25..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js +++ /dev/null @@ -1,7 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import React from 'react'; - -function NonReactiveDepInEffect() { - const obj = makeObject_Primitives(); - React.useEffect(() => print(obj), React.AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md deleted file mode 100644 index 00af7ec6ad59..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md +++ /dev/null @@ -1,60 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import {useSpecialEffect} from 'shared-runtime'; -import {AUTODEPS} from 'react'; - -/** - * Note that a react compiler-based transform still has limitations on JS syntax. - * We should surface these as actionable lint / build errors to devs. - */ -function Component({prop1}) { - 'use memo'; - useSpecialEffect( - () => { - try { - console.log(prop1); - } finally { - console.log('exiting'); - } - }, - [prop1], - AUTODEPS - ); - return
{prop1}
; -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (13:6). - -error.todo-syntax.ts:11:2 - 9 | function Component({prop1}) { - 10 | 'use memo'; -> 11 | useSpecialEffect( - | ^^^^^^^^^^^^^^^^^ -> 12 | () => { - | ^^^^^^^^^^^ -> 13 | try { - … - | ^^^^^^^^^^^ -> 20 | AUTODEPS - | ^^^^^^^^^^^ -> 21 | ); - | ^^^^ Cannot infer dependencies - 22 | return
{prop1}
; - 23 | } - 24 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js deleted file mode 100644 index ad1e58532953..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js +++ /dev/null @@ -1,23 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import {useSpecialEffect} from 'shared-runtime'; -import {AUTODEPS} from 'react'; - -/** - * Note that a react compiler-based transform still has limitations on JS syntax. - * We should surface these as actionable lint / build errors to devs. - */ -function Component({prop1}) { - 'use memo'; - useSpecialEffect( - () => { - try { - console.log(prop1); - } finally { - console.log('exiting'); - } - }, - [prop1], - AUTODEPS - ); - return
{prop1}
; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md deleted file mode 100644 index 8371ba004034..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; - -function Component({propVal}) { - 'use no memo'; - useEffect(() => [propVal], AUTODEPS); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.use-no-memo.ts:6:2 - 4 | function Component({propVal}) { - 5 | 'use no memo'; -> 6 | useEffect(() => [propVal], AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 7 | } - 8 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js deleted file mode 100644 index 30fbd8c2a61e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js +++ /dev/null @@ -1,7 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; - -function Component({propVal}) { - 'use no memo'; - useEffect(() => [propVal], AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md deleted file mode 100644 index b8cf1bc40aa8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({foo}) { - const arr = []; - // Taking either arr[0].value or arr as a dependency is reasonable - // as long as developers know what to expect. - useEffect(() => print(arr[0].value), AUTODEPS); - arr.push({value: foo}); - return arr; -} - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function Component(t0) { - const { foo } = t0; - const arr = []; - - useEffect(() => print(arr[0].value), [arr[0].value]); - arr.push({ value: foo }); - return arr; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js deleted file mode 100644 index 9ec840ab623e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js +++ /dev/null @@ -1,12 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({foo}) { - const arr = []; - // Taking either arr[0].value or arr as a dependency is reasonable - // as long as developers know what to expect. - useEffect(() => print(arr[0].value), AUTODEPS); - arr.push({value: foo}); - return arr; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md deleted file mode 100644 index 4606d49f37fa..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({foo}) { - const arr = []; - // Taking either arr[0].value or arr as a dependency is reasonable - // as long as developers know what to expect. - useEffect(() => print(arr[0]?.value), AUTODEPS); - arr.push({value: foo}); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function Component(t0) { - const { foo } = t0; - const arr = []; - - useEffect(() => print(arr[0]?.value), [arr[0]?.value]); - arr.push({ value: foo }); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ foo: 1 }], -}; - -``` - -## Logs - -``` -{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} -{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: ok) [{"value":1}] -logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js deleted file mode 100644 index c78251bf2d3b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.js +++ /dev/null @@ -1,17 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({foo}) { - const arr = []; - // Taking either arr[0].value or arr as a dependency is reasonable - // as long as developers know what to expect. - useEffect(() => print(arr[0]?.value), AUTODEPS); - arr.push({value: foo}); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md deleted file mode 100644 index ea5a887b8bf7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ /dev/null @@ -1,57 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly - -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({arrRef}) { - // Avoid taking arr.current as a dependency - useEffect(() => print(arrRef.current), AUTODEPS); - arrRef.current.val = 2; - return arrRef; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{arrRef: {current: {val: 'initial ref value'}}}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly - -import { useEffect, useRef, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function Component(t0) { - const { arrRef } = t0; - - useEffect(() => print(arrRef.current), [arrRef]); - arrRef.current.val = 2; - return arrRef; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ arrRef: { current: { val: "initial ref value" } } }], -}; - -``` - -## Logs - -``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} -{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: ok) {"current":{"val":2}} -logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js deleted file mode 100644 index d972d6d001c8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ /dev/null @@ -1,16 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly - -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({arrRef}) { - // Avoid taking arr.current as a dependency - useEffect(() => print(arrRef.current), AUTODEPS); - arrRef.current.val = 2; - return arrRef; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{arrRef: {current: {val: 'initial ref value'}}}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md deleted file mode 100644 index 71cd9fb62085..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ /dev/null @@ -1,56 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly -import {useEffect, AUTODEPS} from 'react'; - -function Component({foo}) { - const arr = []; - useEffect(() => { - arr.push(foo); - }, AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly -import { useEffect, AUTODEPS } from "react"; - -function Component(t0) { - const { foo } = t0; - const arr = []; - useEffect(() => { - arr.push(foo); - }, [arr, foo]); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ foo: 1 }], -}; - -``` - -## Logs - -``` -{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} -{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js deleted file mode 100644 index 965f64fd7aeb..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ /dev/null @@ -1,16 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly -import {useEffect, AUTODEPS} from 'react'; - -function Component({foo}) { - const arr = []; - useEffect(() => { - arr.push(foo); - }, AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md deleted file mode 100644 index 4b2f94a263a1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; - -function Component({foo}) { - useEffect(AUTODEPS); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.wrong-index-no-func.ts:5:2 - 3 | - 4 | function Component({foo}) { -> 5 | useEffect(AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 6 | } - 7 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.js deleted file mode 100644 index 973798b5731b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.js +++ /dev/null @@ -1,6 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; - -function Component({foo}) { - useEffect(AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md deleted file mode 100644 index 0a5cde5cd65d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md +++ /dev/null @@ -1,52 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {AUTODEPS} from 'react'; -import useEffectWrapper from 'useEffectWrapper'; - -function Component({foo}) { - useEffectWrapper( - () => { - console.log(foo); - }, - [foo], - AUTODEPS - ); -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.wrong-index.ts:6:2 - 4 | - 5 | function Component({foo}) { -> 6 | useEffectWrapper( - | ^^^^^^^^^^^^^^^^^ -> 7 | () => { - | ^^^^^^^^^^^ -> 8 | console.log(foo); - | ^^^^^^^^^^^ -> 9 | }, - | ^^^^^^^^^^^ -> 10 | [foo], - | ^^^^^^^^^^^ -> 11 | AUTODEPS - | ^^^^^^^^^^^ -> 12 | ); - | ^^^^ Cannot infer dependencies - 13 | } - 14 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.js deleted file mode 100644 index b0898e3dbde9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.js +++ /dev/null @@ -1,13 +0,0 @@ -// @inferEffectDependencies -import {AUTODEPS} from 'react'; -import useEffectWrapper from 'useEffectWrapper'; - -function Component({foo}) { - useEffectWrapper( - () => { - console.log(foo); - }, - [foo], - AUTODEPS - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/helper-nonreactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/helper-nonreactive.expect.md deleted file mode 100644 index ae2b018d36dd..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/helper-nonreactive.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -function useCustomRef() { - const ref = useRef(); - return ref; -} -function NonReactiveWrapper() { - const ref = useCustomRef(); - useEffect(() => { - print(ref); - }, AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useRef, AUTODEPS } from "react"; -function useCustomRef() { - const ref = useRef(); - return ref; -} - -function NonReactiveWrapper() { - const $ = _c(2); - const ref = useCustomRef(); - let t0; - if ($[0] !== ref) { - t0 = () => { - print(ref); - }; - $[0] = ref; - $[1] = t0; - } else { - t0 = $[1]; - } - useEffect(t0, [ref]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/helper-nonreactive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/helper-nonreactive.js deleted file mode 100644 index 18de64bc08b4..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/helper-nonreactive.js +++ /dev/null @@ -1,12 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -function useCustomRef() { - const ref = useRef(); - return ref; -} -function NonReactiveWrapper() { - const ref = useCustomRef(); - useEffect(() => { - print(ref); - }, AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/import-namespace-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/import-namespace-useEffect.expect.md deleted file mode 100644 index a221475b95bb..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/import-namespace-useEffect.expect.md +++ /dev/null @@ -1,59 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import * as React from 'react'; -import * as SharedRuntime from 'shared-runtime'; - -function NonReactiveDepInEffect() { - const obj = makeObject_Primitives(); - React.useEffect(() => print(obj), React.AUTODEPS); - SharedRuntime.useSpecialEffect(() => print(obj), [obj], React.AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import * as React from "react"; -import * as SharedRuntime from "shared-runtime"; - -function NonReactiveDepInEffect() { - const $ = _c(4); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = makeObject_Primitives(); - $[0] = t0; - } else { - t0 = $[0]; - } - const obj = t0; - let t1; - if ($[1] === Symbol.for("react.memo_cache_sentinel")) { - t1 = () => print(obj); - $[1] = t1; - } else { - t1 = $[1]; - } - React.useEffect(t1, [obj]); - let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => print(obj); - t3 = [obj]; - $[2] = t2; - $[3] = t3; - } else { - t2 = $[2]; - t3 = $[3]; - } - SharedRuntime.useSpecialEffect(t2, t3, [obj]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/import-namespace-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/import-namespace-useEffect.js deleted file mode 100644 index 3ef0bf400e5b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/import-namespace-useEffect.js +++ /dev/null @@ -1,9 +0,0 @@ -// @inferEffectDependencies -import * as React from 'react'; -import * as SharedRuntime from 'shared-runtime'; - -function NonReactiveDepInEffect() { - const obj = makeObject_Primitives(); - React.useEffect(() => print(obj), React.AUTODEPS); - SharedRuntime.useSpecialEffect(() => print(obj), [obj], React.AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-deps-custom-config.expect.md deleted file mode 100644 index 03d9ba04e90c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-deps-custom-config.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {print, useSpecialEffect} from 'shared-runtime'; -import {AUTODEPS} from 'react'; - -function CustomConfig({propVal}) { - // Insertion - useSpecialEffect(() => print(propVal), [propVal], AUTODEPS); - // No insertion - useSpecialEffect(() => print(propVal), [propVal], [propVal]); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { print, useSpecialEffect } from "shared-runtime"; -import { AUTODEPS } from "react"; - -function CustomConfig(t0) { - const $ = _c(7); - const { propVal } = t0; - let t1; - let t2; - if ($[0] !== propVal) { - t1 = () => print(propVal); - t2 = [propVal]; - $[0] = propVal; - $[1] = t1; - $[2] = t2; - } else { - t1 = $[1]; - t2 = $[2]; - } - useSpecialEffect(t1, t2, [propVal]); - let t3; - let t4; - let t5; - if ($[3] !== propVal) { - t3 = () => print(propVal); - t4 = [propVal]; - t5 = [propVal]; - $[3] = propVal; - $[4] = t3; - $[5] = t4; - $[6] = t5; - } else { - t3 = $[4]; - t4 = $[5]; - t5 = $[6]; - } - useSpecialEffect(t3, t4, t5); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-deps-custom-config.js deleted file mode 100644 index efb821239841..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-deps-custom-config.js +++ /dev/null @@ -1,10 +0,0 @@ -// @inferEffectDependencies -import {print, useSpecialEffect} from 'shared-runtime'; -import {AUTODEPS} from 'react'; - -function CustomConfig({propVal}) { - // Insertion - useSpecialEffect(() => print(propVal), [propVal], AUTODEPS); - // No insertion - useSpecialEffect(() => print(propVal), [propVal], [propVal]); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-effect-dependencies.expect.md deleted file mode 100644 index e09c0ef8e8a4..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-effect-dependencies.expect.md +++ /dev/null @@ -1,129 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -import useEffectWrapper from 'useEffectWrapper'; - -const moduleNonReactive = 0; - -function Component({foo, bar}) { - const localNonreactive = 0; - const ref = useRef(0); - const localNonPrimitiveReactive = { - foo, - }; - const localNonPrimitiveNonreactive = {}; - useEffect(() => { - console.log(foo); - console.log(bar); - console.log(moduleNonReactive); - console.log(localNonreactive); - console.log(globalValue); - console.log(ref.current); - console.log(localNonPrimitiveReactive); - console.log(localNonPrimitiveNonreactive); - }, AUTODEPS); - - // Optional chains and property accesses - // TODO: we may be able to save bytes by omitting property accesses if the - // object of the member expression is already included in the inferred deps - useEffect(() => { - console.log(bar?.baz); - console.log(bar.qux); - }, AUTODEPS); - - useEffectWrapper(() => { - console.log(foo); - }, AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useRef, AUTODEPS } from "react"; -import useEffectWrapper from "useEffectWrapper"; - -const moduleNonReactive = 0; - -function Component(t0) { - const $ = _c(12); - const { foo, bar } = t0; - - const ref = useRef(0); - let t1; - if ($[0] !== foo) { - t1 = { foo }; - $[0] = foo; - $[1] = t1; - } else { - t1 = $[1]; - } - const localNonPrimitiveReactive = t1; - let t2; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = {}; - $[2] = t2; - } else { - t2 = $[2]; - } - const localNonPrimitiveNonreactive = t2; - let t3; - if ($[3] !== bar || $[4] !== foo || $[5] !== localNonPrimitiveReactive) { - t3 = () => { - console.log(foo); - console.log(bar); - console.log(moduleNonReactive); - console.log(0); - console.log(globalValue); - console.log(ref.current); - console.log(localNonPrimitiveReactive); - console.log(localNonPrimitiveNonreactive); - }; - $[3] = bar; - $[4] = foo; - $[5] = localNonPrimitiveReactive; - $[6] = t3; - } else { - t3 = $[6]; - } - useEffect(t3, [ - foo, - bar, - localNonPrimitiveReactive, - localNonPrimitiveNonreactive, - ]); - let t4; - if ($[7] !== bar.baz || $[8] !== bar.qux) { - t4 = () => { - console.log(bar?.baz); - console.log(bar.qux); - }; - $[7] = bar.baz; - $[8] = bar.qux; - $[9] = t4; - } else { - t4 = $[9]; - } - useEffect(t4, [bar.baz, bar.qux]); - let t5; - if ($[10] !== foo) { - t5 = () => { - console.log(foo); - }; - $[10] = foo; - $[11] = t5; - } else { - t5 = $[11]; - } - useEffectWrapper(t5, [foo]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-effect-dependencies.js deleted file mode 100644 index bf02842d25ff..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/infer-effect-dependencies.js +++ /dev/null @@ -1,36 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -import useEffectWrapper from 'useEffectWrapper'; - -const moduleNonReactive = 0; - -function Component({foo, bar}) { - const localNonreactive = 0; - const ref = useRef(0); - const localNonPrimitiveReactive = { - foo, - }; - const localNonPrimitiveNonreactive = {}; - useEffect(() => { - console.log(foo); - console.log(bar); - console.log(moduleNonReactive); - console.log(localNonreactive); - console.log(globalValue); - console.log(ref.current); - console.log(localNonPrimitiveReactive); - console.log(localNonPrimitiveNonreactive); - }, AUTODEPS); - - // Optional chains and property accesses - // TODO: we may be able to save bytes by omitting property accesses if the - // object of the member expression is already included in the inferred deps - useEffect(() => { - console.log(bar?.baz); - console.log(bar.qux); - }, AUTODEPS); - - useEffectWrapper(() => { - console.log(foo); - }, AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-dep.expect.md deleted file mode 100644 index 2730f8941b51..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-dep.expect.md +++ /dev/null @@ -1,80 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {makeObject_Primitives, print} from 'shared-runtime'; - -/** - * Note that `obj` is currently added to the effect dependency array, even - * though it's non-reactive due to memoization. - * - * This is a TODO in effect dependency inference. Note that we cannot simply - * filter out non-reactive effect dependencies, as some non-reactive (by data - * flow) values become reactive due to scope pruning. See the - * `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters. - * - * Realizing that this `useEffect` should have an empty dependency array - * requires effect dependency inference to be structured similarly to memo - * dependency inference. - * Pass 1: add all potential dependencies regardless of dataflow reactivity - * Pass 2: (todo) prune non-reactive dependencies - * - * Note that instruction reordering should significantly reduce scope pruning - */ -function NonReactiveDepInEffect() { - const obj = makeObject_Primitives(); - useEffect(() => print(obj), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { makeObject_Primitives, print } from "shared-runtime"; - -/** - * Note that `obj` is currently added to the effect dependency array, even - * though it's non-reactive due to memoization. - * - * This is a TODO in effect dependency inference. Note that we cannot simply - * filter out non-reactive effect dependencies, as some non-reactive (by data - * flow) values become reactive due to scope pruning. See the - * `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters. - * - * Realizing that this `useEffect` should have an empty dependency array - * requires effect dependency inference to be structured similarly to memo - * dependency inference. - * Pass 1: add all potential dependencies regardless of dataflow reactivity - * Pass 2: (todo) prune non-reactive dependencies - * - * Note that instruction reordering should significantly reduce scope pruning - */ -function NonReactiveDepInEffect() { - const $ = _c(2); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = makeObject_Primitives(); - $[0] = t0; - } else { - t0 = $[0]; - } - const obj = t0; - let t1; - if ($[1] === Symbol.for("react.memo_cache_sentinel")) { - t1 = () => print(obj); - $[1] = t1; - } else { - t1 = $[1]; - } - useEffect(t1, [obj]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-dep.js deleted file mode 100644 index f6e44034c51f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-dep.js +++ /dev/null @@ -1,25 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {makeObject_Primitives, print} from 'shared-runtime'; - -/** - * Note that `obj` is currently added to the effect dependency array, even - * though it's non-reactive due to memoization. - * - * This is a TODO in effect dependency inference. Note that we cannot simply - * filter out non-reactive effect dependencies, as some non-reactive (by data - * flow) values become reactive due to scope pruning. See the - * `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters. - * - * Realizing that this `useEffect` should have an empty dependency array - * requires effect dependency inference to be structured similarly to memo - * dependency inference. - * Pass 1: add all potential dependencies regardless of dataflow reactivity - * Pass 2: (todo) prune non-reactive dependencies - * - * Note that instruction reordering should significantly reduce scope pruning - */ -function NonReactiveDepInEffect() { - const obj = makeObject_Primitives(); - useEffect(() => print(obj), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.expect.md deleted file mode 100644 index c95f9625bbb3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useEffectEvent, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * We do not include effect events in dep arrays. - */ -function NonReactiveEffectEvent() { - const fn = useEffectEvent(() => print('hello world')); - useEffect(() => fn(), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useEffectEvent, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/** - * We do not include effect events in dep arrays. - */ -function NonReactiveEffectEvent() { - const $ = _c(2); - const fn = useEffectEvent(_temp); - let t0; - if ($[0] !== fn) { - t0 = () => fn(); - $[0] = fn; - $[1] = t0; - } else { - t0 = $[1]; - } - useEffect(t0, []); -} -function _temp() { - return print("hello world"); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.js deleted file mode 100644 index fc78581e6d52..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.js +++ /dev/null @@ -1,11 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useEffectEvent, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * We do not include effect events in dep arrays. - */ -function NonReactiveEffectEvent() { - const fn = useEffectEvent(() => print('hello world')); - useEffect(() => fn(), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref-helper.expect.md deleted file mode 100644 index 05dcab77968a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref-helper.expect.md +++ /dev/null @@ -1,89 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * We never include a .current access in a dep array because it may be a ref access. - * This might over-capture objects that are not refs and happen to have fields named - * current, but that should be a rare case and the result would still be correct - * (assuming the effect is idempotent). In the worst case, you can always write a manual - * dep array. - */ -function RefsInEffects() { - const ref = useRefHelper(); - const wrapped = useDeeperRefHelper(); - useEffect(() => { - print(ref.current); - print(wrapped.foo.current); - }, AUTODEPS); -} - -function useRefHelper() { - return useRef(0); -} - -function useDeeperRefHelper() { - return {foo: useRefHelper()}; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/** - * We never include a .current access in a dep array because it may be a ref access. - * This might over-capture objects that are not refs and happen to have fields named - * current, but that should be a rare case and the result would still be correct - * (assuming the effect is idempotent). In the worst case, you can always write a manual - * dep array. - */ -function RefsInEffects() { - const $ = _c(3); - const ref = useRefHelper(); - const wrapped = useDeeperRefHelper(); - let t0; - if ($[0] !== ref || $[1] !== wrapped.foo.current) { - t0 = () => { - print(ref.current); - print(wrapped.foo.current); - }; - $[0] = ref; - $[1] = wrapped.foo.current; - $[2] = t0; - } else { - t0 = $[2]; - } - useEffect(t0, [ref, wrapped.foo]); -} - -function useRefHelper() { - return useRef(0); -} - -function useDeeperRefHelper() { - const $ = _c(2); - const t0 = useRefHelper(); - let t1; - if ($[0] !== t0) { - t1 = { foo: t0 }; - $[0] = t0; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref-helper.js deleted file mode 100644 index 92a905061fe0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref-helper.js +++ /dev/null @@ -1,27 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * We never include a .current access in a dep array because it may be a ref access. - * This might over-capture objects that are not refs and happen to have fields named - * current, but that should be a rare case and the result would still be correct - * (assuming the effect is idempotent). In the worst case, you can always write a manual - * dep array. - */ -function RefsInEffects() { - const ref = useRefHelper(); - const wrapped = useDeeperRefHelper(); - useEffect(() => { - print(ref.current); - print(wrapped.foo.current); - }, AUTODEPS); -} - -function useRefHelper() { - return useRef(0); -} - -function useDeeperRefHelper() { - return {foo: useRefHelper()}; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref.expect.md deleted file mode 100644 index 3c5835e75550..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref.expect.md +++ /dev/null @@ -1,51 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * Special case of `infer-effect-deps/nonreactive-dep`. - * - * We know that local `useRef` return values are stable, regardless of - * inferred memoization. - */ -function NonReactiveRefInEffect() { - const ref = useRef('initial value'); - useEffect(() => print(ref.current), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useRef, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/** - * Special case of `infer-effect-deps/nonreactive-dep`. - * - * We know that local `useRef` return values are stable, regardless of - * inferred memoization. - */ -function NonReactiveRefInEffect() { - const $ = _c(1); - const ref = useRef("initial value"); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => print(ref.current); - $[0] = t0; - } else { - t0 = $[0]; - } - useEffect(t0, []); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref.js deleted file mode 100644 index 38b83b9b8f81..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-ref.js +++ /dev/null @@ -1,14 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * Special case of `infer-effect-deps/nonreactive-dep`. - * - * We know that local `useRef` return values are stable, regardless of - * inferred memoization. - */ -function NonReactiveRefInEffect() { - const ref = useRef('initial value'); - useEffect(() => print(ref.current), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-setState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-setState.expect.md deleted file mode 100644 index e0a1a95b339f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-setState.expect.md +++ /dev/null @@ -1,51 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useState, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * Special case of `infer-effect-deps/nonreactive-dep`. - * - * We know that local `useRef` return values are stable, regardless of - * inferred memoization. - */ -function NonReactiveSetStateInEffect() { - const [_, setState] = useState('initial value'); - useEffect(() => print(setState), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useState, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/** - * Special case of `infer-effect-deps/nonreactive-dep`. - * - * We know that local `useRef` return values are stable, regardless of - * inferred memoization. - */ -function NonReactiveSetStateInEffect() { - const $ = _c(1); - const [, setState] = useState("initial value"); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => print(setState); - $[0] = t0; - } else { - t0 = $[0]; - } - useEffect(t0, []); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-setState.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-setState.js deleted file mode 100644 index 0f8abceca0ec..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-setState.js +++ /dev/null @@ -1,14 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useState, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/** - * Special case of `infer-effect-deps/nonreactive-dep`. - * - * We know that local `useRef` return values are stable, regardless of - * inferred memoization. - */ -function NonReactiveSetStateInEffect() { - const [_, setState] = useState('initial value'); - useEffect(() => print(setState), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/outlined-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/outlined-function.expect.md deleted file mode 100644 index a0b0594afa1b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/outlined-function.expect.md +++ /dev/null @@ -1,46 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; -/** - * This compiled output is technically incorrect but this is currently the same - * case as a bailout (an effect that overfires). - * - * To ensure an empty deps array is passed, we need special case - * `InferEffectDependencies` for outlined functions (likely easier) or run it - * before OutlineFunctions - */ -function OutlinedFunctionInEffect() { - useEffect(() => print('hello world!'), AUTODEPS); -} - -``` - -## Code - -```javascript -// @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; -/** - * This compiled output is technically incorrect but this is currently the same - * case as a bailout (an effect that overfires). - * - * To ensure an empty deps array is passed, we need special case - * `InferEffectDependencies` for outlined functions (likely easier) or run it - * before OutlineFunctions - */ -function OutlinedFunctionInEffect() { - useEffect(_temp, []); -} -function _temp() { - return print("hello world!"); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/outlined-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/outlined-function.js deleted file mode 100644 index 5ec2f7d8f9ed..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/outlined-function.js +++ /dev/null @@ -1,14 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; -/** - * This compiled output is technically incorrect but this is currently the same - * case as a bailout (an effect that overfires). - * - * To ensure an empty deps array is passed, we need special case - * `InferEffectDependencies` for outlined functions (likely easier) or run it - * before OutlineFunctions - */ -function OutlinedFunctionInEffect() { - useEffect(() => print('hello world!'), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/pruned-nonreactive-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/pruned-nonreactive-obj.expect.md deleted file mode 100644 index c6c0087a774f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/pruned-nonreactive-obj.expect.md +++ /dev/null @@ -1,119 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useIdentity, mutate, makeObject} from 'shared-runtime'; -import {useEffect, AUTODEPS} from 'react'; - -/** - * When a semantically non-reactive value has a pruned scope (i.e. the object - * identity becomes reactive, but the underlying value it represents should be - * constant), the compiler can choose to either - * - add it as a dependency (and rerun the effect) - * - not add it as a dependency - * - * We keep semantically non-reactive values in both memo block and effect - * dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`. - * ```js - * function Component() { - * // obj is semantically non-reactive, but its memo scope is pruned due to - * // the interleaving hook call - * const obj = {}; - * useHook(); - * write(obj); - * - * const ref = useRef(); - * - * // this effect needs to be rerun when obj's referential identity changes, - * // because it might alias obj to a useRef / mutable store. - * useEffect(() => ref.current = obj, ???); - * - * // in a custom hook (or child component), the user might expect versioning - * // invariants to hold - * useHook(ref, obj); - * } - * - * // defined elsewhere - * function useHook(someRef, obj) { - * useEffect( - * () => assert(someRef.current === obj), - * [someRef, obj] - * ); - * } - * ``` - */ -function PrunedNonReactive() { - const obj = makeObject(); - useIdentity(null); - mutate(obj); - - useEffect(() => print(obj.value), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useIdentity, mutate, makeObject } from "shared-runtime"; -import { useEffect, AUTODEPS } from "react"; - -/** - * When a semantically non-reactive value has a pruned scope (i.e. the object - * identity becomes reactive, but the underlying value it represents should be - * constant), the compiler can choose to either - * - add it as a dependency (and rerun the effect) - * - not add it as a dependency - * - * We keep semantically non-reactive values in both memo block and effect - * dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`. - * ```js - * function Component() { - * // obj is semantically non-reactive, but its memo scope is pruned due to - * // the interleaving hook call - * const obj = {}; - * useHook(); - * write(obj); - * - * const ref = useRef(); - * - * // this effect needs to be rerun when obj's referential identity changes, - * // because it might alias obj to a useRef / mutable store. - * useEffect(() => ref.current = obj, ???); - * - * // in a custom hook (or child component), the user might expect versioning - * // invariants to hold - * useHook(ref, obj); - * } - * - * // defined elsewhere - * function useHook(someRef, obj) { - * useEffect( - * () => assert(someRef.current === obj), - * [someRef, obj] - * ); - * } - * ``` - */ -function PrunedNonReactive() { - const $ = _c(2); - const obj = makeObject(); - useIdentity(null); - mutate(obj); - let t0; - if ($[0] !== obj.value) { - t0 = () => print(obj.value); - $[0] = obj.value; - $[1] = t0; - } else { - t0 = $[1]; - } - useEffect(t0, [obj.value]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/pruned-nonreactive-obj.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/pruned-nonreactive-obj.js deleted file mode 100644 index f210f2779a18..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/pruned-nonreactive-obj.js +++ /dev/null @@ -1,48 +0,0 @@ -// @inferEffectDependencies -import {useIdentity, mutate, makeObject} from 'shared-runtime'; -import {useEffect, AUTODEPS} from 'react'; - -/** - * When a semantically non-reactive value has a pruned scope (i.e. the object - * identity becomes reactive, but the underlying value it represents should be - * constant), the compiler can choose to either - * - add it as a dependency (and rerun the effect) - * - not add it as a dependency - * - * We keep semantically non-reactive values in both memo block and effect - * dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`. - * ```js - * function Component() { - * // obj is semantically non-reactive, but its memo scope is pruned due to - * // the interleaving hook call - * const obj = {}; - * useHook(); - * write(obj); - * - * const ref = useRef(); - * - * // this effect needs to be rerun when obj's referential identity changes, - * // because it might alias obj to a useRef / mutable store. - * useEffect(() => ref.current = obj, ???); - * - * // in a custom hook (or child component), the user might expect versioning - * // invariants to hold - * useHook(ref, obj); - * } - * - * // defined elsewhere - * function useHook(someRef, obj) { - * useEffect( - * () => assert(someRef.current === obj), - * [someRef, obj] - * ); - * } - * ``` - */ -function PrunedNonReactive() { - const obj = makeObject(); - useIdentity(null); - mutate(obj); - - useEffect(() => print(obj.value), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr-merge.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr-merge.expect.md deleted file mode 100644 index 983c474bdeae..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr-merge.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveMemberExprMerge({propVal}) { - const obj = {a: {b: propVal}}; - useEffect(() => print(obj.a, obj.a.b), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function ReactiveMemberExprMerge(t0) { - const $ = _c(4); - const { propVal } = t0; - let t1; - if ($[0] !== propVal) { - t1 = { a: { b: propVal } }; - $[0] = propVal; - $[1] = t1; - } else { - t1 = $[1]; - } - const obj = t1; - let t2; - if ($[2] !== obj.a) { - t2 = () => print(obj.a, obj.a.b); - $[2] = obj.a; - $[3] = t2; - } else { - t2 = $[3]; - } - useEffect(t2, [obj.a]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr-merge.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr-merge.js deleted file mode 100644 index 41c18d937a2e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr-merge.js +++ /dev/null @@ -1,8 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveMemberExprMerge({propVal}) { - const obj = {a: {b: propVal}}; - useEffect(() => print(obj.a, obj.a.b), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr.expect.md deleted file mode 100644 index 50bceb09aef1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveMemberExpr({propVal}) { - const obj = {a: {b: propVal}}; - useEffect(() => print(obj.a.b), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function ReactiveMemberExpr(t0) { - const $ = _c(4); - const { propVal } = t0; - let t1; - if ($[0] !== propVal) { - t1 = { a: { b: propVal } }; - $[0] = propVal; - $[1] = t1; - } else { - t1 = $[1]; - } - const obj = t1; - let t2; - if ($[2] !== obj.a.b) { - t2 = () => print(obj.a.b); - $[2] = obj.a.b; - $[3] = t2; - } else { - t2 = $[3]; - } - useEffect(t2, [obj.a.b]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr.js deleted file mode 100644 index ca6e7d4f0f01..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-memberexpr.js +++ /dev/null @@ -1,8 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveMemberExpr({propVal}) { - const obj = {a: {b: propVal}}; - useEffect(() => print(obj.a.b), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md deleted file mode 100644 index a886842c8eb0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.expect.md +++ /dev/null @@ -1,100 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print, shallowCopy} from 'shared-runtime'; - -function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null, c: null}; - const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); - const primitive = shallowCopy(propVal); - useEffect( - () => print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f), - AUTODEPS - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveMemberExpr, - params: [{cond: true, propVal: 1}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print, shallowCopy } from "shared-runtime"; - -function ReactiveMemberExpr(t0) { - const $ = _c(13); - const { cond, propVal } = t0; - let t1; - if ($[0] !== cond || $[1] !== propVal) { - t1 = cond ? { b: propVal } : null; - $[0] = cond; - $[1] = propVal; - $[2] = t1; - } else { - t1 = $[2]; - } - let t2; - if ($[3] !== t1) { - t2 = { a: t1, c: null }; - $[3] = t1; - $[4] = t2; - } else { - t2 = $[4]; - } - const obj = t2; - const t3 = propVal + 1; - let t4; - if ($[5] !== t3) { - t4 = shallowCopy({ a: { b: { c: { d: { e: { f: t3 } } } } } }); - $[5] = t3; - $[6] = t4; - } else { - t4 = $[6]; - } - const other = t4; - let t5; - if ($[7] !== propVal) { - t5 = shallowCopy(propVal); - $[7] = propVal; - $[8] = t5; - } else { - t5 = $[8]; - } - const primitive = t5; - let t6; - if ( - $[9] !== obj.a?.b || - $[10] !== other?.a?.b?.c?.d?.e.f || - $[11] !== primitive.a?.b.c?.d?.e.f - ) { - t6 = () => - print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f); - $[9] = obj.a?.b; - $[10] = other?.a?.b?.c?.d?.e.f; - $[11] = primitive.a?.b.c?.d?.e.f; - $[12] = t6; - } else { - t6 = $[12]; - } - useEffect(t6, [obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveMemberExpr, - params: [{ cond: true, propVal: 1 }], -}; - -``` - -### Eval output -(kind: ok) -logs: [1,2,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js deleted file mode 100644 index 42dc585cbe9a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain-complex.js +++ /dev/null @@ -1,18 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print, shallowCopy} from 'shared-runtime'; - -function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null, c: null}; - const other = shallowCopy({a: {b: {c: {d: {e: {f: propVal + 1}}}}}}); - const primitive = shallowCopy(propVal); - useEffect( - () => print(obj.a?.b, other?.a?.b?.c?.d?.e.f, primitive.a?.b.c?.d?.e.f), - AUTODEPS - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveMemberExpr, - params: [{cond: true, propVal: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md deleted file mode 100644 index 7dfad84f7959..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.expect.md +++ /dev/null @@ -1,79 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null, c: null}; - useEffect(() => print(obj.a?.b), AUTODEPS); - useEffect(() => print(obj.c?.d), AUTODEPS); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveMemberExpr, - params: [{cond: true, propVal: 1}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function ReactiveMemberExpr(t0) { - const $ = _c(9); - const { cond, propVal } = t0; - let t1; - if ($[0] !== cond || $[1] !== propVal) { - t1 = cond ? { b: propVal } : null; - $[0] = cond; - $[1] = propVal; - $[2] = t1; - } else { - t1 = $[2]; - } - let t2; - if ($[3] !== t1) { - t2 = { a: t1, c: null }; - $[3] = t1; - $[4] = t2; - } else { - t2 = $[4]; - } - const obj = t2; - let t3; - if ($[5] !== obj.a?.b) { - t3 = () => print(obj.a?.b); - $[5] = obj.a?.b; - $[6] = t3; - } else { - t3 = $[6]; - } - useEffect(t3, [obj.a?.b]); - let t4; - if ($[7] !== obj.c?.d) { - t4 = () => print(obj.c?.d); - $[7] = obj.c?.d; - $[8] = t4; - } else { - t4 = $[8]; - } - useEffect(t4, [obj.c?.d]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveMemberExpr, - params: [{ cond: true, propVal: 1 }], -}; - -``` - -### Eval output -(kind: ok) -logs: [1,undefined] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js deleted file mode 100644 index 882f9026613c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-optional-chain.js +++ /dev/null @@ -1,14 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveMemberExpr({cond, propVal}) { - const obj = {a: cond ? {b: propVal} : null, c: null}; - useEffect(() => print(obj.a?.b), AUTODEPS); - useEffect(() => print(obj.c?.d), AUTODEPS); -} - -export const FIXTURE_ENTRYPOINT = { - fn: ReactiveMemberExpr, - params: [{cond: true, propVal: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref-ternary.expect.md deleted file mode 100644 index 97fab06c1f37..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref-ternary.expect.md +++ /dev/null @@ -1,69 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useRef, useEffect, AUTODEPS} from 'react'; -import {print, mutate} from 'shared-runtime'; - -function Component({cond}) { - const arr = useRef([]); - const other = useRef([]); - // Although arr and other are both stable, derived is not - const derived = cond ? arr : other; - useEffect(() => { - mutate(derived.current); - print(derived.current); - }, AUTODEPS); - return arr; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useRef, useEffect, AUTODEPS } from "react"; -import { print, mutate } from "shared-runtime"; - -function Component(t0) { - const $ = _c(4); - const { cond } = t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = []; - $[0] = t1; - } else { - t1 = $[0]; - } - const arr = useRef(t1); - let t2; - if ($[1] === Symbol.for("react.memo_cache_sentinel")) { - t2 = []; - $[1] = t2; - } else { - t2 = $[1]; - } - const other = useRef(t2); - - const derived = cond ? arr : other; - let t3; - if ($[2] !== derived) { - t3 = () => { - mutate(derived.current); - print(derived.current); - }; - $[2] = derived; - $[3] = t3; - } else { - t3 = $[3]; - } - useEffect(t3, [derived]); - return arr; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref-ternary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref-ternary.js deleted file mode 100644 index 32a9037a9f7e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref-ternary.js +++ /dev/null @@ -1,15 +0,0 @@ -// @inferEffectDependencies -import {useRef, useEffect, AUTODEPS} from 'react'; -import {print, mutate} from 'shared-runtime'; - -function Component({cond}) { - const arr = useRef([]); - const other = useRef([]); - // Although arr and other are both stable, derived is not - const derived = cond ? arr : other; - useEffect(() => { - mutate(derived.current); - print(derived.current); - }, AUTODEPS); - return arr; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref.expect.md deleted file mode 100644 index 7e5371dba355..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref.expect.md +++ /dev/null @@ -1,66 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/* - * Ref types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const ref1 = useRef('initial value'); - const ref2 = useRef('initial value'); - let ref; - if (props.foo) { - ref = ref1; - } else { - ref = ref2; - } - useEffect(() => print(ref), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useRef, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/* - * Ref types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const $ = _c(4); - const ref1 = useRef("initial value"); - const ref2 = useRef("initial value"); - let ref; - if ($[0] !== props.foo) { - if (props.foo) { - ref = ref1; - } else { - ref = ref2; - } - $[0] = props.foo; - $[1] = ref; - } else { - ref = $[1]; - } - let t0; - if ($[2] !== ref) { - t0 = () => print(ref); - $[2] = ref; - $[3] = t0; - } else { - t0 = $[3]; - } - useEffect(t0, [ref]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref.js deleted file mode 100644 index ee98a93c70bf..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-ref.js +++ /dev/null @@ -1,18 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/* - * Ref types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const ref1 = useRef('initial value'); - const ref2 = useRef('initial value'); - let ref; - if (props.foo) { - ref = ref1; - } else { - ref = ref2; - } - useEffect(() => print(ref), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-setState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-setState.expect.md deleted file mode 100644 index db79dda30103..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-setState.expect.md +++ /dev/null @@ -1,66 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, useState, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/* - * setState types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const [_state1, setState1] = useRef('initial value'); - const [_state2, setState2] = useRef('initial value'); - let setState; - if (props.foo) { - setState = setState1; - } else { - setState = setState2; - } - useEffect(() => print(setState), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, useState, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/* - * setState types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const $ = _c(4); - const [, setState1] = useRef("initial value"); - const [, setState2] = useRef("initial value"); - let setState; - if ($[0] !== props.foo) { - if (props.foo) { - setState = setState1; - } else { - setState = setState2; - } - $[0] = props.foo; - $[1] = setState; - } else { - setState = $[1]; - } - let t0; - if ($[2] !== setState) { - t0 = () => print(setState); - $[2] = setState; - $[3] = t0; - } else { - t0 = $[3]; - } - useEffect(t0, [setState]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-setState.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-setState.js deleted file mode 100644 index f69efd650cd0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-setState.js +++ /dev/null @@ -1,18 +0,0 @@ -// @inferEffectDependencies -import {useEffect, useState, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/* - * setState types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const [_state1, setState1] = useRef('initial value'); - const [_state2, setState2] = useRef('initial value'); - let setState; - if (props.foo) { - setState = setState1; - } else { - setState = setState2; - } - useEffect(() => print(setState), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-variable.expect.md deleted file mode 100644 index 8646a926aa72..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-variable.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveVariable({propVal}) { - const arr = [propVal]; - useEffect(() => print(arr), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function ReactiveVariable(t0) { - const $ = _c(4); - const { propVal } = t0; - let t1; - if ($[0] !== propVal) { - t1 = [propVal]; - $[0] = propVal; - $[1] = t1; - } else { - t1 = $[1]; - } - const arr = t1; - let t2; - if ($[2] !== arr) { - t2 = () => print(arr); - $[2] = arr; - $[3] = t2; - } else { - t2 = $[3]; - } - useEffect(t2, [arr]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-variable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-variable.js deleted file mode 100644 index 510ff6e1242c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/reactive-variable.js +++ /dev/null @@ -1,8 +0,0 @@ -// @inferEffectDependencies -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function ReactiveVariable({propVal}) { - const arr = [propVal]; - useEffect(() => print(arr), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation--lint.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation--lint.expect.md deleted file mode 100644 index b8d213b63740..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation--lint.expect.md +++ /dev/null @@ -1,48 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @outputMode:"lint" @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.infer-effect-deps-with-rule-violation--lint.ts:8:2 - 6 | function Foo({propVal}) { - 7 | const arr = [propVal]; -> 8 | useEffectWrapper(() => print(arr), AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 9 | - 10 | const arr2 = []; - 11 | useEffectWrapper(() => arr2.push(propVal), AUTODEPS); -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation--lint.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation--lint.js deleted file mode 100644 index 7df86cdfd254..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation--lint.js +++ /dev/null @@ -1,20 +0,0 @@ -// @inferEffectDependencies @outputMode:"lint" @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.expect.md deleted file mode 100644 index 80d8af00e610..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @outputMode:"lint" @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - 'use memo'; - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot infer dependencies of this effect. This will break your build! - -To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. - -error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.ts:9:2 - 7 | 'use memo'; - 8 | const arr = [propVal]; -> 9 | useEffectWrapper(() => print(arr), AUTODEPS); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer dependencies - 10 | - 11 | const arr2 = []; - 12 | useEffectWrapper(() => arr2.push(propVal), AUTODEPS); -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.js deleted file mode 100644 index 42bbf4c994bf..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/error.infer-effect-deps-with-rule-violation-use-memo-opt-in--lint.js +++ /dev/null @@ -1,21 +0,0 @@ -// @inferEffectDependencies @outputMode:"lint" @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - 'use memo'; - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md deleted file mode 100644 index 47de4a1d1932..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import { print } from "shared-runtime"; -import useEffectWrapper from "useEffectWrapper"; -import { AUTODEPS } from "react"; - -function Foo(t0) { - const { propVal } = t0; - const arr = [propVal]; - useEffectWrapper(() => print(arr), [arr]); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); - arr2.push(2); - return { arr, arr2 }; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ propVal: 1 }], - sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], -}; - -``` - -### Eval output -(kind: ok) {"arr":[1],"arr2":[2]} -{"arr":[2],"arr2":[2]} -logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.js deleted file mode 100644 index 6cca9a833d81..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.js +++ /dev/null @@ -1,20 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md deleted file mode 100644 index 610ad3489025..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md +++ /dev/null @@ -1,61 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - 'use memo'; - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" -import { print } from "shared-runtime"; -import useEffectWrapper from "useEffectWrapper"; -import { AUTODEPS } from "react"; - -function Foo(t0) { - "use memo"; - const { propVal } = t0; - - const arr = [propVal]; - useEffectWrapper(() => print(arr), [arr]); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), [arr2, propVal]); - arr2.push(2); - return { arr, arr2 }; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ propVal: 1 }], - sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], -}; - -``` - -### Eval output -(kind: ok) {"arr":[1],"arr2":[2]} -{"arr":[2],"arr2":[2]} -logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.js deleted file mode 100644 index efa5db194060..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.js +++ /dev/null @@ -1,21 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function Foo({propVal}) { - 'use memo'; - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); - - const arr2 = []; - useEffectWrapper(() => arr2.push(propVal), AUTODEPS); - arr2.push(2); - return {arr, arr2}; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{propVal: 1}], - sequentialRenders: [{propVal: 1}, {propVal: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/lint-repro.expect.md deleted file mode 100644 index 1d767ce3db71..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/lint-repro.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @outputMode:"lint" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function ReactiveVariable({propVal}) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); -} - -``` - -## Code - -```javascript -// @inferEffectDependencies @outputMode:"lint" -import { print } from "shared-runtime"; -import useEffectWrapper from "useEffectWrapper"; -import { AUTODEPS } from "react"; - -function ReactiveVariable({ propVal }) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/lint-repro.js deleted file mode 100644 index 011cc535c215..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/lint-repro.js +++ /dev/null @@ -1,9 +0,0 @@ -// @inferEffectDependencies @outputMode:"lint" -import {print} from 'shared-runtime'; -import useEffectWrapper from 'useEffectWrapper'; -import {AUTODEPS} from 'react'; - -function ReactiveVariable({propVal}) { - const arr = [propVal]; - useEffectWrapper(() => print(arr), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md deleted file mode 100644 index ae93de0b1e7e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md +++ /dev/null @@ -1,478 +0,0 @@ - -## Input - -```javascript -// @inlineJsxTransform - -function Parent({children, a: _a, b: _b, c: _c, ref}) { - return
{children}
; -} - -function Child({children}) { - return <>{children}; -} - -function GrandChild({className}) { - return ( - - Hello world - - ); -} - -function ParentAndRefAndKey(props) { - const testRef = useRef(); - return ; -} - -function ParentAndChildren(props) { - const render = () => { - return
{props.foo}
; - }; - return ( - - - - - {render()} - - - ); -} - -const propsToSpread = {a: 'a', b: 'b', c: 'c'}; -function PropsSpread() { - return ( - <> - - - - ); -} - -function ConditionalJsx({shouldWrap}) { - let content =
Hello
; - - if (shouldWrap) { - content = {content}; - } - - return content; -} - -function ComponentWithSpreadPropsAndRef({ref, ...other}) { - return ; -} - -// TODO: Support value blocks -function TernaryJsx({cond}) { - return cond ?
: null; -} - -global.DEV = true; -export const FIXTURE_ENTRYPOINT = { - fn: ParentAndChildren, - params: [{foo: 'abc'}], -}; - -``` - -## Code - -```javascript -import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform - -function Parent(t0) { - const $ = _c2(3); - const { children, ref } = t0; - let t1; - if ($[0] !== children || $[1] !== ref) { - if (DEV) { - t1 =
{children}
; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: "div", - ref: ref, - key: null, - props: { ref: ref, children: children }, - }; - } - $[0] = children; - $[1] = ref; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -function Child(t0) { - const $ = _c2(2); - const { children } = t0; - let t1; - if ($[0] !== children) { - if (DEV) { - t1 = <>{children}; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Symbol.for("react.fragment"), - ref: null, - key: null, - props: { children: children }, - }; - } - $[0] = children; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -function GrandChild(t0) { - const $ = _c2(3); - const { className } = t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - if (DEV) { - t1 = Hello world; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: React.Fragment, - ref: null, - key: "fragmentKey", - props: { children: "Hello world" }, - }; - } - $[0] = t1; - } else { - t1 = $[0]; - } - let t2; - if ($[1] !== className) { - if (DEV) { - t2 = {t1}; - } else { - t2 = { - $$typeof: Symbol.for("react.transitional.element"), - type: "span", - ref: null, - key: null, - props: { className: className, children: t1 }, - }; - } - $[1] = className; - $[2] = t2; - } else { - t2 = $[2]; - } - return t2; -} - -function ParentAndRefAndKey(props) { - const $ = _c2(1); - const testRef = useRef(); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - if (DEV) { - t0 = ; - } else { - t0 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Parent, - ref: testRef, - key: "testKey", - props: { a: "a", b: { b: "b" }, c: C, ref: testRef }, - }; - } - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -function ParentAndChildren(props) { - const $ = _c2(14); - let t0; - if ($[0] !== props.foo) { - t0 = () => { - let t1; - if (DEV) { - t1 =
{props.foo}
; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: "div", - ref: null, - key: "d", - props: { children: props.foo }, - }; - } - return t1; - }; - $[0] = props.foo; - $[1] = t0; - } else { - t0 = $[1]; - } - const render = t0; - let t1; - if ($[2] !== props) { - if (DEV) { - t1 = ; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Child, - ref: null, - key: "a", - props: props, - }; - } - $[2] = props; - $[3] = t1; - } else { - t1 = $[3]; - } - - const t2 = props.foo; - let t3; - if ($[4] !== props) { - if (DEV) { - t3 = ; - } else { - t3 = { - $$typeof: Symbol.for("react.transitional.element"), - type: GrandChild, - ref: null, - key: "c", - props: { className: t2, ...props }, - }; - } - $[4] = props; - $[5] = t3; - } else { - t3 = $[5]; - } - let t4; - if ($[6] !== render) { - t4 = render(); - $[6] = render; - $[7] = t4; - } else { - t4 = $[7]; - } - let t5; - if ($[8] !== t3 || $[9] !== t4) { - if (DEV) { - t5 = ( - - {t3} - {t4} - - ); - } else { - t5 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Child, - ref: null, - key: "b", - props: { children: [t3, t4] }, - }; - } - $[8] = t3; - $[9] = t4; - $[10] = t5; - } else { - t5 = $[10]; - } - let t6; - if ($[11] !== t1 || $[12] !== t5) { - if (DEV) { - t6 = ( - - {t1} - {t5} - - ); - } else { - t6 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Parent, - ref: null, - key: null, - props: { children: [t1, t5] }, - }; - } - $[11] = t1; - $[12] = t5; - $[13] = t6; - } else { - t6 = $[13]; - } - return t6; -} - -const propsToSpread = { a: "a", b: "b", c: "c" }; -function PropsSpread() { - const $ = _c2(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - let t1; - if (DEV) { - t1 = ; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Test, - ref: null, - key: "a", - props: propsToSpread, - }; - } - let t2; - if (DEV) { - t2 = ; - } else { - t2 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Test, - ref: null, - key: "b", - props: { ...propsToSpread, a: "z" }, - }; - } - if (DEV) { - t0 = ( - <> - {t1} - {t2} - - ); - } else { - t0 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Symbol.for("react.fragment"), - ref: null, - key: null, - props: { children: [t1, t2] }, - }; - } - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -function ConditionalJsx(t0) { - const $ = _c2(2); - const { shouldWrap } = t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - if (DEV) { - t1 =
Hello
; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: "div", - ref: null, - key: null, - props: { children: "Hello" }, - }; - } - $[0] = t1; - } else { - t1 = $[0]; - } - let content = t1; - - if (shouldWrap) { - const t2 = content; - let t3; - if ($[1] === Symbol.for("react.memo_cache_sentinel")) { - if (DEV) { - t3 = {t2}; - } else { - t3 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Parent, - ref: null, - key: null, - props: { children: t2 }, - }; - } - $[1] = t3; - } else { - t3 = $[1]; - } - content = t3; - } - - return content; -} - -function ComponentWithSpreadPropsAndRef(t0) { - const $ = _c2(6); - let other; - let ref; - if ($[0] !== t0) { - ({ ref, ...other } = t0); - $[0] = t0; - $[1] = other; - $[2] = ref; - } else { - other = $[1]; - ref = $[2]; - } - let t1; - if ($[3] !== other || $[4] !== ref) { - if (DEV) { - t1 = ; - } else { - t1 = { - $$typeof: Symbol.for("react.transitional.element"), - type: Foo, - ref: ref, - key: null, - props: { ref: ref, ...other }, - }; - } - $[3] = other; - $[4] = ref; - $[5] = t1; - } else { - t1 = $[5]; - } - return t1; -} - -// TODO: Support value blocks -function TernaryJsx(t0) { - const $ = _c2(2); - const { cond } = t0; - let t1; - if ($[0] !== cond) { - t1 = cond ?
: null; - $[0] = cond; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -global.DEV = true; -export const FIXTURE_ENTRYPOINT = { - fn: ParentAndChildren, - params: [{ foo: "abc" }], -}; - -``` - -### Eval output -(kind: ok)
Hello world
abc
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js deleted file mode 100644 index 2ab1efef794b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js +++ /dev/null @@ -1,72 +0,0 @@ -// @inlineJsxTransform - -function Parent({children, a: _a, b: _b, c: _c, ref}) { - return
{children}
; -} - -function Child({children}) { - return <>{children}; -} - -function GrandChild({className}) { - return ( - - Hello world - - ); -} - -function ParentAndRefAndKey(props) { - const testRef = useRef(); - return ; -} - -function ParentAndChildren(props) { - const render = () => { - return
{props.foo}
; - }; - return ( - - - - - {render()} - - - ); -} - -const propsToSpread = {a: 'a', b: 'b', c: 'c'}; -function PropsSpread() { - return ( - <> - - - - ); -} - -function ConditionalJsx({shouldWrap}) { - let content =
Hello
; - - if (shouldWrap) { - content = {content}; - } - - return content; -} - -function ComponentWithSpreadPropsAndRef({ref, ...other}) { - return ; -} - -// TODO: Support value blocks -function TernaryJsx({cond}) { - return cond ?
: null; -} - -global.DEV = true; -export const FIXTURE_ENTRYPOINT = { - fn: ParentAndChildren, - params: [{foo: 'abc'}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md deleted file mode 100644 index c6e179a6e7fe..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md +++ /dev/null @@ -1,66 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess @enableEmitHookGuards -function App() { - const {foo} = useContext(MyContext); - const {bar} = useContext(MyContext); - return ; -} - -``` - -## Code - -```javascript -import { - $dispatcherGuard, - useContext_withSelector, -} from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess @enableEmitHookGuards -function App() { - const $ = _c(3); - try { - $dispatcherGuard(0); - const { foo } = (function () { - try { - $dispatcherGuard(2); - return useContext_withSelector(MyContext, _temp); - } finally { - $dispatcherGuard(3); - } - })(); - const { bar } = (function () { - try { - $dispatcherGuard(2); - return useContext_withSelector(MyContext, _temp2); - } finally { - $dispatcherGuard(3); - } - })(); - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; - } finally { - $dispatcherGuard(1); - } -} -function _temp2(t0) { - return [t0.bar]; -} -function _temp(t0) { - return [t0.foo]; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js deleted file mode 100644 index da881ea124a4..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js +++ /dev/null @@ -1,6 +0,0 @@ -// @lowerContextAccess @enableEmitHookGuards -function App() { - const {foo} = useContext(MyContext); - const {bar} = useContext(MyContext); - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md deleted file mode 100644 index af9b1df36ad0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ /dev/null @@ -1,42 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const {foo} = useContext(MyContext); - const {bar} = useContext(MyContext); - return ; -} - -``` - -## Code - -```javascript -import { useContext_withSelector } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const { foo } = useContext_withSelector(MyContext, _temp); - const { bar } = useContext_withSelector(MyContext, _temp2); - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} -function _temp2(t0) { - return [t0.bar]; -} -function _temp(t0) { - return [t0.foo]; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.js deleted file mode 100644 index 314ff41c280e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.js +++ /dev/null @@ -1,6 +0,0 @@ -// @lowerContextAccess -function App() { - const {foo} = useContext(MyContext); - const {bar} = useContext(MyContext); - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md deleted file mode 100644 index d13682467b3e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const {foo, bar} = useContext(MyContext); - return ; -} - -``` - -## Code - -```javascript -import { useContext_withSelector } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const { foo, bar } = useContext_withSelector(MyContext, _temp); - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} -function _temp(t0) { - return [t0.foo, t0.bar]; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.js deleted file mode 100644 index 0d53548e67c5..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.js +++ /dev/null @@ -1,5 +0,0 @@ -// @lowerContextAccess -function App() { - const {foo, bar} = useContext(MyContext); - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoization-comments.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoization-comments.expect.md deleted file mode 100644 index eae0b280f02e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoization-comments.expect.md +++ /dev/null @@ -1,78 +0,0 @@ - -## Input - -```javascript -// @enableMemoizationComments -import {addOne, getNumber, identity} from 'shared-runtime'; - -function Component(props) { - const x = identity(props.a); - const y = addOne(x); - const z = identity(props.b); - return [x, y, z]; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{a: 1, b: 10}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableMemoizationComments -import { addOne, getNumber, identity } from "shared-runtime"; - -function Component(props) { - const $ = _c(9); - let t0; - let x; // "useMemo" for t0 and x: - // check if props.a changed - if ($[0] !== props.a) { - // Inputs changed, recompute - x = identity(props.a); - t0 = addOne(x); - $[0] = props.a; - $[1] = t0; - $[2] = x; - } else { - // Inputs did not change, use cached value - t0 = $[1]; - x = $[2]; - } - const y = t0; - let t1; // "useMemo" for t1: - // check if props.b changed - if ($[3] !== props.b) { - // Inputs changed, recompute - t1 = identity(props.b); - $[3] = props.b; - $[4] = t1; - } else { - // Inputs did not change, use cached value - t1 = $[4]; - } - const z = t1; - let t2; // "useMemo" for t2: - // check if x, y, or z changed - if ($[5] !== x || $[6] !== y || $[7] !== z) { - // Inputs changed, recompute - t2 = [x, y, z]; - $[5] = x; - $[6] = y; - $[7] = z; - $[8] = t2; - } else { - // Inputs did not change, use cached value - t2 = $[8]; - } - return t2; -} -export const FIXTURE_ENTRYPOINT = { fn: Component, params: [{ a: 1, b: 10 }] }; - -``` - -### Eval output -(kind: ok) [1,2,10] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoization-comments.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoization-comments.js deleted file mode 100644 index d35993ad053e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoization-comments.js +++ /dev/null @@ -1,14 +0,0 @@ -// @enableMemoizationComments -import {addOne, getNumber, identity} from 'shared-runtime'; - -function Component(props) { - const x = identity(props.a); - const y = addOne(x); - const z = identity(props.b); - return [x, y, z]; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{a: 1, b: 10}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md deleted file mode 100644 index e5a9081137a2..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ /dev/null @@ -1,88 +0,0 @@ - -## Input - -```javascript -// @enableInstructionReordering -import {useState} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Component() { - let [state, setState] = useState(0); - return ( -
- - {state} - -
- ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{value: 42}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableInstructionReordering -import { useState } from "react"; -import { Stringify } from "shared-runtime"; - -function Component() { - const $ = _c(7); - const [state, setState] = useState(0); - let t0; - let t1; - if ($[0] !== state) { - t0 = ( - - ); - t1 = {state}; - $[0] = state; - $[1] = t0; - $[2] = t1; - } else { - t0 = $[1]; - t1 = $[2]; - } - let t2; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { - t2 = ; - $[3] = t2; - } else { - t2 = $[3]; - } - let t3; - if ($[4] !== t0 || $[5] !== t1) { - t3 = ( -
- {t2} - {t1} - {t0} -
- ); - $[4] = t0; - $[5] = t1; - $[6] = t3; - } else { - t3 = $[6]; - } - return t3; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ value: 42 }], -}; - -``` - -### Eval output -(kind: ok)
{"text":"Counter"}
0
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.js deleted file mode 100644 index 137c161b2478..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.js +++ /dev/null @@ -1,21 +0,0 @@ -// @enableInstructionReordering -import {useState} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Component() { - let [state, setState] = useState(0); - return ( -
- - {state} - -
- ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{value: 42}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md deleted file mode 100644 index 0ff9773f76e0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ /dev/null @@ -1,71 +0,0 @@ - -## Input - -```javascript -// @enableInstructionReordering -import {useState} from 'react'; - -function Component() { - const [state, setState] = useState(0); - const onClick = () => { - setState(s => s + 1); - }; - return ( - <> - Count: {state} - - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableInstructionReordering -import { useState } from "react"; - -function Component() { - const $ = _c(4); - const [state, setState] = useState(0); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - setState(_temp); - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const onClick = t0; - let t1; - if ($[1] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; - $[1] = t1; - } else { - t1 = $[1]; - } - let t2; - if ($[2] !== state) { - t2 = ( - <> - Count: {state} - {t1} - - ); - $[2] = state; - $[3] = t2; - } else { - t2 = $[3]; - } - return t2; -} -function _temp(s) { - return s + 1; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.js deleted file mode 100644 index 33dfe347119e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.js +++ /dev/null @@ -1,15 +0,0 @@ -// @enableInstructionReordering -import {useState} from 'react'; - -function Component() { - const [state, setState] = useState(0); - const onClick = () => { - setState(s => s + 1); - }; - return ( - <> - Count: {state} - - - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merged-scopes-are-valid-effect-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merged-scopes-are-valid-effect-deps.expect.md deleted file mode 100644 index bcf1aa0165d8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merged-scopes-are-valid-effect-deps.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validateMemoizedEffectDependencies - -import {useEffect} from 'react'; - -function Component(props) { - const y = [[props.value]]; // merged w scope for inner array - - useEffect(() => { - console.log(y); - }, [y]); // should still be a valid dependency here - - return y; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{value: 42}], - isComponent: false, -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validateMemoizedEffectDependencies - -import { useEffect } from "react"; - -function Component(props) { - const $ = _c(5); - let t0; - if ($[0] !== props.value) { - t0 = [[props.value]]; - $[0] = props.value; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; - let t1; - let t2; - if ($[2] !== y) { - t1 = () => { - console.log(y); - }; - t2 = [y]; - $[2] = y; - $[3] = t1; - $[4] = t2; - } else { - t1 = $[3]; - t2 = $[4]; - } - useEffect(t1, t2); - - return y; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ value: 42 }], - isComponent: false, -}; - -``` - -### Eval output -(kind: ok) [[42]] -logs: [[ [ 42 ] ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merged-scopes-are-valid-effect-deps.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merged-scopes-are-valid-effect-deps.js deleted file mode 100644 index 1ede9dd4ccca..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merged-scopes-are-valid-effect-deps.js +++ /dev/null @@ -1,19 +0,0 @@ -// @validateMemoizedEffectDependencies - -import {useEffect} from 'react'; - -function Component(props) { - const y = [[props.value]]; // merged w scope for inner array - - useEffect(() => { - console.log(y); - }, [y]); // should still be a valid dependency here - - return y; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{value: 42}], - isComponent: false, -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md deleted file mode 100644 index edc266b9f334..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({foo}) { - const arr = []; - // Taking either arr[0].value or arr as a dependency is reasonable - // as long as developers know what to expect. - useEffect(() => print(arr[0]?.value), AUTODEPS); - arr.push({value: foo}); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel -import { useEffect, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function Component(t0) { - const { foo } = t0; - const arr = []; - - useEffect(() => print(arr[0]?.value), [arr[0]?.value]); - arr.push({ value: foo }); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ foo: 1 }], -}; - -``` - -## Logs - -``` -{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} -{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":346},"end":{"line":9,"column":49,"index":393},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":368},"end":{"line":9,"column":27,"index":371},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: ok) [{"value":1}] -logs: [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.js deleted file mode 100644 index ee59d8fe4ea9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.js +++ /dev/null @@ -1,17 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel -import {useEffect, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({foo}) { - const arr = []; - // Taking either arr[0].value or arr as a dependency is reasonable - // as long as developers know what to expect. - useEffect(() => print(arr[0]?.value), AUTODEPS); - arr.push({value: foo}); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md deleted file mode 100644 index acf9c28cabdf..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md +++ /dev/null @@ -1,57 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel - -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({arrRef}) { - // Avoid taking arr.current as a dependency - useEffect(() => print(arrRef.current), AUTODEPS); - arrRef.current.val = 2; - return arrRef; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{arrRef: {current: {val: 'initial ref value'}}}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel - -import { useEffect, useRef, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -function Component(t0) { - const { arrRef } = t0; - - useEffect(() => print(arrRef.current), [arrRef]); - arrRef.current.val = 2; - return arrRef; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ arrRef: { current: { val: "initial ref value" } } }], -}; - -``` - -## Logs - -``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} -{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: ok) {"current":{"val":2}} -logs: [{ val: 2 }] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.js deleted file mode 100644 index 9d0f7e194d0e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.js +++ /dev/null @@ -1,16 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel - -import {useEffect, useRef, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -function Component({arrRef}) { - // Avoid taking arr.current as a dependency - useEffect(() => print(arrRef.current), AUTODEPS); - arrRef.current.val = 2; - return arrRef; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{arrRef: {current: {val: 'initial ref value'}}}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md deleted file mode 100644 index 93e7f1e06096..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md +++ /dev/null @@ -1,56 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel -import {useEffect, AUTODEPS} from 'react'; - -function Component({foo}) { - const arr = []; - useEffect(() => { - arr.push(foo); - }, AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; - -``` - -## Code - -```javascript -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel -import { useEffect, AUTODEPS } from "react"; - -function Component(t0) { - const { foo } = t0; - const arr = []; - useEffect(() => { - arr.push(foo); - }, [arr, foo]); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ foo: 1 }], -}; - -``` - -## Logs - -``` -{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} -{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":191},"end":{"line":8,"column":14,"index":242},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":222},"end":{"line":7,"column":16,"index":225},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: ok) [2] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.js deleted file mode 100644 index 21298604043a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.js +++ /dev/null @@ -1,16 +0,0 @@ -// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel -import {useEffect, AUTODEPS} from 'react'; - -function Component({foo}) { - const arr = []; - useEffect(() => { - arr.push(foo); - }, AUTODEPS); - arr.push(2); - return arr; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{foo: 1}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.expect.md deleted file mode 100644 index 9f9786e4be83..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.expect.md +++ /dev/null @@ -1,66 +0,0 @@ - -## Input - -```javascript -// @inferEffectDependencies @enableNewMutationAliasingModel -import {useEffect, useState, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/* - * setState types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const [_state1, setState1] = useRef('initial value'); - const [_state2, setState2] = useRef('initial value'); - let setState; - if (props.foo) { - setState = setState1; - } else { - setState = setState2; - } - useEffect(() => print(setState), AUTODEPS); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @enableNewMutationAliasingModel -import { useEffect, useState, AUTODEPS } from "react"; -import { print } from "shared-runtime"; - -/* - * setState types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const $ = _c(4); - const [, setState1] = useRef("initial value"); - const [, setState2] = useRef("initial value"); - let setState; - if ($[0] !== props.foo) { - if (props.foo) { - setState = setState1; - } else { - setState = setState2; - } - $[0] = props.foo; - $[1] = setState; - } else { - setState = $[1]; - } - let t0; - if ($[2] !== setState) { - t0 = () => print(setState); - $[2] = setState; - $[3] = t0; - } else { - t0 = $[3]; - } - useEffect(t0, [setState]); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.js deleted file mode 100644 index d35199803243..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.js +++ /dev/null @@ -1,18 +0,0 @@ -// @inferEffectDependencies @enableNewMutationAliasingModel -import {useEffect, useState, AUTODEPS} from 'react'; -import {print} from 'shared-runtime'; - -/* - * setState types are not enough to determine to omit from deps. Must also take reactivity into account. - */ -function ReactiveRefInEffect(props) { - const [_state1, setState1] = useRef('initial value'); - const [_state2, setState2] = useRef('initial value'); - let setState; - if (props.foo) { - setState = setState1; - } else { - setState = setState2; - } - useEffect(() => print(setState), AUTODEPS); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.expect.md deleted file mode 100644 index 05405df219f9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @enableFire @enableNewMutationAliasingModel -import {fire} from 'react'; - -function Component({bar, baz}) { - const foo = () => { - console.log(bar); - }; - useEffect(() => { - fire(foo(bar)); - fire(baz(bar)); - }); - - useEffect(() => { - fire(foo(bar)); - }); - - return null; -} - -``` - -## Code - -```javascript -import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @enableNewMutationAliasingModel -import { fire } from "react"; - -function Component(t0) { - const $ = _c(9); - const { bar, baz } = t0; - let t1; - if ($[0] !== bar) { - t1 = () => { - console.log(bar); - }; - $[0] = bar; - $[1] = t1; - } else { - t1 = $[1]; - } - const foo = t1; - const t2 = useFire(foo); - const t3 = useFire(baz); - let t4; - if ($[2] !== bar || $[3] !== t2 || $[4] !== t3) { - t4 = () => { - t2(bar); - t3(bar); - }; - $[2] = bar; - $[3] = t2; - $[4] = t3; - $[5] = t4; - } else { - t4 = $[5]; - } - useEffect(t4); - let t5; - if ($[6] !== bar || $[7] !== t2) { - t5 = () => { - t2(bar); - }; - $[6] = bar; - $[7] = t2; - $[8] = t5; - } else { - t5 = $[8]; - } - useEffect(t5); - - return null; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.js deleted file mode 100644 index 54d4cf83fe31..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.js +++ /dev/null @@ -1,18 +0,0 @@ -// @enableFire @enableNewMutationAliasingModel -import {fire} from 'react'; - -function Component({bar, baz}) { - const foo = () => { - console.log(bar); - }; - useEffect(() => { - fire(foo(bar)); - fire(baz(bar)); - }); - - useEffect(() => { - fire(foo(bar)); - }); - - return null; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/option-enable-change-variable-codegen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/option-enable-change-variable-codegen.expect.md deleted file mode 100644 index 82f71f4a0501..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/option-enable-change-variable-codegen.expect.md +++ /dev/null @@ -1,47 +0,0 @@ - -## Input - -```javascript -// @enableChangeVariableCodegen -function Component(props) { - const c_0 = [props.a, props.b.c]; - return c_0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{a: 3.14, b: {c: true}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableChangeVariableCodegen -function Component(props) { - const $ = _c(3); - const c_00 = $[0] !== props.a; - const c_1 = $[1] !== props.b.c; - let t0; - if (c_00 || c_1) { - t0 = [props.a, props.b.c]; - $[0] = props.a; - $[1] = props.b.c; - $[2] = t0; - } else { - t0 = $[2]; - } - const c_0 = t0; - return c_0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ a: 3.14, b: { c: true } }], -}; - -``` - -### Eval output -(kind: ok) [3.14,true] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/option-enable-change-variable-codegen.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/option-enable-change-variable-codegen.js deleted file mode 100644 index 4b89fddcf3c6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/option-enable-change-variable-codegen.js +++ /dev/null @@ -1,10 +0,0 @@ -// @enableChangeVariableCodegen -function Component(props) { - const c_0 = [props.a, props.b.c]; - return c_0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{a: 3.14, b: {c: true}}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md index 84c611dec355..d676bb8dad2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; @@ -43,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import { useMemo } from "react"; import { identity, ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js index 373fdc53fa27..445a908c39ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js @@ -1,4 +1,4 @@ -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md index 82c11f7783d4..9f16154c7011 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; @@ -39,7 +39,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import { useMemo } from "react"; import { identity, ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js index 6b55e68bb018..a601957052dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js @@ -1,4 +1,4 @@ -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md index ab4940bcc3ce..13fdd5c15002 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; @@ -44,7 +44,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import { useMemo } from "react"; import { identity, ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js index 820cef20cb2a..85122e62be9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js @@ -1,4 +1,4 @@ -// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md deleted file mode 100644 index e8d1d04b0618..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.expect.md +++ /dev/null @@ -1,78 +0,0 @@ - -## Input - -```javascript -// @enableChangeVariableCodegen -import {identity} from 'shared-runtime'; - -const $ = 'module_$'; -const t0 = 'module_t0'; -const c_0 = 'module_c_0'; -function useFoo(props: {value: number}): number { - const a = () => { - const b = () => { - const c = () => { - console.log($); - console.log(t0); - console.log(c_0); - return identity(props.value); - }; - return c; - }; - return b; - }; - return a()()(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{value: 42}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableChangeVariableCodegen -import { identity } from "shared-runtime"; - -const $ = "module_$"; -const t0 = "module_t0"; -const c_0 = "module_c_0"; -function useFoo(props) { - const $0 = _c(2); - const c_00 = $0[0] !== props.value; - let t1; - if (c_00) { - const a = () => { - const b = () => { - const c = () => { - console.log($); - console.log(t0); - console.log(c_0); - return identity(props.value); - }; - return c; - }; - return b; - }; - t1 = a()()(); - $0[0] = props.value; - $0[1] = t1; - } else { - t1 = $0[1]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ value: 42 }], -}; - -``` - -### Eval output -(kind: ok) 42 -logs: ['module_$','module_t0','module_c_0'] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.js deleted file mode 100644 index 5a3d6a3ef3ac..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-function.js +++ /dev/null @@ -1,26 +0,0 @@ -// @enableChangeVariableCodegen -import {identity} from 'shared-runtime'; - -const $ = 'module_$'; -const t0 = 'module_t0'; -const c_0 = 'module_c_0'; -function useFoo(props: {value: number}): number { - const a = () => { - const b = () => { - const c = () => { - console.log($); - console.log(t0); - console.log(c_0); - return identity(props.value); - }; - return c; - }; - return b; - }; - return a()()(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{value: 42}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md deleted file mode 100644 index 01130cc3a376..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.expect.md +++ /dev/null @@ -1,80 +0,0 @@ - -## Input - -```javascript -// @enableChangeVariableCodegen -import {identity} from 'shared-runtime'; - -const $ = 'module_$'; -const t0 = 'module_t0'; -const c_0 = 'module_c_0'; -function useFoo(props: {value: number}): number { - const a = { - foo() { - const b = { - bar() { - console.log($); - console.log(t0); - console.log(c_0); - return identity(props.value); - }, - }; - return b; - }, - }; - return a.foo().bar(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{value: 42}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableChangeVariableCodegen -import { identity } from "shared-runtime"; - -const $ = "module_$"; -const t0 = "module_t0"; -const c_0 = "module_c_0"; -function useFoo(props) { - const $0 = _c(2); - const c_00 = $0[0] !== props; - let t1; - if (c_00) { - const a = { - foo() { - const b = { - bar() { - console.log($); - console.log(t0); - console.log(c_0); - return identity(props.value); - }, - }; - return b; - }, - }; - t1 = a.foo().bar(); - $0[0] = props; - $0[1] = t1; - } else { - t1 = $0[1]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ value: 42 }], -}; - -``` - -### Eval output -(kind: ok) 42 -logs: ['module_$','module_t0','module_c_0'] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.js deleted file mode 100644 index c1432148a970..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables-nested-object-method.js +++ /dev/null @@ -1,27 +0,0 @@ -// @enableChangeVariableCodegen -import {identity} from 'shared-runtime'; - -const $ = 'module_$'; -const t0 = 'module_t0'; -const c_0 = 'module_c_0'; -function useFoo(props: {value: number}): number { - const a = { - foo() { - const b = { - bar() { - console.log($); - console.log(t0); - console.log(c_0); - return identity(props.value); - }, - }; - return b; - }, - }; - return a.foo().bar(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{value: 42}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables.expect.md deleted file mode 100644 index 71e9680c4e77..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables.expect.md +++ /dev/null @@ -1,62 +0,0 @@ - -## Input - -```javascript -// @enableChangeVariableCodegen -import {identity} from 'shared-runtime'; - -const $ = 'module_$'; -const t0 = 'module_t0'; -const c_0 = 'module_c_0'; -function useFoo(props: {value: number}): number { - const results = identity(props.value); - console.log($); - console.log(t0); - console.log(c_0); - return results; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{value: 0}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableChangeVariableCodegen -import { identity } from "shared-runtime"; - -const $ = "module_$"; -const t0 = "module_t0"; -const c_0 = "module_c_0"; -function useFoo(props) { - const $0 = _c(2); - const c_00 = $0[0] !== props.value; - let t1; - if (c_00) { - t1 = identity(props.value); - $0[0] = props.value; - $0[1] = t1; - } else { - t1 = $0[1]; - } - const results = t1; - console.log($); - console.log(t0); - console.log(c_0); - return results; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ value: 0 }], -}; - -``` - -### Eval output -(kind: ok) 0 -logs: ['module_$','module_t0','module_c_0'] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables.ts deleted file mode 100644 index 38e75a821906..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rename-source-variables.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @enableChangeVariableCodegen -import {identity} from 'shared-runtime'; - -const $ = 'module_$'; -const t0 = 'module_t0'; -const c_0 = 'module_c_0'; -function useFoo(props: {value: number}): number { - const results = identity(props.value); - console.log($); - console.log(t0); - console.log(c_0); - return results; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{value: 0}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md deleted file mode 100644 index b19a06519f75..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md +++ /dev/null @@ -1,27 +0,0 @@ - -## Input - -```javascript -// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire - -component Foo(useDynamicHook) { - useDynamicHook(); - return
hello world
; -} - -``` - -## Code - -```javascript -function Foo({ - useDynamicHook, -}: $ReadOnly<{ useDynamicHook: any }>): React.Node { - useDynamicHook(); - return
hello world
; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js deleted file mode 100644 index 54b18182133d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js +++ /dev/null @@ -1,6 +0,0 @@ -// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire - -component Foo(useDynamicHook) { - useDynamicHook(); - return
hello world
; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md deleted file mode 100644 index 15bc857ac76f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ /dev/null @@ -1,90 +0,0 @@ - -## Input - -```javascript -// @flow @validatePreserveExistingMemoizationGuarantees @enableUseTypeAnnotations -import {useMemo} from 'react'; -import {useFragment} from 'shared-runtime'; - -// This is a version of error.todo-repro-missing-memoization-lack-of-phi-types -// with explicit type annotations and using enableUseTypeAnnotations to demonstrate -// that type information is sufficient to preserve memoization in this example -function Component() { - const data = useFragment(); - const nodes: Array = data.nodes ?? []; - const flatMap: Array = nodes.flatMap(node => node.items); - const filtered: Array = flatMap.filter(item => item != null); - const map: Array = useMemo(() => filtered.map(), [filtered]); - const index: Array = filtered.findIndex(x => x === null); - - return ( -
- {map} - {index} -
- ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useMemo } from "react"; -import { useFragment } from "shared-runtime"; - -function Component() { - const $ = _c(7); - const data = useFragment(); - let t0; - if ($[0] !== data.nodes) { - const nodes = data.nodes ?? []; - const flatMap = nodes.flatMap(_temp); - t0 = flatMap.filter(_temp2); - $[0] = data.nodes; - $[1] = t0; - } else { - t0 = $[1]; - } - const filtered = t0; - let t1; - if ($[2] !== filtered) { - t1 = filtered.map(); - $[2] = filtered; - $[3] = t1; - } else { - t1 = $[3]; - } - const map = t1; - const index = filtered.findIndex(_temp3); - let t2; - if ($[4] !== index || $[5] !== map) { - t2 = ( -
- {map} - {index} -
- ); - $[4] = index; - $[5] = map; - $[6] = t2; - } else { - t2 = $[6]; - } - return t2; -} -function _temp3(x) { - return x === null; -} -function _temp2(item) { - return item != null; -} -function _temp(node) { - return node.items; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.js deleted file mode 100644 index 914ef6199d36..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow @validatePreserveExistingMemoizationGuarantees @enableUseTypeAnnotations -import {useMemo} from 'react'; -import {useFragment} from 'shared-runtime'; - -// This is a version of error.todo-repro-missing-memoization-lack-of-phi-types -// with explicit type annotations and using enableUseTypeAnnotations to demonstrate -// that type information is sufficient to preserve memoization in this example -function Component() { - const data = useFragment(); - const nodes: Array = data.nodes ?? []; - const flatMap: Array = nodes.flatMap(node => node.items); - const filtered: Array = flatMap.filter(item => item != null); - const map: Array = useMemo(() => filtered.map(), [filtered]); - const index: Array = filtered.findIndex(x => x === null); - - return ( -
- {map} - {index} -
- ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md deleted file mode 100644 index 7ac6486b4703..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const [foo, bar] = useContext(MyContext); - return ; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const [foo, bar] = useContext(MyContext); - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.js deleted file mode 100644 index 387c30cdc1e7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.js +++ /dev/null @@ -1,5 +0,0 @@ -// @lowerContextAccess -function App() { - const [foo, bar] = useContext(MyContext); - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md deleted file mode 100644 index 3eac66304be6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const context = useContext(MyContext); - const {foo} = context; - const {bar} = context; - return ; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const context = useContext(MyContext); - const { foo } = context; - const { bar } = context; - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.js deleted file mode 100644 index e7b106e9db86..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.js +++ /dev/null @@ -1,7 +0,0 @@ -// @lowerContextAccess -function App() { - const context = useContext(MyContext); - const {foo} = context; - const {bar} = context; - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md deleted file mode 100644 index 4cca8b19d95c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const context = useContext(MyContext); - const [foo] = context; - const {bar} = context; - return ; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const context = useContext(MyContext); - const [foo] = context; - const { bar } = context; - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.js deleted file mode 100644 index fa511732e3fb..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.js +++ /dev/null @@ -1,7 +0,0 @@ -// @lowerContextAccess -function App() { - const context = useContext(MyContext); - const [foo] = context; - const {bar} = context; - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md deleted file mode 100644 index f5a391662691..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const { - joe: {foo}, - bar, - } = useContext(MyContext); - return ; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const { joe: t0, bar } = useContext(MyContext); - const { foo } = t0; - let t1; - if ($[0] !== bar || $[1] !== foo) { - t1 = ; - $[0] = bar; - $[1] = foo; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.js deleted file mode 100644 index 89a7ccd7db47..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.js +++ /dev/null @@ -1,8 +0,0 @@ -// @lowerContextAccess -function App() { - const { - joe: {foo}, - bar, - } = useContext(MyContext); - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md deleted file mode 100644 index 0888d67b2aa9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @lowerContextAccess -function App() { - const context = useContext(MyContext); - const foo = context.foo; - const bar = context.bar; - return ; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess -function App() { - const $ = _c(3); - const context = useContext(MyContext); - const foo = context.foo; - const bar = context.bar; - let t0; - if ($[0] !== bar || $[1] !== foo) { - t0 = ; - $[0] = bar; - $[1] = foo; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.js deleted file mode 100644 index a055114c1ec7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.js +++ /dev/null @@ -1,7 +0,0 @@ -// @lowerContextAccess -function App() { - const context = useContext(MyContext); - const foo = context.foo; - const bar = context.bar; - return ; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md deleted file mode 100644 index d1fd19c00573..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md +++ /dev/null @@ -1,51 +0,0 @@ - -## Input - -```javascript -// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" -import {fire} from 'react'; -const CapitalizedCall = require('shared-runtime').sum; - -function Component({prop1, bar}) { - const foo = () => { - console.log(prop1); - }; - useEffect(() => { - fire(foo(prop1)); - fire(foo()); - fire(bar()); - }); - - return CapitalizedCall(); -} - -``` - -## Code - -```javascript -import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" -import { fire } from "react"; -const CapitalizedCall = require("shared-runtime").sum; - -function Component(t0) { - const { prop1, bar } = t0; - const foo = () => { - console.log(prop1); - }; - const t1 = useFire(foo); - const t2 = useFire(bar); - - useEffect(() => { - t1(prop1); - t1(); - t2(); - }); - - return CapitalizedCall(); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js deleted file mode 100644 index bd9715e91a2d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js +++ /dev/null @@ -1,16 +0,0 @@ -// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" -import {fire} from 'react'; -const CapitalizedCall = require('shared-runtime').sum; - -function Component({prop1, bar}) { - const foo = () => { - console.log(prop1); - }; - useEffect(() => { - fire(foo(prop1)); - fire(foo()); - fire(bar()); - }); - - return CapitalizedCall(); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md deleted file mode 100644 index 99774bdd3e60..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md +++ /dev/null @@ -1,55 +0,0 @@ - -## Input - -```javascript -// @enableFire @panicThreshold:"none" -import {useRef} from 'react'; - -function Component({props, bar}) { - const foo = () => { - console.log(props); - }; - useEffect(() => { - fire(foo(props)); - fire(foo()); - fire(bar()); - }); - - const ref = useRef(null); - // eslint-disable-next-line react-hooks/rules-of-hooks - ref.current = 'bad'; - return