From 3d6e4a723b4b8a2989a633983b6095bf2e95357e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 5 Jun 2025 17:14:54 -0400 Subject: [PATCH] [compiler] Don't include useEffectEvent values in autodeps Summary: useEffectEvent values are not meant to be added to the dep array --- .../src/HIR/Globals.ts | 23 +++++++++ .../src/HIR/HIR.ts | 7 +++ .../src/HIR/ObjectShape.ts | 16 ++++++ .../src/Inference/InferEffectDependencies.ts | 4 +- .../nonreactive-effect-event.expect.md | 49 +++++++++++++++++++ .../nonreactive-effect-event.js | 11 +++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.js 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 b8504494662d6..cc11d0faceb18 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -17,6 +17,7 @@ import { BuiltInSetId, BuiltInUseActionStateId, BuiltInUseContextHookId, + BuiltInUseEffectEventId, BuiltInUseEffectHookId, BuiltInUseInsertionEffectHookId, BuiltInUseLayoutEffectHookId, @@ -27,6 +28,7 @@ import { BuiltInUseTransitionId, BuiltInWeakMapId, BuiltInWeakSetId, + BuiltinEffectEventId, ReanimatedSharedValueId, ShapeRegistry, addFunction, @@ -722,6 +724,27 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ BuiltInFireId, ), ], + [ + 'useEffectEvent', + addHook( + DEFAULT_SHAPES, + { + positionalParams: [], + restParam: Effect.Freeze, + returnType: { + kind: 'Function', + return: {kind: 'Poly'}, + shapeId: BuiltinEffectEventId, + isConstructor: false, + }, + calleeEffect: Effect.Read, + hookKind: 'useEffectEvent', + // Frozen because it should not mutate any locally-bound values + returnValueKind: ValueKind.Frozen, + }, + BuiltInUseEffectEventId, + ), + ], ]; 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 1699a0fc3d292..6c55ff22bc649 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1785,6 +1785,13 @@ export function isFireFunctionType(id: Identifier): boolean { ); } +export function isEffectEventFunctionType(id: Identifier): boolean { + return ( + id.type.kind === 'Function' && + id.type.shapeId === 'BuiltInEffectEventFunction' + ); +} + export function isStableType(id: Identifier): boolean { return ( isSetStateType(id) || 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 03f4120149b0e..a017e1479a22b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -131,6 +131,7 @@ export type HookKind = | 'useCallback' | 'useTransition' | 'useImperativeHandle' + | 'useEffectEvent' | 'Custom'; /* @@ -226,6 +227,8 @@ export const BuiltInUseTransitionId = 'BuiltInUseTransition'; export const BuiltInStartTransitionId = 'BuiltInStartTransition'; export const BuiltInFireId = 'BuiltInFire'; export const BuiltInFireFunctionId = 'BuiltInFireFunction'; +export const BuiltInUseEffectEventId = 'BuiltInUseEffectEvent'; +export const BuiltinEffectEventId = 'BuiltInEffectEventFunction'; // See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types export const ReanimatedSharedValueId = 'ReanimatedSharedValueId'; @@ -948,6 +951,19 @@ addObject(BUILTIN_SHAPES, BuiltInRefValueId, [ ['*', {kind: 'Object', shapeId: BuiltInRefValueId}], ]); +addFunction( + BUILTIN_SHAPES, + [], + { + positionalParams: [], + restParam: Effect.ConditionallyMutate, + returnType: {kind: 'Poly'}, + calleeEffect: Effect.ConditionallyMutate, + returnValueKind: ValueKind.Mutable, + }, + BuiltinEffectEventId, +); + /** * 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 index 4daa2f9fbaee7..eab3c241bcccf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -31,6 +31,7 @@ import { HIR, BasicBlock, BlockId, + isEffectEventFunctionType, } from '../HIR'; import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads'; import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies'; @@ -209,7 +210,8 @@ export function inferEffectDependencies(fn: HIRFunction): void { ((isUseRefType(maybeDep.identifier) || isSetStateType(maybeDep.identifier)) && !reactiveIds.has(maybeDep.identifier.id)) || - isFireFunctionType(maybeDep.identifier) + isFireFunctionType(maybeDep.identifier) || + isEffectEventFunctionType(maybeDep.identifier) ) { // exclude non-reactive hook results, which will never be in a memo block continue; 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 new file mode 100644 index 0000000000000..56c5f6f6f8807 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect, useEffectEvent} 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()); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect, useEffectEvent } 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 new file mode 100644 index 0000000000000..02706c6b23735 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/nonreactive-effect-event.js @@ -0,0 +1,11 @@ +// @inferEffectDependencies +import {useEffect, useEffectEvent} 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()); +}