diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
index 8ef78aa196428..a0e9593268812 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
@@ -2089,7 +2089,7 @@ function computeSignatureForInstruction(
effects.push({
kind: 'Freeze',
value: operand,
- reason: ValueReason.Other,
+ reason: ValueReason.HookCaptured,
});
}
}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts
index 2b4e890a40da8..e440340bd29f3 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts
@@ -175,6 +175,41 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
if (node != null) {
valueBlockNodes.set(fallthrough, node);
}
+ } else if (terminal.kind === 'goto') {
+ /**
+ * If we encounter a goto that is not to the natural fallthrough of the current
+ * block (not the topmost fallthrough on the stack), then this is a goto to a
+ * label. Any scopes that extend beyond the goto must be extended to include
+ * the labeled range, so that the break statement doesn't accidentally jump
+ * out of the scope. We do this by extending the start and end of the scope's
+ * range to the label and its fallthrough respectively.
+ */
+ const start = activeBlockFallthroughRanges.find(
+ range => range.fallthrough === terminal.block,
+ );
+ if (start != null && start !== activeBlockFallthroughRanges.at(-1)) {
+ const fallthroughBlock = fn.body.blocks.get(start.fallthrough)!;
+ const firstId =
+ fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
+ for (const scope of activeScopes) {
+ /**
+ * activeScopes is only filtered at block start points, so some of the
+ * scopes may not actually be active anymore, ie we've past their end
+ * instruction. Only extend ranges for scopes that are actually active.
+ *
+ * TODO: consider pruning activeScopes per instruction
+ */
+ if (scope.range.end <= terminal.id) {
+ continue;
+ }
+ scope.range.start = makeInstructionId(
+ Math.min(start.range.start, scope.range.start),
+ );
+ scope.range.end = makeInstructionId(
+ Math.max(firstId, scope.range.end),
+ );
+ }
+ }
}
/*
diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts
index 3afb00b71a816..5735f7e80115b 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts
@@ -411,7 +411,9 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
this.state = state;
this.options = {
memoizeJsxElements: !this.env.config.enableForest,
- forceMemoizePrimitives: this.env.config.enableForest,
+ forceMemoizePrimitives:
+ this.env.config.enableForest ||
+ this.env.config.enablePreserveExistingMemoizationGuarantees,
};
}
@@ -534,9 +536,23 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
case 'JSXText':
case 'BinaryExpression':
case 'UnaryExpression': {
- const level = options.forceMemoizePrimitives
- ? MemoizationLevel.Memoized
- : MemoizationLevel.Never;
+ if (options.forceMemoizePrimitives) {
+ /**
+ * Because these instructions produce primitives we usually don't consider
+ * them as escape points: they are known to copy, not return references.
+ * However if we're forcing memoization of primitives then we mark these
+ * instructions as needing memoization and walk their rvalues to ensure
+ * any scopes transitively reachable from the rvalues are considered for
+ * memoization. Note: we may still prune primitive-producing scopes if
+ * they don't ultimately escape at all.
+ */
+ const level = MemoizationLevel.Conditional;
+ return {
+ lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
+ rvalues: [...eachReactiveValueOperand(value)],
+ };
+ }
+ const level = MemoizationLevel.Never;
return {
// All of these instructions return a primitive value and never need to be memoized
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
@@ -685,9 +701,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
}
case 'ComputedLoad':
case 'PropertyLoad': {
- const level = options.forceMemoizePrimitives
- ? MemoizationLevel.Memoized
- : MemoizationLevel.Conditional;
+ const level = MemoizationLevel.Conditional;
return {
// Indirection for the inner value, memoized if the value is
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-if.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-if.expect.md
index 7136b3a173f61..03939d16d66e0 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-if.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-if.expect.md
@@ -46,14 +46,16 @@ function useFoo(t0) {
t1 = $[0];
}
let items = t1;
- bb0: if ($[1] !== cond) {
- if (cond) {
- items = [];
- } else {
- break bb0;
- }
+ if ($[1] !== cond) {
+ bb0: {
+ if (cond) {
+ items = [];
+ } else {
+ break bb0;
+ }
- items.push(2);
+ items.push(2);
+ }
$[1] = cond;
$[2] = items;
} else {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping-useMemo.expect.md
new file mode 100644
index 0000000000000..93b08128a0afe
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping-useMemo.expect.md
@@ -0,0 +1,77 @@
+
+## Input
+
+```javascript
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const result = useMemo(
+ () => makeObject(props.value).value + 1,
+ [props.value]
+ );
+ console.log(result);
+ return 'ok';
+}
+
+function makeObject(value) {
+ console.log(value);
+ return {value};
+}
+
+export const TODO_FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [
+ {value: 42},
+ {value: 42},
+ {value: 3.14},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import { useMemo } from "react";
+import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
+
+function Component(props) {
+ const result = makeObject(props.value).value + 1;
+
+ console.log(result);
+ return "ok";
+}
+
+function makeObject(value) {
+ console.log(value);
+ return { value };
+}
+
+export const TODO_FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+ sequentialRenders: [
+ { value: 42 },
+ { value: 42 },
+ { value: 3.14 },
+ { value: 3.14 },
+ { value: 42 },
+ { value: 3.14 },
+ { value: 42 },
+ { value: 3.14 },
+ ],
+};
+
+```
+
+### 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/dont-memoize-primitive-function-call-non-escaping-useMemo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping-useMemo.js
new file mode 100644
index 0000000000000..2ee24917c53e6
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping-useMemo.js
@@ -0,0 +1,32 @@
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const result = useMemo(
+ () => makeObject(props.value).value + 1,
+ [props.value]
+ );
+ console.log(result);
+ return 'ok';
+}
+
+function makeObject(value) {
+ console.log(value);
+ return {value};
+}
+
+export const TODO_FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [
+ {value: 42},
+ {value: 42},
+ {value: 3.14},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping.expect.md
new file mode 100644
index 0000000000000..e2f6c9e6c2ccf
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping.expect.md
@@ -0,0 +1,81 @@
+
+## Input
+
+```javascript
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const result = makeObject(props.value).value + 1;
+ console.log(result);
+ return 'ok';
+}
+
+function makeObject(value) {
+ console.log(value);
+ return {value};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [
+ {value: 42},
+ {value: 42},
+ {value: 3.14},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import { useMemo } from "react";
+import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
+
+function Component(props) {
+ const result = makeObject(props.value).value + 1;
+ console.log(result);
+ return "ok";
+}
+
+function makeObject(value) {
+ console.log(value);
+ return { value };
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+ sequentialRenders: [
+ { value: 42 },
+ { value: 42 },
+ { value: 3.14 },
+ { value: 3.14 },
+ { value: 42 },
+ { value: 3.14 },
+ { value: 42 },
+ { value: 3.14 },
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) "ok"
+"ok"
+"ok"
+"ok"
+"ok"
+"ok"
+"ok"
+"ok"
+logs: [42,43,42,43,3.14,4.140000000000001,3.14,4.140000000000001,42,43,3.14,4.140000000000001,42,43,3.14,4.140000000000001]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping.js
new file mode 100644
index 0000000000000..b4d8d344441c9
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-memoize-primitive-function-call-non-escaping.js
@@ -0,0 +1,29 @@
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const result = makeObject(props.value).value + 1;
+ console.log(result);
+ return 'ok';
+}
+
+function makeObject(value) {
+ console.log(value);
+ return {value};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [
+ {value: 42},
+ {value: 42},
+ {value: 3.14},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-primitive-function-calls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-primitive-function-calls.expect.md
new file mode 100644
index 0000000000000..70e70a26b4656
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-primitive-function-calls.expect.md
@@ -0,0 +1,107 @@
+
+## Input
+
+```javascript
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const result = useMemo(() => {
+ return makeObject(props.value).value + 1;
+ }, [props.value]);
+ return ;
+}
+
+function makeObject(value) {
+ console.log(value);
+ return {value};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [
+ {value: 42},
+ {value: 42},
+ {value: 3.14},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import { useMemo } from "react";
+import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(7);
+ let t0;
+ if ($[0] !== props.value) {
+ t0 = makeObject(props.value);
+ $[0] = props.value;
+ $[1] = t0;
+ } else {
+ t0 = $[1];
+ }
+ const result = t0.value + 1;
+ let t1;
+ if ($[2] !== props.value) {
+ t1 = [props.value];
+ $[2] = props.value;
+ $[3] = t1;
+ } else {
+ t1 = $[3];
+ }
+ let t2;
+ if ($[4] !== result || $[5] !== t1) {
+ t2 = ;
+ $[4] = result;
+ $[5] = t1;
+ $[6] = t2;
+ } else {
+ t2 = $[6];
+ }
+ return t2;
+}
+
+function makeObject(value) {
+ console.log(value);
+ return { value };
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+ sequentialRenders: [
+ { value: 42 },
+ { value: 42 },
+ { value: 3.14 },
+ { value: 3.14 },
+ { value: 42 },
+ { value: 3.14 },
+ { value: 42 },
+ { value: 3.14 },
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok)
{"inputs":[42],"output":43}
+{"inputs":[42],"output":43}
+{"inputs":[3.14],"output":4.140000000000001}
+{"inputs":[3.14],"output":4.140000000000001}
+{"inputs":[42],"output":43}
+{"inputs":[3.14],"output":4.140000000000001}
+{"inputs":[42],"output":43}
+{"inputs":[3.14],"output":4.140000000000001}
+logs: [42,3.14,42,3.14,42,3.14]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-primitive-function-calls.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-primitive-function-calls.js
new file mode 100644
index 0000000000000..5c7cefb609c46
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-primitive-function-calls.js
@@ -0,0 +1,30 @@
+// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
+import {useMemo} from 'react';
+import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
+
+function Component(props) {
+ const result = useMemo(() => {
+ return makeObject(props.value).value + 1;
+ }, [props.value]);
+ return ;
+}
+
+function makeObject(value) {
+ console.log(value);
+ return {value};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [
+ {value: 42},
+ {value: 42},
+ {value: 3.14},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ {value: 42},
+ {value: 3.14},
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.expect.md
index 7f1fb96617e33..90de08f333520 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.expect.md
@@ -2,6 +2,7 @@
## Input
```javascript
+// @compilationMode:"infer"
import {makeArray} from 'shared-runtime';
function Component() {
@@ -30,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
-import { c as _c } from "react/compiler-runtime";
+import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
import { makeArray } from "shared-runtime";
function Component() {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.js
index 352e2e5c19ba7..41aebae7e335d 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-nesting.js
@@ -1,3 +1,4 @@
+// @compilationMode:"infer"
import {makeArray} from 'shared-runtime';
function Component() {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mutation-within-jsx-and-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mutation-within-jsx-and-break.expect.md
index 1d0f40e29f518..17a8524eeee8b 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mutation-within-jsx-and-break.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/mutation-within-jsx-and-break.expect.md
@@ -49,12 +49,12 @@ import {
} from "shared-runtime";
function useFoo(t0) {
- const $ = _c(3);
+ const $ = _c(4);
const { data } = t0;
let obj;
let myDiv = null;
- bb0: if (data.cond) {
- if ($[0] !== data.cond1) {
+ if ($[0] !== data.cond || $[1] !== data.cond1) {
+ bb0: if (data.cond) {
obj = makeObject_Primitives();
if (data.cond1) {
myDiv = ;
@@ -62,13 +62,14 @@ function useFoo(t0) {
}
mutate(obj);
- $[0] = data.cond1;
- $[1] = obj;
- $[2] = myDiv;
- } else {
- obj = $[1];
- myDiv = $[2];
}
+ $[0] = data.cond;
+ $[1] = data.cond1;
+ $[2] = obj;
+ $[3] = myDiv;
+ } else {
+ obj = $[2];
+ myDiv = $[3];
}
return myDiv;
}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.expect.md
new file mode 100644
index 0000000000000..bafbb5c5ef37a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.expect.md
@@ -0,0 +1,86 @@
+
+## Input
+
+```javascript
+// @enablePreserveExistingMemoizationGuarantees
+import {fbt} from 'fbt';
+
+function Component() {
+ const buttonLabel = () => {
+ if (!someCondition) {
+ return {'Purchase as a gift'};
+ } else if (
+ !iconOnly &&
+ showPrice &&
+ item?.current_gift_offer?.price?.formatted != null
+ ) {
+ return (
+
+ {'Gift | '}
+
+ {item?.current_gift_offer?.price?.formatted}
+
+
+ );
+ } else if (!iconOnly && !showPrice) {
+ return {'Gift'};
+ }
+ };
+
+ return (
+
+
+
+ );
+}
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
+import { fbt } from "fbt";
+
+function Component() {
+ const $ = _c(1);
+ const buttonLabel = _temp;
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = (
+
+
+
+ );
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+function _temp() {
+ if (!someCondition) {
+ return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
+ } else {
+ if (
+ !iconOnly &&
+ showPrice &&
+ item?.current_gift_offer?.price?.formatted != null
+ ) {
+ return fbt._(
+ "Gift | {price}",
+ [fbt._param("price", item?.current_gift_offer?.price?.formatted)],
+ { hk: "3GTnGE" },
+ );
+ } else {
+ if (!iconOnly && !showPrice) {
+ return fbt._("Gift", null, { hk: "3fqfrk" });
+ }
+ }
+ }
+}
+
+```
+
+### 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/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.js
new file mode 100644
index 0000000000000..6b49c4a1fb73b
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-existing-memoization-guarantees/lambda-with-fbt-preserve-memoization.js
@@ -0,0 +1,31 @@
+// @enablePreserveExistingMemoizationGuarantees
+import {fbt} from 'fbt';
+
+function Component() {
+ const buttonLabel = () => {
+ if (!someCondition) {
+ return {'Purchase as a gift'};
+ } else if (
+ !iconOnly &&
+ showPrice &&
+ item?.current_gift_offer?.price?.formatted != null
+ ) {
+ return (
+
+ {'Gift | '}
+
+ {item?.current_gift_offer?.price?.formatted}
+
+
+ );
+ } else if (!iconOnly && !showPrice) {
+ return {'Gift'};
+ }
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md
index de39fc9706a69..6c2170feb6193 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md
@@ -2,7 +2,7 @@
## Input
```javascript
-// @enableForest
+// @enablePreserveExistingMemoizationGuarantees
function Component({base, start, increment, test}) {
let value = base;
for (let i = start; i < test; i += increment) {
@@ -27,25 +27,23 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
-import { c as _c } from "react/compiler-runtime"; // @enableForest
+import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
function Component(t0) {
- const $ = _c(5);
+ const $ = _c(2);
const { base, start, increment, test } = t0;
- let value;
- if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) {
- value = base;
- for (let i = start; i < test; i = i + increment, i) {
- value = value + i;
- }
- $[0] = base;
- $[1] = increment;
- $[2] = start;
- $[3] = test;
- $[4] = value;
+ let value = base;
+ for (let i = start; i < test; i = i + increment, i) {
+ value = value + i;
+ }
+ let t1;
+ if ($[0] !== value) {
+ t1 = {value}
;
+ $[0] = value;
+ $[1] = t1;
} else {
- value = $[4];
+ t1 = $[1];
}
- return {value}
;
+ return t1;
}
export const FIXTURE_ENTRYPOINT = {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.js
index cb4fc2050d94b..cce3123f94eeb 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.js
@@ -1,4 +1,4 @@
-// @enableForest
+// @enablePreserveExistingMemoizationGuarantees
function Component({base, start, increment, test}) {
let value = base;
for (let i = start; i < test; i += increment) {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md
index a75b592b83232..4ce8ef58028cf 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/useMemo-multiple-if-else.expect.md
@@ -34,17 +34,16 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
import { useMemo } from "react";
function Component(props) {
- const $ = _c(6);
+ const $ = _c(5);
let t0;
- bb0: {
- let y;
- if (
- $[0] !== props.a ||
- $[1] !== props.b ||
- $[2] !== props.cond ||
- $[3] !== props.cond2
- ) {
- y = [];
+ if (
+ $[0] !== props.a ||
+ $[1] !== props.b ||
+ $[2] !== props.cond ||
+ $[3] !== props.cond2
+ ) {
+ bb0: {
+ const y = [];
if (props.cond) {
y.push(props.a);
}
@@ -54,17 +53,15 @@ function Component(props) {
}
y.push(props.b);
- $[0] = props.a;
- $[1] = props.b;
- $[2] = props.cond;
- $[3] = props.cond2;
- $[4] = y;
- $[5] = t0;
- } else {
- y = $[4];
- t0 = $[5];
+ t0 = y;
}
- t0 = y;
+ $[0] = props.a;
+ $[1] = props.b;
+ $[2] = props.cond;
+ $[3] = props.cond2;
+ $[4] = t0;
+ } else {
+ t0 = $[4];
}
const x = t0;
return x;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-useMemo-if-else-both-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-useMemo-if-else-both-early-return.expect.md
new file mode 100644
index 0000000000000..acb3b72e3a44b
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-useMemo-if-else-both-early-return.expect.md
@@ -0,0 +1,118 @@
+
+## Input
+
+```javascript
+import {useMemo} from 'react';
+import {
+ makeObject_Primitives,
+ mutate,
+ Stringify,
+ ValidateMemoization,
+} from 'shared-runtime';
+
+function Component({cond}) {
+ const memoized = useMemo(() => {
+ const value = makeObject_Primitives();
+ if (cond) {
+ return value;
+ } else {
+ mutate(value);
+ return value;
+ }
+ }, [cond]);
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{cond: false}],
+ sequentialRenders: [
+ {cond: false},
+ {cond: false},
+ {cond: true},
+ {cond: true},
+ {cond: false},
+ {cond: true},
+ {cond: false},
+ {cond: true},
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useMemo } from "react";
+import {
+ makeObject_Primitives,
+ mutate,
+ Stringify,
+ ValidateMemoization,
+} from "shared-runtime";
+
+function Component(t0) {
+ const $ = _c(7);
+ const { cond } = t0;
+ let t1;
+ if ($[0] !== cond) {
+ const value = makeObject_Primitives();
+ if (cond) {
+ t1 = value;
+ } else {
+ mutate(value);
+ t1 = value;
+ }
+ $[0] = cond;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ const memoized = t1;
+ let t2;
+ if ($[2] !== cond) {
+ t2 = [cond];
+ $[2] = cond;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ let t3;
+ if ($[4] !== memoized || $[5] !== t2) {
+ t3 = ;
+ $[4] = memoized;
+ $[5] = t2;
+ $[6] = t3;
+ } else {
+ t3 = $[6];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ cond: false }],
+ sequentialRenders: [
+ { cond: false },
+ { cond: false },
+ { cond: true },
+ { cond: true },
+ { cond: false },
+ { cond: true },
+ { cond: false },
+ { cond: true },
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) {"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
+{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
+{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}
+{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}
+{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
+{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}
+{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}
+{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-useMemo-if-else-both-early-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-useMemo-if-else-both-early-return.js
new file mode 100644
index 0000000000000..d381661077131
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-useMemo-if-else-both-early-return.js
@@ -0,0 +1,35 @@
+import {useMemo} from 'react';
+import {
+ makeObject_Primitives,
+ mutate,
+ Stringify,
+ ValidateMemoization,
+} from 'shared-runtime';
+
+function Component({cond}) {
+ const memoized = useMemo(() => {
+ const value = makeObject_Primitives();
+ if (cond) {
+ return value;
+ } else {
+ mutate(value);
+ return value;
+ }
+ }, [cond]);
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{cond: false}],
+ sequentialRenders: [
+ {cond: false},
+ {cond: false},
+ {cond: true},
+ {cond: true},
+ {cond: false},
+ {cond: true},
+ {cond: false},
+ {cond: true},
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md
index 23b05b548221f..7f4b8af9650f6 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md
@@ -33,17 +33,16 @@ import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
function Component(props) {
- const $ = _c(6);
+ const $ = _c(5);
let t0;
- bb0: {
- let y;
- if (
- $[0] !== props.a ||
- $[1] !== props.b ||
- $[2] !== props.cond ||
- $[3] !== props.cond2
- ) {
- y = [];
+ if (
+ $[0] !== props.a ||
+ $[1] !== props.b ||
+ $[2] !== props.cond ||
+ $[3] !== props.cond2
+ ) {
+ bb0: {
+ const y = [];
if (props.cond) {
y.push(props.a);
}
@@ -53,17 +52,15 @@ function Component(props) {
}
y.push(props.b);
- $[0] = props.a;
- $[1] = props.b;
- $[2] = props.cond;
- $[3] = props.cond2;
- $[4] = y;
- $[5] = t0;
- } else {
- y = $[4];
- t0 = $[5];
+ t0 = y;
}
- t0 = y;
+ $[0] = props.a;
+ $[1] = props.b;
+ $[2] = props.cond;
+ $[3] = props.cond2;
+ $[4] = t0;
+ } else {
+ t0 = $[4];
}
const x = t0;
return x;