diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts
index 5e70158bac..e0ddd97941 100644
--- a/compiler/forget/src/HIR/HIR.ts
+++ b/compiler/forget/src/HIR/HIR.ts
@@ -762,6 +762,19 @@ export enum Effect {
Store = "store",
}
+export function isMutableEffect(effect: Effect): boolean {
+ switch (effect) {
+ case Effect.Capture:
+ case Effect.Mutate:
+ case Effect.Store: {
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+}
+
export type ReactiveScope = {
id: ScopeId;
range: MutableRange;
diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts
index 2f5892a1cd..d1eabcba22 100644
--- a/compiler/forget/src/Inference/InferReferenceEffects.ts
+++ b/compiler/forget/src/Inference/InferReferenceEffects.ts
@@ -15,6 +15,7 @@ import {
HIRFunction,
IdentifierId,
InstructionValue,
+ isMutableEffect,
isObjectType,
MethodCall,
Phi,
@@ -679,13 +680,20 @@ function inferBlock(
break;
}
case "FunctionExpression": {
+ let hasMutableOperand = false;
for (const operand of eachInstructionOperand(instr)) {
state.reference(
operand,
operand.effect === Effect.Unknown ? Effect.Read : operand.effect
);
+ hasMutableOperand ||= isMutableEffect(operand.effect);
}
- state.initialize(instrValue, ValueKind.Mutable);
+ // If a closure did not capture any mutable values, then we can consider it to be
+ // frozen, which allows it to be independently memoized.
+ state.initialize(
+ instrValue,
+ hasMutableOperand ? ValueKind.Mutable : ValueKind.Frozen
+ );
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.Store;
continue;
diff --git a/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
index 263d03cace..b48791c364 100644
--- a/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+++ b/compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
@@ -9,10 +9,10 @@ import invariant from "invariant";
import prettyFormat from "pretty-format";
import { CompilerError } from "../CompilerError";
import {
- Effect,
IdentifierId,
InstructionId,
isHookType,
+ isMutableEffect,
Pattern,
Place,
ReactiveFunction,
@@ -702,16 +702,3 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
}
}
}
-
-function isMutableEffect(effect: Effect): boolean {
- switch (effect) {
- case Effect.Capture:
- case Effect.Mutate:
- case Effect.Store: {
- return true;
- }
- default: {
- return false;
- }
- }
-}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/_bug.computed-call-evaluation-order.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/_bug.computed-call-evaluation-order.expect.md
index 178d7de69e..f417b0d3aa 100644
--- a/compiler/forget/src/__tests__/fixtures/compiler/_bug.computed-call-evaluation-order.expect.md
+++ b/compiler/forget/src/__tests__/fixtures/compiler/_bug.computed-call-evaluation-order.expect.md
@@ -29,19 +29,26 @@ function changeF(o) {
}
function Component() {
- const $ = React.unstable_useMemoCache(1);
- let x;
+ const $ = React.unstable_useMemoCache(2);
+ let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
- x = { f: () => console.log("original") };
+ t0 = () => console.log("original");
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ let x;
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
+ x = { f: t0 };
console.log("A");
console.log("B");
changeF(x);
console.log("arg");
x.f(1);
- $[0] = x;
+ $[1] = x;
} else {
- x = $[0];
+ x = $[1];
}
return x;
}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/_bug.property-call-evaluation-order.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/_bug.property-call-evaluation-order.expect.md
index 2cc7b48c22..4b44ec86d2 100644
--- a/compiler/forget/src/__tests__/fixtures/compiler/_bug.property-call-evaluation-order.expect.md
+++ b/compiler/forget/src/__tests__/fixtures/compiler/_bug.property-call-evaluation-order.expect.md
@@ -29,18 +29,25 @@ function changeF(o) {
}
function Component() {
- const $ = React.unstable_useMemoCache(1);
- let x;
+ const $ = React.unstable_useMemoCache(2);
+ let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
- x = { f: () => console.log("original") };
+ t0 = () => console.log("original");
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ let x;
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
+ x = { f: t0 };
console.log("A");
changeF(x);
console.log("arg");
x.f(1);
- $[0] = x;
+ $[1] = x;
} else {
- x = $[0];
+ x = $[1];
}
return x;
}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/array-at-closure.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/array-at-closure.expect.md
index 8e418fad9b..c828bc29c0 100644
--- a/compiler/forget/src/__tests__/fixtures/compiler/array-at-closure.expect.md
+++ b/compiler/forget/src/__tests__/fixtures/compiler/array-at-closure.expect.md
@@ -18,7 +18,7 @@ function Component(props) {
```javascript
function Component(props) {
- const $ = React.unstable_useMemoCache(5);
+ const $ = React.unstable_useMemoCache(7);
const c_0 = $[0] !== props.x;
let t0;
if (c_0) {
@@ -33,18 +33,27 @@ function Component(props) {
const c_3 = $[3] !== x;
let t1;
if (c_2 || c_3) {
- const fn = function () {
+ t1 = function () {
const arr = [...bar(props)];
return arr.at(x);
};
- t1 = fn();
$[2] = props;
$[3] = x;
$[4] = t1;
} else {
t1 = $[4];
}
- const fnResult = t1;
+ const fn = t1;
+ const c_5 = $[5] !== fn;
+ let t2;
+ if (c_5) {
+ t2 = fn();
+ $[5] = fn;
+ $[6] = t2;
+ } else {
+ t2 = $[6];
+ }
+ const fnResult = t2;
return fnResult;
}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/capturing-function-runs-inference.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/capturing-function-runs-inference.expect.md
index 311d191aa4..02579f109e 100644
--- a/compiler/forget/src/__tests__/fixtures/compiler/capturing-function-runs-inference.expect.md
+++ b/compiler/forget/src/__tests__/fixtures/compiler/capturing-function-runs-inference.expect.md
@@ -14,7 +14,7 @@ function component(a, b) {
```javascript
function component(a, b) {
- const $ = React.unstable_useMemoCache(4);
+ const $ = React.unstable_useMemoCache(6);
const c_0 = $[0] !== a;
let t0;
if (c_0) {
@@ -28,14 +28,23 @@ function component(a, b) {
const c_2 = $[2] !== z;
let t1;
if (c_2) {
- const p = () =>