@samitouri / QOS-React / commits / 28fe581bac

[compiler] Option for preserving calls to useMemo/useCallback

Summary: This adds a compiler option to not drop existing manual memoization and leaving useMemo/useCallback in the generated source. Why do we need this, given that we also have options to validate or ensure that existing memoization is preserved? It's because later diffs on this stack are designed to alter the behavior of the memoization that the compiler emits, in order to detect rules of react violations and debug issues. We don't want to change the behavior of user-level memoization, however, since doing so would be altering the semantics of the user's program in an unacceptable way. ghstack-source-id: 89dccdec9ccb4306b16e849e9fa2170bb5dd021f Pull Request resolved: https://github.com/facebook/react/pull/29654

Mike Vitousek committed May 31, 2024 at 14:02 UTC 28fe581bac10ca91b0d12d95beb034cfe790f3d0
4 files changed +90 -2
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+4 -2
@@ -147,8 +147,10 @@ function* runWithEnvironment(
147 validateContextVariableLValues(hir);
148 validateUseMemo(hir);
149
150 - dropManualMemoization(hir);
151 - yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
150 + if (!env.config.enablePreserveExistingManualUseMemo) {
151 + dropManualMemoization(hir);
152 + yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
153 + }
154
155 inlineImmediatelyInvokedFunctionExpressions(hir);
156 yield log({
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+7
@@ -165,6 +165,13 @@ const EnvironmentConfigSchema = z.object({
165 */
166 validatePreserveExistingMemoizationGuarantees: z.boolean().default(true),
167
168 + /**
169 + * When this is true, rather than pruning existing manual memoization but ensuring or validating
170 + * that the memoized values remain memoized, the compiler will simply not prune existing calls to
171 + * useMemo/useCallback.
172 + */
173 + enablePreserveExistingManualUseMemo: z.boolean().default(false),
174 +
175 // 🌲
176 enableForest: z.boolean().default(false),
177
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-simple-preserved.expect.md new
+66
@@ -0,0 +1,66 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePreserveExistingManualUseMemo
6 +import { useMemo } from "react";
7 +
8 +function Component({ a }) {
9 + let x = useMemo(() => [a], []);
10 + return <div>{x}</div>;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{ a: 42 }],
16 + isComponent: true,
17 +};
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingManualUseMemo
25 +import { useMemo } from "react";
26 +
27 +function Component(t0) {
28 + const $ = _c(5);
29 + const { a } = t0;
30 + let t1;
31 + if ($[0] !== a) {
32 + t1 = () => [a];
33 + $[0] = a;
34 + $[1] = t1;
35 + } else {
36 + t1 = $[1];
37 + }
38 + let t2;
39 + if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
40 + t2 = [];
41 + $[2] = t2;
42 + } else {
43 + t2 = $[2];
44 + }
45 + const x = useMemo(t1, t2);
46 + let t3;
47 + if ($[3] !== x) {
48 + t3 = <div>{x}</div>;
49 + $[3] = x;
50 + $[4] = t3;
51 + } else {
52 + t3 = $[4];
53 + }
54 + return t3;
55 +}
56 +
57 +export const FIXTURE_ENTRYPOINT = {
58 + fn: Component,
59 + params: [{ a: 42 }],
60 + isComponent: true,
61 +};
62 +
63 +```
64 +
65 +### Eval output
66 +(kind: ok) <div>42</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-simple-preserved.js new
+13
@@ -0,0 +1,13 @@
1 +// @enablePreserveExistingManualUseMemo
2 +import { useMemo } from "react";
3 +
4 +function Component({ a }) {
5 + let x = useMemo(() => [a], []);
6 + return <div>{x}</div>;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: Component,
11 + params: [{ a: 42 }],
12 + isComponent: true,
13 +};