Repository: facebook/react
Pull request: [compiler][ez] Patch hoistability for ObjectMethods


Extends #31066 to ObjectMethods (somehow missed this before).

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31197).
* #31204
* #31202
* #31203
* #31201
* #31200
* #31346
* #31199
* #31431
* #31345
* __->__ #31197

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts
patch: @@ -348,7 +348,8 @@ function collectNonNullsInBlocks(
         assumedNonNullObjects.add(maybeNonNull);
       }
       if (
-        instr.value.kind === 'FunctionExpression' &&
+        (instr.value.kind === 'FunctionExpression' ||
+          instr.value.kind === 'ObjectMethod') &&
         !fn.env.config.enableTreatFunctionDepsAsConditional
       ) {
         const innerFn = instr.value.loweredFunc;
@@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads(
 
   for (const [_, block] of fn.body.blocks) {
     for (const {lvalue, value} of block.instructions) {
-      if (value.kind === 'FunctionExpression') {
+      if (
+        value.kind === 'FunctionExpression' ||
+        value.kind === 'ObjectMethod'
+      ) {
         for (const reference of value.loweredFunc.dependencies) {
           let curr: IdentifierId | undefined = reference.identifier.id;
           while (curr != null) {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md
patch: @@ -0,0 +1,82 @@
+
+## Input
+
+```javascript
+// @enablePropagateDepsInHIR
+import {Stringify} from 'shared-runtime';
+
+function Foo({a, shouldReadA}) {
+  return (
+    <Stringify
+      objectMethod={{
+        method() {
+          if (shouldReadA) return a.b.c;
+          return null;
+        },
+      }}
+      shouldInvokeFns={true}
+    />
+  );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: Foo,
+  params: [{a: null, shouldReadA: true}],
+  sequentialRenders: [
+    {a: null, shouldReadA: true},
+    {a: null, shouldReadA: false},
+    {a: {b: {c: 4}}, shouldReadA: true},
+  ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
+import { Stringify } from "shared-runtime";
+
+function Foo(t0) {
+  const $ = _c(3);
+  const { a, shouldReadA } = t0;
+  let t1;
+  if ($[0] !== shouldReadA || $[1] !== a) {
+    t1 = (
+      <Stringify
+        objectMethod={{
+          method() {
+            if (shouldReadA) {
+              return a.b.c;
+            }
+            return null;
+          },
+        }}
+        shouldInvokeFns={true}
+      />
+    );
+    $[0] = shouldReadA;
+    $[1] = a;
+    $[2] = t1;
+  } else {
+    t1 = $[2];
+  }
+  return t1;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: Foo,
+  params: [{ a: null, shouldReadA: true }],
+  sequentialRenders: [
+    { a: null, shouldReadA: true },
+    { a: null, shouldReadA: false },
+    { a: { b: { c: 4 } }, shouldReadA: true },
+  ],
+};
+
+```
+      
+### Eval output
+(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
+<div>{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}</div>
+<div>{"objectMethod":{"method":{"kind":"Function","result":4}},"shouldInvokeFns":true}</div>
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js
patch: @@ -0,0 +1,26 @@
+// @enablePropagateDepsInHIR
+import {Stringify} from 'shared-runtime';
+
+function Foo({a, shouldReadA}) {
+  return (
+    <Stringify
+      objectMethod={{
+        method() {
+          if (shouldReadA) return a.b.c;
+          return null;
+        },
+      }}
+      shouldInvokeFns={true}
+    />
+  );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: Foo,
+  params: [{a: null, shouldReadA: true}],
+  sequentialRenders: [
+    {a: null, shouldReadA: true},
+    {a: null, shouldReadA: false},
+    {a: {b: {c: 4}}, shouldReadA: true},
+  ],
+};