Repository: facebook/react
Pull request: [compiler] Consider setter from useOptimistic non-reactive

Closes https://github.com/facebook/react/issues/35138

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
patch: @@ -23,6 +23,7 @@ import {
   BuiltInUseInsertionEffectHookId,
   BuiltInUseLayoutEffectHookId,
   BuiltInUseOperatorId,
+  BuiltInUseOptimisticId,
   BuiltInUseReducerId,
   BuiltInUseRefId,
   BuiltInUseStateId,
@@ -818,6 +819,18 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
       returnValueKind: ValueKind.Frozen,
     }),
   ],
+  [
+    'useOptimistic',
+    addHook(DEFAULT_SHAPES, {
+      positionalParams: [],
+      restParam: Effect.Freeze,
+      returnType: {kind: 'Object', shapeId: BuiltInUseOptimisticId},
+      calleeEffect: Effect.Read,
+      hookKind: 'useOptimistic',
+      returnValueKind: ValueKind.Frozen,
+      returnValueReason: ValueReason.State,
+    }),
+  ],
   [
     'use',
     addFunction(

filename: compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
patch: @@ -1887,6 +1887,18 @@ export function isStartTransitionType(id: Identifier): boolean {
   );
 }
 
+export function isUseOptimisticType(id: Identifier): boolean {
+  return (
+    id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseOptimistic'
+  );
+}
+
+export function isSetOptimisticType(id: Identifier): boolean {
+  return (
+    id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetOptimistic'
+  );
+}
+
 export function isSetActionStateType(id: Identifier): boolean {
   return (
     id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState'
@@ -1920,7 +1932,8 @@ export function isStableType(id: Identifier): boolean {
     isSetActionStateType(id) ||
     isDispatcherType(id) ||
     isUseRefType(id) ||
-    isStartTransitionType(id)
+    isStartTransitionType(id) ||
+    isSetOptimisticType(id)
   );
 }
 
@@ -1931,8 +1944,9 @@ export function isStableTypeContainer(id: Identifier): boolean {
   }
   return (
     isUseStateType(id) || // setState
-    type_.shapeId === 'BuiltInUseActionState' || // setActionState
+    isUseActionStateType(id) || // setActionState
     isUseReducerType(id) || // dispatcher
+    isUseOptimisticType(id) || // setOptimistic
     type_.shapeId === 'BuiltInUseTransition' // startTransition
   );
 }
@@ -1952,6 +1966,7 @@ export function evaluatesToStableTypeOrContainer(
       case 'useActionState':
       case 'useRef':
       case 'useTransition':
+      case 'useOptimistic':
         return true;
     }
   }

filename: compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
patch: @@ -304,6 +304,7 @@ export type HookKind =
   | 'useTransition'
   | 'useImperativeHandle'
   | 'useEffectEvent'
+  | 'useOptimistic'
   | 'Custom';
 
 /*
@@ -399,6 +400,8 @@ export const BuiltInUseReducerId = 'BuiltInUseReducer';
 export const BuiltInDispatchId = 'BuiltInDispatch';
 export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
 export const BuiltInUseTransitionId = 'BuiltInUseTransition';
+export const BuiltInUseOptimisticId = 'BuiltInUseOptimistic';
+export const BuiltInSetOptimisticId = 'BuiltInSetOptimistic';
 export const BuiltInStartTransitionId = 'BuiltInStartTransition';
 export const BuiltInFireId = 'BuiltInFire';
 export const BuiltInFireFunctionId = 'BuiltInFireFunction';
@@ -1186,6 +1189,25 @@ addObject(BUILTIN_SHAPES, BuiltInUseTransitionId, [
   ],
 ]);
 
+addObject(BUILTIN_SHAPES, BuiltInUseOptimisticId, [
+  ['0', {kind: 'Poly'}],
+  [
+    '1',
+    addFunction(
+      BUILTIN_SHAPES,
+      [],
+      {
+        positionalParams: [],
+        restParam: Effect.Freeze,
+        returnType: PRIMITIVE_TYPE,
+        calleeEffect: Effect.Read,
+        returnValueKind: ValueKind.Primitive,
+      },
+      BuiltInSetOptimisticId,
+    ),
+  ],
+]);
+
 addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
   ['0', {kind: 'Poly'}],
   [

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.expect.md
patch: @@ -0,0 +1,91 @@
+
+## Input
+
+```javascript
+// @validatePreserveExistingMemoizationGuarantees
+import {
+  useCallback,
+  useTransition,
+  useState,
+  useOptimistic,
+  useActionState,
+  useRef,
+  useReducer,
+} from 'react';
+
+function useFoo() {
+  const [s, setState] = useState();
+  const ref = useRef(null);
+  const [t, startTransition] = useTransition();
+  const [u, addOptimistic] = useOptimistic();
+  const [v, dispatch] = useReducer(() => {}, null);
+  const [isPending, dispatchAction] = useActionState(() => {}, null);
+
+  return useCallback(() => {
+    dispatch();
+    startTransition(() => {});
+    addOptimistic();
+    setState(null);
+    dispatchAction();
+    ref.current = true;
+  }, []);
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: useFoo,
+  params: [],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
+import {
+  useCallback,
+  useTransition,
+  useState,
+  useOptimistic,
+  useActionState,
+  useRef,
+  useReducer,
+} from "react";
+
+function useFoo() {
+  const $ = _c(1);
+  const [, setState] = useState();
+  const ref = useRef(null);
+  const [, startTransition] = useTransition();
+  const [, addOptimistic] = useOptimistic();
+  const [, dispatch] = useReducer(_temp, null);
+  const [, dispatchAction] = useActionState(_temp2, null);
+  let t0;
+  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+    t0 = () => {
+      dispatch();
+      startTransition(_temp3);
+      addOptimistic();
+      setState(null);
+      dispatchAction();
+      ref.current = true;
+    };
+    $[0] = t0;
+  } else {
+    t0 = $[0];
+  }
+  return t0;
+}
+function _temp3() {}
+function _temp2() {}
+function _temp() {}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: useFoo,
+  params: [],
+};
+
+```
+      
+### Eval output
+(kind: ok) "[[ function params=0 ]]"
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.ts
patch: @@ -0,0 +1,33 @@
+// @validatePreserveExistingMemoizationGuarantees
+import {
+  useCallback,
+  useTransition,
+  useState,
+  useOptimistic,
+  useActionState,
+  useRef,
+  useReducer,
+} from 'react';
+
+function useFoo() {
+  const [s, setState] = useState();
+  const ref = useRef(null);
+  const [t, startTransition] = useTransition();
+  const [u, addOptimistic] = useOptimistic();
+  const [v, dispatch] = useReducer(() => {}, null);
+  const [isPending, dispatchAction] = useActionState(() => {}, null);
+
+  return useCallback(() => {
+    dispatch();
+    startTransition(() => {});
+    addOptimistic();
+    setState(null);
+    dispatchAction();
+    ref.current = true;
+  }, []);
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: useFoo,
+  params: [],
+};