Repository: facebook/react
Pull request: compiler: Known hooks/nonescaping scopes dont count as pruned

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom):
* __->__ #29820
* #29810
* #29790
* #29789
* #29781

There are two cases where it's legit/intended to remove scopes, and we can inline the scope rather than reify a "pruned" scope:
* Scopes that contain a single instruction with a hook call. The fact that we create a scope in this case at all is just an artifact of it being simpler to do this and remove the scope later rather than try to avoid creating it in the first place. So for these scopes, we can just inline them.
* Scopes that are provably non-escaping. Removing the scope is an optimization, not a case of us having to prune away something that should be there. So again, its fine to inline in this case.

I found this from syncing the stack internally and looking at differences in compiled output. The latter case was most common but the first case is just an obvious improvement.

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
patch: @@ -331,29 +331,8 @@ class CountMemoBlockVisitor extends ReactiveFunctionVisitor<void> {
     scopeBlock: PrunedReactiveScopeBlock,
     state: void
   ): void {
-    let isHookOnlyMemoBlock = false;
-    if (
-      scopeBlock.instructions.length === 1 &&
-      scopeBlock.instructions[0].kind === "instruction"
-    ) {
-      const instr = scopeBlock.instructions[0]!.instruction;
-      if (
-        instr.value.kind === "MethodCall" ||
-        instr.value.kind === "CallExpression"
-      ) {
-        const callee =
-          instr.value.kind === "MethodCall"
-            ? instr.value.property
-            : instr.value.callee;
-        if (getHookKind(this.env, callee.identifier) != null) {
-          isHookOnlyMemoBlock = true;
-        }
-      }
-    }
-    if (!isHookOnlyMemoBlock) {
-      this.prunedMemoBlocks += 1;
-      this.prunedMemoValues += scopeBlock.scope.declarations.size;
-    }
+    this.prunedMemoBlocks += 1;
+    this.prunedMemoValues += scopeBlock.scope.declarations.size;
     this.traversePrunedScope(scopeBlock, state);
   }
 }

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md
patch: @@ -3,10 +3,17 @@
 
 ```javascript
 // @logger
-import { useState } from "react";
-import { identity, makeObject_Primitives, useHook } from "shared-runtime";
+import { createContext, use, useState } from "react";
+import {
+  Stringify,
+  identity,
+  makeObject_Primitives,
+  useHook,
+} from "shared-runtime";
 
 function Component() {
+  const w = use(Context);
+
   // The scopes for x and x2 are interleaved, so this is one scope with two values
   const x = makeObject_Primitives();
   const x2 = makeObject_Primitives();
@@ -26,11 +33,21 @@ function Component() {
   }
 
   // Overall we expect two pruned scopes (for x+x2, and obj), with 3 pruned scope values.
-  return [x, x2, y, z];
+  return <Stringify items={[w, x, x2, y, z]} />;
+}
+
+const Context = createContext();
+
+function Wrapper() {
+  return (
+    <Context value={42}>
+      <Component />
+    </Context>
+  );
 }
 
 export const FIXTURE_ENTRYPOINT = {
-  fn: Component,
+  fn: Wrapper,
   params: [{}],
 };
 
@@ -40,11 +57,17 @@ export const FIXTURE_ENTRYPOINT = {
 
 ```javascript
 import { c as _c } from "react/compiler-runtime"; // @logger
-import { useState } from "react";
-import { identity, makeObject_Primitives, useHook } from "shared-runtime";
+import { createContext, use, useState } from "react";
+import {
+  Stringify,
+  identity,
+  makeObject_Primitives,
+  useHook,
+} from "shared-runtime";
 
 function Component() {
-  const $ = _c(5);
+  const $ = _c(6);
+  const w = use(Context);
 
   const x = makeObject_Primitives();
   const x2 = makeObject_Primitives();
@@ -65,20 +88,39 @@ function Component() {
     z = $[0];
   }
   let t0;
-  if ($[1] !== x || $[2] !== x2 || $[3] !== y) {
-    t0 = [x, x2, y, z];
-    $[1] = x;
-    $[2] = x2;
-    $[3] = y;
-    $[4] = t0;
+  if ($[1] !== w || $[2] !== x || $[3] !== x2 || $[4] !== y) {
+    t0 = <Stringify items={[w, x, x2, y, z]} />;
+    $[1] = w;
+    $[2] = x;
+    $[3] = x2;
+    $[4] = y;
+    $[5] = t0;
+  } else {
+    t0 = $[5];
+  }
+  return t0;
+}
+
+const Context = createContext();
+
+function Wrapper() {
+  const $ = _c(1);
+  let t0;
+  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+    t0 = (
+      <Context value={42}>
+        <Component />
+      </Context>
+    );
+    $[0] = t0;
   } else {
-    t0 = $[4];
+    t0 = $[0];
   }
   return t0;
 }
 
 export const FIXTURE_ENTRYPOINT = {
-  fn: Component,
+  fn: Wrapper,
   params: [{}],
 };
 
@@ -87,8 +129,9 @@ export const FIXTURE_ENTRYPOINT = {
 ## Logs
 
 ```
-{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":121},"end":{"line":26,"column":1,"index":813},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3}
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":161},"end":{"line":33,"column":1,"index":905},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3}
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":941},"end":{"line":43,"column":1,"index":1039},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
 ```
       
 ### Eval output
-(kind: ok) [{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},[{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true}]]
\ No newline at end of file
+(kind: ok) <div>{"items":[42,{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},[{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true}]]}</div>
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js
patch: @@ -1,8 +1,15 @@
 // @logger
-import { useState } from "react";
-import { identity, makeObject_Primitives, useHook } from "shared-runtime";
+import { createContext, use, useState } from "react";
+import {
+  Stringify,
+  identity,
+  makeObject_Primitives,
+  useHook,
+} from "shared-runtime";
 
 function Component() {
+  const w = use(Context);
+
   // The scopes for x and x2 are interleaved, so this is one scope with two values
   const x = makeObject_Primitives();
   const x2 = makeObject_Primitives();
@@ -22,10 +29,20 @@ function Component() {
   }
 
   // Overall we expect two pruned scopes (for x+x2, and obj), with 3 pruned scope values.
-  return [x, x2, y, z];
+  return <Stringify items={[w, x, x2, y, z]} />;
+}
+
+const Context = createContext();
+
+function Wrapper() {
+  return (
+    <Context value={42}>
+      <Component />
+    </Context>
+  );
 }
 
 export const FIXTURE_ENTRYPOINT = {
-  fn: Component,
+  fn: Wrapper,
   params: [{}],
 };