From eee5ca2a92c32103b6bc3c1f7e6cdf1c4807ee56 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:59:59 -0500 Subject: [PATCH 1/3] [compiler] Prune all unused array destructure items during DCE (#31619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We didn't originally support holes within array patterns, so DCE was only able to prune unused items from the end of an array pattern. Now that we support holes we can replace any unused item with a hole, and then just prune the items to the last identifier/spread entry. Note: this was motivated by finding useState where either the state or setState go unused — both are strong indications that you're violating the rules in some way. By DCE-ing the unused portions of the useState destructuring we can easily check if you're ignoring either value. closes #31603 This is a redo of that PR not using ghstack --- .../src/Optimization/DeadCodeElimination.ts | 32 +++++++++---------- .../compiler/concise-arrow-expr.expect.md | 2 +- ...alysis-destructured-rest-element.expect.md | 3 +- ...rtent-mutability-readonly-lambda.expect.md | 2 +- ...ted-callback-from-other-callback.expect.md | 2 +- .../preserve-use-memo-transition.expect.md | 2 +- .../compiler/react-namespace.expect.md | 2 +- ...rol-dependency-phi-setState-type.expect.md | 4 +-- ...ession-of-jsxexpressioncontainer.expect.md | 3 +- .../unused-array-middle-element.expect.md | 2 +- ...patch-considered-as-non-reactive.expect.md | 2 +- ...urned-dispatcher-is-non-reactive.expect.md | 2 +- 12 files changed, 27 insertions(+), 31 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 0202d3ecf043f..2b752c6dfd28e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -6,7 +6,6 @@ */ import { - ArrayPattern, BlockId, HIRFunction, Identifier, @@ -184,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void { switch (instr.value.lvalue.pattern.kind) { case 'ArrayPattern': { /* - * For arrays, we can only eliminate unused items from the end of the array, - * so we iterate from the end and break once we find a used item. Note that - * we already know at least one item is used, from the pruneableValue check. + * For arrays, we can prune items prior to the end by replacing + * them with a hole. Items at the end can simply be dropped. */ - let nextItems: ArrayPattern['items'] | null = null; - const originalItems = instr.value.lvalue.pattern.items; - for (let i = originalItems.length - 1; i >= 0; i--) { - const item = originalItems[i]; + let lastEntryIndex = 0; + const items = instr.value.lvalue.pattern.items; + for (let i = 0; i < items.length; i++) { + const item = items[i]; if (item.kind === 'Identifier') { - if (state.isIdOrNameUsed(item.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } else if (item.kind === 'Spread') { - if (state.isIdOrNameUsed(item.place.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.place.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } } - if (nextItems !== null) { - instr.value.lvalue.pattern.items = nextItems; - } + items.length = lastEntryIndex + 1; break; } case 'ObjectPattern': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md index 787418f75b3fb..996afa1cb224b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md @@ -16,7 +16,7 @@ function component() { import { c as _c } from "react/compiler-runtime"; function component() { const $ = _c(1); - const [x, setX] = useState(0); + const [, setX] = useState(0); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const handler = (v) => setX(v); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md index dfa55302aee84..f38ffba958587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md @@ -35,8 +35,7 @@ function Component(props) { } let d; if ($[2] !== props.c) { - const [c, ...t0] = props.c; - d = t0; + [, ...d] = props.c; $[2] = props.c; $[3] = d; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md index 72433173b17ca..76dfc5ab1ca5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md @@ -24,7 +24,7 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); - const [value, setValue] = useState(null); + const [, setValue] = useState(null); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => setValue((value_0) => value_0 + e.target.value); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md index 710f3ac8ec782..57ca944af6bc9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md @@ -39,7 +39,7 @@ import { useState } from "react"; function Component(props) { const $ = _c(1); - const [_state, setState] = useState(); + const [, setState] = useState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const a = () => b(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md index 941c37bc3f29f..3687198df3a99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md @@ -28,7 +28,7 @@ import { useCallback, useTransition } from "react"; function useFoo() { const $ = _c(1); - const [t, start] = useTransition(); + const [, start] = useTransition(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651bb94..5deb18397d91b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -32,7 +32,7 @@ function Component(props) { const $ = _c(5); React.useContext(FooContext); const ref = React.useRef(); - const [x, setX] = React.useState(false); + const [, setX] = React.useState(false); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md index 26e1f35d5b94a..c56bb7936c1aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md @@ -61,8 +61,8 @@ import { useState } from "react"; function Component(props) { const $ = _c(5); - const [x, setX] = useState(false); - const [y, setY] = useState(false); + const [, setX] = useState(false); + const [, setY] = useState(false); let setState; if (props.cond) { setState = setX; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md index b04c51abc51de..3e71c05a2efbf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md @@ -52,8 +52,7 @@ function Component(props) { const { buttons } = props; let nonPrimaryButtons; if ($[0] !== buttons) { - const [primaryButton, ...t0] = buttons; - nonPrimaryButtons = t0; + [, ...nonPrimaryButtons] = buttons; $[0] = buttons; $[1] = nonPrimaryButtons; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md index 2cf76a57adf40..4ae1fb6e22c43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo(props) { - const [x, unused, y] = props.a; + const [x, , y] = props.a; return x + y; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md index 4181651e3d60f..4b6b94ce431c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md @@ -29,7 +29,7 @@ import { useActionState } from "react"; function Component() { const $ = _c(1); - const [actionState, dispatchAction] = useActionState(); + const [, dispatchAction] = useActionState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const onSubmitAction = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md index f5adab196b7bd..a500af2fc8f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md @@ -30,7 +30,7 @@ import { useReducer } from "react"; function f() { const $ = _c(1); - const [state, dispatch] = useReducer(); + const [, dispatch] = useReducer(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const onClick = () => { From e3b7ef32be6a6d01ea050a10a218538e3a75c64f Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 22 Nov 2024 16:13:42 -0500 Subject: [PATCH 2/3] [crud] Only export uRC when flag is enabled (#31617) It's tricky to do feature detection of uRC currently because it's always present on the export. Let's conditionally export it instead. --- .../src/__tests__/ReactHooksWithNoopRenderer-test.js | 5 +++++ packages/react/src/ReactClient.js | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index a0e84b81d034f..a43b6c124df7d 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -3276,6 +3276,11 @@ describe('ReactHooksWithNoopRenderer', () => { } } + // @gate !enableUseResourceEffectHook + it('is null when flag is disabled', async () => { + expect(useResourceEffect).toBeUndefined(); + }); + // @gate enableUseResourceEffectHook it('validates create return value', async () => { function App({id}) { diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 15f70bf70a747..478d9e026aae8 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -65,6 +65,7 @@ import {startTransition} from './ReactStartTransition'; import {act} from './ReactAct'; import {captureOwnerStack} from './ReactOwnerStack'; import ReactCompilerRuntime from './ReactCompilerRuntime'; +import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; const Children = { map, @@ -90,7 +91,6 @@ export { useContext, useEffect, useEffectEvent as experimental_useEffectEvent, - useResourceEffect as experimental_useResourceEffect, useImperativeHandle, useDebugValue, useInsertionEffect, @@ -131,3 +131,6 @@ export { act, // DEV-only captureOwnerStack, // DEV-only }; + +export const experimental_useResourceEffect: typeof useResourceEffect | void = + enableUseResourceEffectHook ? useResourceEffect : undefined; From 2a9f4c04e54294b668e0a2ae11c1930c2e57b248 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 22 Nov 2024 17:19:20 -0500 Subject: [PATCH 3/3] [compiler] Infer deps configuration (#31616) Adds a way to configure how we insert deps for experimental purposes. ``` [ { module: 'react', imported: 'useEffect', numRequiredArgs: 1, }, { module: 'MyExperimentalEffectHooks', imported: 'useExperimentalEffect', numRequiredArgs: 2, }, ] ``` would insert dependencies for calls of `useEffect` imported from `react` if they have 1 argument and calls of useExperimentalEffect` from `MyExperimentalEffectHooks` if they have 2 arguments. The pushed dep array is appended to the arg list. --- .../src/Entrypoint/Pipeline.ts | 2 +- .../src/HIR/Environment.ts | 51 +++++++++++++++- .../src/Inference/InferEffectDependencies.ts | 46 ++++++++++---- .../infer-deps-custom-config.expect.md | 61 +++++++++++++++++++ .../compiler/infer-deps-custom-config.js | 9 +++ .../infer-effect-dependencies.expect.md | 4 ++ .../compiler/infer-effect-dependencies.js | 2 + compiler/packages/snap/src/compiler.ts | 6 -- .../snap/src/sprout/shared-runtime.ts | 9 +++ 9 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js 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 bbd076e46a914..90921454c864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -356,7 +356,7 @@ function* runWithEnvironment( }); if (env.config.inferEffectDependencies) { - inferEffectDependencies(env, hir); + inferEffectDependencies(hir); } if (env.config.inlineJsxTransform) { 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 432eaf96ff99b..90f53d495b5a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -242,9 +242,40 @@ const EnvironmentConfigSchema = z.object({ enableOptionalDependencies: z.boolean().default(true), /** - * Enables inference and auto-insertion of effect dependencies. Still experimental. + * 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', + * numRequiredArgs: 1, + * },{ + * module: 'MyExperimentalEffectHooks', + * imported: 'useExperimentalEffect', + * numRequiredArgs: 2, + * }, + * ] + * would insert dependencies for calls of `useEffect` imported from `react` and calls of + * useExperimentalEffect` from `MyExperimentalEffectHooks`. + * + * `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency + * array to the end of the call. With the configuration above, we'd insert dependencies for + * `useEffect` if it is only given a single argument and it would be appended to the argument list. + * + * numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies + * + * Still experimental. */ - inferEffectDependencies: z.boolean().default(false), + inferEffectDependencies: z + .nullable( + z.array( + z.object({ + function: ExternalFunctionSchema, + numRequiredArgs: z.number(), + }), + ), + ) + .default(null), /** * Enables inlining ReactElement object literals in place of JSX @@ -614,6 +645,22 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { source: 'react-compiler-runtime', importSpecifierName: 'useContext_withSelector', }, + inferEffectDependencies: [ + { + function: { + source: 'react', + importSpecifierName: 'useEffect', + }, + numRequiredArgs: 1, + }, + { + function: { + source: 'shared-runtime', + importSpecifierName: 'useSpecialEffect', + }, + numRequiredArgs: 2, + }, + ], }; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index 9dc7dff78a6af..9fb91a4ac829b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -8,7 +8,6 @@ import { HIRFunction, IdentifierId, Instruction, - isUseEffectHookType, makeInstructionId, TInstruction, InstructionId, @@ -23,20 +22,33 @@ import { markInstructionIds, } from '../HIR/HIRBuilder'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {getOrInsertWith} from '../Utils/utils'; /** * 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( - env: Environment, - fn: HIRFunction, -): void { +export function inferEffectDependencies(fn: HIRFunction): void { let hasRewrite = false; 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.numRequiredArgs, + ); + } + const autodepFnLoads = new Map(); + const scopeInfos = new Map< ScopeId, {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean} @@ -74,15 +86,23 @@ export function inferEffectDependencies( lvalue.identifier.id, instr as TInstruction, ); + } else if ( + value.kind === 'LoadGlobal' && + value.binding.kind === 'ImportSpecifier' + ) { + const moduleTargets = autodepFnConfigs.get(value.binding.module); + if (moduleTargets != null) { + const numRequiredArgs = moduleTargets.get(value.binding.imported); + if (numRequiredArgs != null) { + autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs); + } + } } else if ( /* - * This check is not final. Right now we only look for useEffects without a dependency array. - * This is likely not how we will ship this feature, but it is good enough for us to make progress - * on the implementation and test it. + * TODO: Handle method calls */ value.kind === 'CallExpression' && - isUseEffectHookType(value.callee.identifier) && - value.args.length === 1 && + autodepFnLoads.get(value.callee.identifier.id) === value.args.length && value.args[0].kind === 'Identifier' ) { const fnExpr = fnExpressions.get(value.args[0].identifier.id); @@ -132,7 +152,7 @@ export function inferEffectDependencies( loc: GeneratedSource, }; - const depsPlace = createTemporaryPlace(env, GeneratedSource); + const depsPlace = createTemporaryPlace(fn.env, GeneratedSource); depsPlace.effect = Effect.Read; newInstructions.push({ @@ -142,8 +162,8 @@ export function inferEffectDependencies( value: deps, }); - // Step 2: insert the deps array as an argument of the useEffect - value.args[1] = {...depsPlace, effect: Effect.Freeze}; + // Step 2: push the inferred deps array as an argument of the useEffect + value.args.push({...depsPlace, effect: Effect.Freeze}); rewriteInstrs.set(instr.id, newInstructions); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md new file mode 100644 index 0000000000000..b439d5dee07e2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {print, useSpecialEffect} from 'shared-runtime'; + +function CustomConfig({propVal}) { + // Insertion + useSpecialEffect(() => print(propVal), [propVal]); + // No insertion + useSpecialEffect(() => print(propVal), [propVal], [propVal]); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { print, useSpecialEffect } from "shared-runtime"; + +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-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js new file mode 100644 index 0000000000000..124e607a8ff48 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js @@ -0,0 +1,9 @@ +// @inferEffectDependencies +import {print, useSpecialEffect} from 'shared-runtime'; + +function CustomConfig({propVal}) { + // Insertion + useSpecialEffect(() => print(propVal), [propVal]); + // No insertion + useSpecialEffect(() => print(propVal), [propVal], [propVal]); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md index febdd005a3cb0..6e45941f24264 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md @@ -3,6 +3,8 @@ ```javascript // @inferEffectDependencies +import {useEffect, useRef} from 'react'; + const moduleNonReactive = 0; function Component({foo, bar}) { @@ -45,6 +47,8 @@ function Component({foo, bar}) { ```javascript import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect, useRef } from "react"; + const moduleNonReactive = 0; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js index 6a70bc1298b0a..723ad6516565f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js @@ -1,4 +1,6 @@ // @inferEffectDependencies +import {useEffect, useRef} from 'react'; + const moduleNonReactive = 0; function Component({foo, bar}) { diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 6942ffb2adc7b..95af40d62a880 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -174,11 +174,6 @@ function makePluginOptions( .filter(s => s.length > 0); } - let inferEffectDependencies = false; - if (firstLine.includes('@inferEffectDependencies')) { - inferEffectDependencies = true; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -202,7 +197,6 @@ function makePluginOptions( hookPattern, validatePreserveExistingMemoizationGuarantees, validateBlocklistedImports, - inferEffectDependencies, }, compilationMode, logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index bd069ae6d0299..58815842cb03c 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -363,6 +363,14 @@ export function useFragment(..._args: Array): object { }; } +export function useSpecialEffect( + fn: () => any, + _secondArg: any, + deps: Array, +) { + React.useEffect(fn, deps); +} + export function typedArrayPush(array: Array, item: T): void { array.push(item); } @@ -370,4 +378,5 @@ export function typedArrayPush(array: Array, item: T): void { export function typedLog(...values: Array): void { console.log(...values); } + export default typedLog;