diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/aliased-nested-scope-truncated-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/aliased-nested-scope-truncated-dep.expect.md
new file mode 100644
index 0000000000..933fafff5f
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/aliased-nested-scope-truncated-dep.expect.md
@@ -0,0 +1,221 @@
+
+## Input
+
+```javascript
+import {
+ Stringify,
+ mutate,
+ identity,
+ shallowCopy,
+ setPropertyByKey,
+} from 'shared-runtime';
+
+/**
+ * This fixture is similar to `bug-aliased-capture-aliased-mutate` and
+ * `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on
+ * dependency extraction.
+ *
+ * NOTE: this fixture is currently valid, but will break with optimizations:
+ * - Scope and mutable-range based reordering may move the array creation
+ * *after* the `mutate(aliasedObj)` call. This is invalid if mutate
+ * reassigns inner properties.
+ * - RecycleInto or other deeper-equality optimizations may produce invalid
+ * output -- it may compare the array's contents / dependencies too early.
+ * - Runtime validation for immutable values will break if `mutate` does
+ * interior mutation of the value captured into the array.
+ *
+ * Before scope block creation, HIR looks like this:
+ * //
+ * // $1 is unscoped as obj's mutable range will be
+ * // extended in a later pass
+ * //
+ * $1 = LoadLocal obj@0[0:12]
+ * $2 = PropertyLoad $1.id
+ * //
+ * // $3 gets assigned a scope as Array is an allocating
+ * // instruction, but this does *not* get extended or
+ * // merged into the later mutation site.
+ * // (explained in `bug-aliased-capture-aliased-mutate`)
+ * //
+ * $3@1 = Array[$2]
+ * ...
+ * $10@0 = LoadLocal shallowCopy@0[0, 12]
+ * $11 = LoadGlobal mutate
+ * $12 = $11($10@0[0, 12])
+ *
+ * When filling in scope dependencies, we find that it's incorrect to depend on
+ * PropertyLoads from obj as it hasn't completed its mutable range. Following
+ * the immutable / mutable-new typing system, we check the identity of obj to
+ * detect whether it was newly created (and thus mutable) in this render pass.
+ *
+ * HIR with scopes looks like this.
+ * bb0:
+ * $1 = LoadLocal obj@0[0:12]
+ * $2 = PropertyLoad $1.id
+ * scopeTerminal deps=[obj@0] block=bb1 fallt=bb2
+ * bb1:
+ * $3@1 = Array[$2]
+ * goto bb2
+ * bb2:
+ * ...
+ *
+ * This is surprising as deps now is entirely decoupled from temporaries used
+ * by the block itself. scope @1's instructions now reference a value (1)
+ * produced outside its scope range and (2) not represented in its dependencies
+ *
+ * The right thing to do is to ensure that all Loads from a value get assigned
+ * the value's reactive scope. This also requires track mutating and aliasing
+ * separately from scope range. In this example, that would correctly merge
+ * the scopes of $3 with obj.
+ * Runtime validation and optimizations such as ReactiveGraph-based reordering
+ * require this as well.
+ *
+ * A tempting fix is to instead extend $3's ReactiveScope range up to include
+ * $2 (the PropertyLoad). This fixes dependency deduping but not reordering
+ * and mutability.
+ */
+function Component({prop}) {
+ let obj = shallowCopy(prop);
+ const aliasedObj = identity(obj);
+
+ // [obj.id] currently is assigned its own reactive scope
+ const id = [obj.id];
+
+ // Writing to the alias may reassign to previously captured references.
+ // The compiler currently produces valid output, but this breaks with
+ // reordering, recycleInto, and other potential optimizations.
+ mutate(aliasedObj);
+ setPropertyByKey(aliasedObj, 'id', prop.id + 1);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{prop: {id: 1}}],
+ sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import {
+ Stringify,
+ mutate,
+ identity,
+ shallowCopy,
+ setPropertyByKey,
+} from "shared-runtime";
+
+/**
+ * This fixture is similar to `bug-aliased-capture-aliased-mutate` and
+ * `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on
+ * dependency extraction.
+ *
+ * NOTE: this fixture is currently valid, but will break with optimizations:
+ * - Scope and mutable-range based reordering may move the array creation
+ * *after* the `mutate(aliasedObj)` call. This is invalid if mutate
+ * reassigns inner properties.
+ * - RecycleInto or other deeper-equality optimizations may produce invalid
+ * output -- it may compare the array's contents / dependencies too early.
+ * - Runtime validation for immutable values will break if `mutate` does
+ * interior mutation of the value captured into the array.
+ *
+ * Before scope block creation, HIR looks like this:
+ * //
+ * // $1 is unscoped as obj's mutable range will be
+ * // extended in a later pass
+ * //
+ * $1 = LoadLocal obj@0[0:12]
+ * $2 = PropertyLoad $1.id
+ * //
+ * // $3 gets assigned a scope as Array is an allocating
+ * // instruction, but this does *not* get extended or
+ * // merged into the later mutation site.
+ * // (explained in `bug-aliased-capture-aliased-mutate`)
+ * //
+ * $3@1 = Array[$2]
+ * ...
+ * $10@0 = LoadLocal shallowCopy@0[0, 12]
+ * $11 = LoadGlobal mutate
+ * $12 = $11($10@0[0, 12])
+ *
+ * When filling in scope dependencies, we find that it's incorrect to depend on
+ * PropertyLoads from obj as it hasn't completed its mutable range. Following
+ * the immutable / mutable-new typing system, we check the identity of obj to
+ * detect whether it was newly created (and thus mutable) in this render pass.
+ *
+ * HIR with scopes looks like this.
+ * bb0:
+ * $1 = LoadLocal obj@0[0:12]
+ * $2 = PropertyLoad $1.id
+ * scopeTerminal deps=[obj@0] block=bb1 fallt=bb2
+ * bb1:
+ * $3@1 = Array[$2]
+ * goto bb2
+ * bb2:
+ * ...
+ *
+ * This is surprising as deps now is entirely decoupled from temporaries used
+ * by the block itself. scope @1's instructions now reference a value (1)
+ * produced outside its scope range and (2) not represented in its dependencies
+ *
+ * The right thing to do is to ensure that all Loads from a value get assigned
+ * the value's reactive scope. This also requires track mutating and aliasing
+ * separately from scope range. In this example, that would correctly merge
+ * the scopes of $3 with obj.
+ * Runtime validation and optimizations such as ReactiveGraph-based reordering
+ * require this as well.
+ *
+ * A tempting fix is to instead extend $3's ReactiveScope range up to include
+ * $2 (the PropertyLoad). This fixes dependency deduping but not reordering
+ * and mutability.
+ */
+function Component(t0) {
+ const $ = _c(4);
+ const { prop } = t0;
+ let t1;
+ if ($[0] !== prop) {
+ const obj = shallowCopy(prop);
+ const aliasedObj = identity(obj);
+ let t2;
+ if ($[2] !== obj) {
+ t2 = [obj.id];
+ $[2] = obj;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ const id = t2;
+
+ mutate(aliasedObj);
+ setPropertyByKey(aliasedObj, "id", prop.id + 1);
+
+ t1 = ;
+ $[0] = prop;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ prop: { id: 1 } }],
+ sequentialRenders: [
+ { prop: { id: 1 } },
+ { prop: { id: 1 } },
+ { prop: { id: 2 } },
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok)
{"id":[1]}
+{"id":[1]}
+{"id":[2]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/aliased-nested-scope-truncated-dep.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/aliased-nested-scope-truncated-dep.tsx
new file mode 100644
index 0000000000..4d9d7e78fb
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/aliased-nested-scope-truncated-dep.tsx
@@ -0,0 +1,93 @@
+import {
+ Stringify,
+ mutate,
+ identity,
+ shallowCopy,
+ setPropertyByKey,
+} from 'shared-runtime';
+
+/**
+ * This fixture is similar to `bug-aliased-capture-aliased-mutate` and
+ * `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on
+ * dependency extraction.
+ *
+ * NOTE: this fixture is currently valid, but will break with optimizations:
+ * - Scope and mutable-range based reordering may move the array creation
+ * *after* the `mutate(aliasedObj)` call. This is invalid if mutate
+ * reassigns inner properties.
+ * - RecycleInto or other deeper-equality optimizations may produce invalid
+ * output -- it may compare the array's contents / dependencies too early.
+ * - Runtime validation for immutable values will break if `mutate` does
+ * interior mutation of the value captured into the array.
+ *
+ * Before scope block creation, HIR looks like this:
+ * //
+ * // $1 is unscoped as obj's mutable range will be
+ * // extended in a later pass
+ * //
+ * $1 = LoadLocal obj@0[0:12]
+ * $2 = PropertyLoad $1.id
+ * //
+ * // $3 gets assigned a scope as Array is an allocating
+ * // instruction, but this does *not* get extended or
+ * // merged into the later mutation site.
+ * // (explained in `bug-aliased-capture-aliased-mutate`)
+ * //
+ * $3@1 = Array[$2]
+ * ...
+ * $10@0 = LoadLocal shallowCopy@0[0, 12]
+ * $11 = LoadGlobal mutate
+ * $12 = $11($10@0[0, 12])
+ *
+ * When filling in scope dependencies, we find that it's incorrect to depend on
+ * PropertyLoads from obj as it hasn't completed its mutable range. Following
+ * the immutable / mutable-new typing system, we check the identity of obj to
+ * detect whether it was newly created (and thus mutable) in this render pass.
+ *
+ * HIR with scopes looks like this.
+ * bb0:
+ * $1 = LoadLocal obj@0[0:12]
+ * $2 = PropertyLoad $1.id
+ * scopeTerminal deps=[obj@0] block=bb1 fallt=bb2
+ * bb1:
+ * $3@1 = Array[$2]
+ * goto bb2
+ * bb2:
+ * ...
+ *
+ * This is surprising as deps now is entirely decoupled from temporaries used
+ * by the block itself. scope @1's instructions now reference a value (1)
+ * produced outside its scope range and (2) not represented in its dependencies
+ *
+ * The right thing to do is to ensure that all Loads from a value get assigned
+ * the value's reactive scope. This also requires track mutating and aliasing
+ * separately from scope range. In this example, that would correctly merge
+ * the scopes of $3 with obj.
+ * Runtime validation and optimizations such as ReactiveGraph-based reordering
+ * require this as well.
+ *
+ * A tempting fix is to instead extend $3's ReactiveScope range up to include
+ * $2 (the PropertyLoad). This fixes dependency deduping but not reordering
+ * and mutability.
+ */
+function Component({prop}) {
+ let obj = shallowCopy(prop);
+ const aliasedObj = identity(obj);
+
+ // [obj.id] currently is assigned its own reactive scope
+ const id = [obj.id];
+
+ // Writing to the alias may reassign to previously captured references.
+ // The compiler currently produces valid output, but this breaks with
+ // reordering, recycleInto, and other potential optimizations.
+ mutate(aliasedObj);
+ setPropertyByKey(aliasedObj, 'id', prop.id + 1);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{prop: {id: 1}}],
+ sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/array-map-named-callback-cross-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/array-map-named-callback-cross-context.expect.md
new file mode 100644
index 0000000000..c1a6dfb3ea
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/array-map-named-callback-cross-context.expect.md
@@ -0,0 +1,133 @@
+
+## Input
+
+```javascript
+import {Stringify} from 'shared-runtime';
+
+/**
+ * Forked from array-map-simple.js
+ *
+ * Named lambdas (e.g. cb1) may be defined in the top scope of a function and
+ * used in a different lambda (getArrMap1).
+ *
+ * Here, we should try to determine if cb1 is actually called. In this case:
+ * - getArrMap1 is assumed to be called as it's passed to JSX
+ * - cb1 is not assumed to be called since it's only used as a call operand
+ */
+function useFoo({arr1, arr2}) {
+ const cb1 = e => arr1[0].value + e.value;
+ const getArrMap1 = () => arr1.map(cb1);
+ const cb2 = e => arr2[0].value + e.value;
+ const getArrMap2 = () => arr1.map(cb2);
+ return (
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [{arr1: [], arr2: []}],
+ sequentialRenders: [
+ {arr1: [], arr2: []},
+ {arr1: [], arr2: null},
+ {arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { Stringify } from "shared-runtime";
+
+/**
+ * Forked from array-map-simple.js
+ *
+ * Named lambdas (e.g. cb1) may be defined in the top scope of a function and
+ * used in a different lambda (getArrMap1).
+ *
+ * Here, we should try to determine if cb1 is actually called. In this case:
+ * - getArrMap1 is assumed to be called as it's passed to JSX
+ * - cb1 is not assumed to be called since it's only used as a call operand
+ */
+function useFoo(t0) {
+ const $ = _c(13);
+ const { arr1, arr2 } = t0;
+ let t1;
+ if ($[0] !== arr1[0]) {
+ t1 = (e) => arr1[0].value + e.value;
+ $[0] = arr1[0];
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ const cb1 = t1;
+ let t2;
+ if ($[2] !== arr1 || $[3] !== cb1) {
+ t2 = () => arr1.map(cb1);
+ $[2] = arr1;
+ $[3] = cb1;
+ $[4] = t2;
+ } else {
+ t2 = $[4];
+ }
+ const getArrMap1 = t2;
+ let t3;
+ if ($[5] !== arr2) {
+ t3 = (e_0) => arr2[0].value + e_0.value;
+ $[5] = arr2;
+ $[6] = t3;
+ } else {
+ t3 = $[6];
+ }
+ const cb2 = t3;
+ let t4;
+ if ($[7] !== arr1 || $[8] !== cb2) {
+ t4 = () => arr1.map(cb2);
+ $[7] = arr1;
+ $[8] = cb2;
+ $[9] = t4;
+ } else {
+ t4 = $[9];
+ }
+ const getArrMap2 = t4;
+ let t5;
+ if ($[10] !== getArrMap1 || $[11] !== getArrMap2) {
+ t5 = (
+
+ );
+ $[10] = getArrMap1;
+ $[11] = getArrMap2;
+ $[12] = t5;
+ } else {
+ t5 = $[12];
+ }
+ return t5;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [{ arr1: [], arr2: [] }],
+ sequentialRenders: [
+ { arr1: [], arr2: [] },
+ { arr1: [], arr2: null },
+ { arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) {"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}
+{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}
+{"getArrMap1":{"kind":"Function","result":[2,3]},"getArrMap2":{"kind":"Function","result":[0,1]},"shouldInvokeFns":true}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/array-map-named-callback-cross-context.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/array-map-named-callback-cross-context.js
new file mode 100644
index 0000000000..e905656226
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/array-map-named-callback-cross-context.js
@@ -0,0 +1,35 @@
+import {Stringify} from 'shared-runtime';
+
+/**
+ * Forked from array-map-simple.js
+ *
+ * Named lambdas (e.g. cb1) may be defined in the top scope of a function and
+ * used in a different lambda (getArrMap1).
+ *
+ * Here, we should try to determine if cb1 is actually called. In this case:
+ * - getArrMap1 is assumed to be called as it's passed to JSX
+ * - cb1 is not assumed to be called since it's only used as a call operand
+ */
+function useFoo({arr1, arr2}) {
+ const cb1 = e => arr1[0].value + e.value;
+ const getArrMap1 = () => arr1.map(cb1);
+ const cb2 = e => arr2[0].value + e.value;
+ const getArrMap2 = () => arr1.map(cb2);
+ return (
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [{arr1: [], arr2: []}],
+ sequentialRenders: [
+ {arr1: [], arr2: []},
+ {arr1: [], arr2: null},
+ {arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-2-iife.expect.md
new file mode 100644
index 0000000000..2afc5fd25d
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-2-iife.expect.md
@@ -0,0 +1,52 @@
+
+## Input
+
+```javascript
+function bar(a) {
+ let x = [a];
+ let y = {};
+ (function () {
+ y = x[0][1];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [['val1', 'val2']],
+ isComponent: false,
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+function bar(a) {
+ const $ = _c(2);
+ let y;
+ if ($[0] !== a) {
+ const x = [a];
+ y = {};
+
+ y = x[0][1];
+ $[0] = a;
+ $[1] = y;
+ } else {
+ y = $[1];
+ }
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [["val1", "val2"]],
+ isComponent: false,
+};
+
+```
+
+### Eval output
+(kind: ok) "val2"
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-2-iife.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-2-iife.js
new file mode 100644
index 0000000000..4c224e2841
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-2-iife.js
@@ -0,0 +1,15 @@
+function bar(a) {
+ let x = [a];
+ let y = {};
+ (function () {
+ y = x[0][1];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [['val1', 'val2']],
+ isComponent: false,
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-3-iife.expect.md
new file mode 100644
index 0000000000..f0267c3309
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-3-iife.expect.md
@@ -0,0 +1,61 @@
+
+## Input
+
+```javascript
+function bar(a, b) {
+ let x = [a, b];
+ let y = {};
+ let t = {};
+ (function () {
+ y = x[0][1];
+ t = x[1][0];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [
+ [1, 2],
+ [2, 3],
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+function bar(a, b) {
+ const $ = _c(3);
+ let y;
+ if ($[0] !== a || $[1] !== b) {
+ const x = [a, b];
+ y = {};
+ let t = {};
+
+ y = x[0][1];
+ t = x[1][0];
+ $[0] = a;
+ $[1] = b;
+ $[2] = y;
+ } else {
+ y = $[2];
+ }
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [
+ [1, 2],
+ [2, 3],
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) 2
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-3-iife.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-3-iife.js
new file mode 100644
index 0000000000..1afc28a992
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-3-iife.js
@@ -0,0 +1,19 @@
+function bar(a, b) {
+ let x = [a, b];
+ let y = {};
+ let t = {};
+ (function () {
+ y = x[0][1];
+ t = x[1][0];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [
+ [1, 2],
+ [2, 3],
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-4-iife.expect.md
new file mode 100644
index 0000000000..22728aaf43
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-4-iife.expect.md
@@ -0,0 +1,52 @@
+
+## Input
+
+```javascript
+function bar(a) {
+ let x = [a];
+ let y = {};
+ (function () {
+ y = x[0].a[1];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [{a: ['val1', 'val2']}],
+ isComponent: false,
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+function bar(a) {
+ const $ = _c(2);
+ let y;
+ if ($[0] !== a) {
+ const x = [a];
+ y = {};
+
+ y = x[0].a[1];
+ $[0] = a;
+ $[1] = y;
+ } else {
+ y = $[1];
+ }
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [{ a: ["val1", "val2"] }],
+ isComponent: false,
+};
+
+```
+
+### Eval output
+(kind: ok) "val2"
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-4-iife.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-4-iife.js
new file mode 100644
index 0000000000..ca479a7458
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-4-iife.js
@@ -0,0 +1,15 @@
+function bar(a) {
+ let x = [a];
+ let y = {};
+ (function () {
+ y = x[0].a[1];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: [{a: ['val1', 'val2']}],
+ isComponent: false,
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-iife.expect.md
new file mode 100644
index 0000000000..60f829cdc4
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-iife.expect.md
@@ -0,0 +1,50 @@
+
+## Input
+
+```javascript
+function bar(a) {
+ let x = [a];
+ let y = {};
+ (function () {
+ y = x[0];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: ['TodoAdd'],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+function bar(a) {
+ const $ = _c(2);
+ let y;
+ if ($[0] !== a) {
+ const x = [a];
+ y = {};
+
+ y = x[0];
+ $[0] = a;
+ $[1] = y;
+ } else {
+ y = $[1];
+ }
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: ["TodoAdd"],
+};
+
+```
+
+### Eval output
+(kind: ok) "TodoAdd"
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-iife.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-iife.js
new file mode 100644
index 0000000000..9a0c7c19aa
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/capturing-function-alias-computed-load-iife.js
@@ -0,0 +1,14 @@
+function bar(a) {
+ let x = [a];
+ let y = {};
+ (function () {
+ y = x[0];
+ })();
+
+ return y;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: bar,
+ params: ['TodoAdd'],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md
new file mode 100644
index 0000000000..a67d467df8
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md
@@ -0,0 +1,33 @@
+
+## Input
+
+```javascript
+// @validateNoImpureFunctionsInRender
+
+function Component() {
+ const date = Date.now();
+ const now = performance.now();
+ const rand = Math.random();
+ return ;
+}
+
+```
+
+
+## Error
+
+```
+ 2 |
+ 3 | function Component() {
+> 4 | const date = Date.now();
+ | ^^^^^^^^ InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Date.now` is an impure function whose results may change on every call (4:4)
+
+InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `performance.now` is an impure function whose results may change on every call (5:5)
+
+InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Math.random` is an impure function whose results may change on every call (6:6)
+ 5 | const now = performance.now();
+ 6 | const rand = Math.random();
+ 7 | return ;
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.js
new file mode 100644
index 0000000000..6faf98caff
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.js
@@ -0,0 +1,8 @@
+// @validateNoImpureFunctionsInRender
+
+function Component() {
+ const date = Date.now();
+ const now = performance.now();
+ const rand = Math.random();
+ return ;
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.mutate-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.mutate-hook-argument.expect.md
new file mode 100644
index 0000000000..665fc7053b
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.mutate-hook-argument.expect.md
@@ -0,0 +1,24 @@
+
+## Input
+
+```javascript
+function useHook(a, b) {
+ b.test = 1;
+ a.test = 2;
+}
+
+```
+
+
+## Error
+
+```
+ 1 | function useHook(a, b) {
+> 2 | b.test = 1;
+ | ^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead (2:2)
+ 3 | a.test = 2;
+ 4 | }
+ 5 |
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.mutate-hook-argument.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.mutate-hook-argument.js
new file mode 100644
index 0000000000..321e9049cd
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.mutate-hook-argument.js
@@ -0,0 +1,4 @@
+function useHook(a, b) {
+ b.test = 1;
+ a.test = 2;
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md
new file mode 100644
index 0000000000..7d829fe9b0
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md
@@ -0,0 +1,29 @@
+
+## Input
+
+```javascript
+let x = {a: 42};
+
+function Component(props) {
+ foo(() => {
+ x.a = 10;
+ x.a = 20;
+ });
+}
+
+```
+
+
+## Error
+
+```
+ 3 | function Component(props) {
+ 4 | foo(() => {
+> 5 | x.a = 10;
+ | ^ InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (5:5)
+ 6 | x.a = 20;
+ 7 | });
+ 8 | }
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.js
new file mode 100644
index 0000000000..3b44c4c247
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.js
@@ -0,0 +1,8 @@
+let x = {a: 42};
+
+function Component(props) {
+ foo(() => {
+ x.a = 10;
+ x.a = 20;
+ });
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md
new file mode 100644
index 0000000000..e4073947f7
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md
@@ -0,0 +1,29 @@
+
+## Input
+
+```javascript
+function Component() {
+ const foo = () => {
+ // Cannot assign to globals
+ someUnknownGlobal = true;
+ moduleLocal = true;
+ };
+ foo();
+}
+
+```
+
+
+## Error
+
+```
+ 2 | const foo = () => {
+ 3 | // Cannot assign to globals
+> 4 | someUnknownGlobal = true;
+ | ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4)
+ 5 | moduleLocal = true;
+ 6 | };
+ 7 | foo();
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.js
new file mode 100644
index 0000000000..708fe643d5
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.js
@@ -0,0 +1,8 @@
+function Component() {
+ const foo = () => {
+ // Cannot assign to globals
+ someUnknownGlobal = true;
+ moduleLocal = true;
+ };
+ foo();
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md
new file mode 100644
index 0000000000..4619cd27cb
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md
@@ -0,0 +1,26 @@
+
+## Input
+
+```javascript
+function Component() {
+ // Cannot assign to globals
+ someUnknownGlobal = true;
+ moduleLocal = true;
+}
+
+```
+
+
+## Error
+
+```
+ 1 | function Component() {
+ 2 | // Cannot assign to globals
+> 3 | someUnknownGlobal = true;
+ | ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
+ 4 | moduleLocal = true;
+ 5 | }
+ 6 |
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.js
new file mode 100644
index 0000000000..d0509a3d52
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.js
@@ -0,0 +1,5 @@
+function Component() {
+ // Cannot assign to globals
+ someUnknownGlobal = true;
+ moduleLocal = true;
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md
new file mode 100644
index 0000000000..2a935256d7
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md
@@ -0,0 +1,30 @@
+
+## Input
+
+```javascript
+function Component(props) {
+ function hasErrors() {
+ let hasErrors = false;
+ if (props.items == null) {
+ hasErrors = true;
+ }
+ return hasErrors;
+ }
+ return hasErrors();
+}
+
+```
+
+
+## Error
+
+```
+ 7 | return hasErrors;
+ 8 | }
+> 9 | return hasErrors();
+ | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$15 (9:9)
+ 10 | }
+ 11 |
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js
new file mode 100644
index 0000000000..b7a450ccba
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js
@@ -0,0 +1,10 @@
+function Component(props) {
+ function hasErrors() {
+ let hasErrors = false;
+ if (props.items == null) {
+ hasErrors = true;
+ }
+ return hasErrors;
+ }
+ return hasErrors();
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md
new file mode 100644
index 0000000000..e4560848dd
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md
@@ -0,0 +1,58 @@
+
+## Input
+
+```javascript
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+import {useEffect} from 'react';
+import {print} from 'shared-runtime';
+
+function Component({foo}) {
+ const arr = [];
+ // Taking either arr[0].value or arr as a dependency is reasonable
+ // as long as developers know what to expect.
+ useEffect(() => print(arr[0]?.value));
+ arr.push({value: foo});
+ return arr;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{foo: 1}],
+};
+
+```
+
+## Code
+
+```javascript
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+import { useEffect } from "react";
+import { print } from "shared-runtime";
+
+function Component(t0) {
+ const { foo } = t0;
+ const arr = [];
+
+ useEffect(() => print(arr[0]?.value), [arr[0]?.value]);
+ arr.push({ value: foo });
+ return arr;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ foo: 1 }],
+};
+
+```
+
+## Logs
+
+```
+{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}}
+{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
+```
+
+### Eval output
+(kind: ok) [{"value":1}]
+logs: [1]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.js
new file mode 100644
index 0000000000..c435b72d1a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.js
@@ -0,0 +1,17 @@
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+import {useEffect} from 'react';
+import {print} from 'shared-runtime';
+
+function Component({foo}) {
+ const arr = [];
+ // Taking either arr[0].value or arr as a dependency is reasonable
+ // as long as developers know what to expect.
+ useEffect(() => print(arr[0]?.value));
+ arr.push({value: foo});
+ return arr;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{foo: 1}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md
new file mode 100644
index 0000000000..5e6f19dd83
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md
@@ -0,0 +1,57 @@
+
+## Input
+
+```javascript
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+
+import {useEffect, useRef} from 'react';
+import {print} from 'shared-runtime';
+
+function Component({arrRef}) {
+ // Avoid taking arr.current as a dependency
+ useEffect(() => print(arrRef.current));
+ arrRef.current.val = 2;
+ return arrRef;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{arrRef: {current: {val: 'initial ref value'}}}],
+};
+
+```
+
+## Code
+
+```javascript
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+
+import { useEffect, useRef } from "react";
+import { print } from "shared-runtime";
+
+function Component(t0) {
+ const { arrRef } = t0;
+
+ useEffect(() => print(arrRef.current), [arrRef]);
+ arrRef.current.val = 2;
+ return arrRef;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ arrRef: { current: { val: "initial ref value" } } }],
+};
+
+```
+
+## Logs
+
+```
+{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}}
+{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
+```
+
+### Eval output
+(kind: ok) {"current":{"val":2}}
+logs: [{ val: 2 }]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.js
new file mode 100644
index 0000000000..bd3f6d1de5
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.js
@@ -0,0 +1,16 @@
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+
+import {useEffect, useRef} from 'react';
+import {print} from 'shared-runtime';
+
+function Component({arrRef}) {
+ // Avoid taking arr.current as a dependency
+ useEffect(() => print(arrRef.current));
+ arrRef.current.val = 2;
+ return arrRef;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{arrRef: {current: {val: 'initial ref value'}}}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md
new file mode 100644
index 0000000000..3b61fbf834
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md
@@ -0,0 +1,56 @@
+
+## Input
+
+```javascript
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+import {useEffect} from 'react';
+
+function Component({foo}) {
+ const arr = [];
+ useEffect(() => {
+ arr.push(foo);
+ });
+ arr.push(2);
+ return arr;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{foo: 1}],
+};
+
+```
+
+## Code
+
+```javascript
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+import { useEffect } from "react";
+
+function Component(t0) {
+ const { foo } = t0;
+ const arr = [];
+ useEffect(() => {
+ arr.push(foo);
+ }, [arr, foo]);
+ arr.push(2);
+ return arr;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ foo: 1 }],
+};
+
+```
+
+## Logs
+
+```
+{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}}
+{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
+```
+
+### Eval output
+(kind: ok) [2]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.js
new file mode 100644
index 0000000000..fbcbf004a3
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.js
@@ -0,0 +1,16 @@
+// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
+import {useEffect} from 'react';
+
+function Component({foo}) {
+ const arr = [];
+ useEffect(() => {
+ arr.push(foo);
+ });
+ arr.push(2);
+ return arr;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{foo: 1}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-key-object-mutated-later.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-key-object-mutated-later.expect.md
new file mode 100644
index 0000000000..bf0f9da6b1
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-key-object-mutated-later.expect.md
@@ -0,0 +1,69 @@
+
+## Input
+
+```javascript
+import {identity, mutate} from 'shared-runtime';
+
+function Component(props) {
+ const key = {};
+ const context = {
+ [key]: identity([props.value]),
+ };
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { identity, mutate } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(5);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = {};
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ const key = t0;
+ let t1;
+ if ($[1] !== props.value) {
+ t1 = identity([props.value]);
+ $[1] = props.value;
+ $[2] = t1;
+ } else {
+ t1 = $[2];
+ }
+ let t2;
+ if ($[3] !== t1) {
+ t2 = { [key]: t1 };
+ $[3] = t1;
+ $[4] = t2;
+ } else {
+ t2 = $[4];
+ }
+ const context = t2;
+
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"[object Object]":[42]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-key-object-mutated-later.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-key-object-mutated-later.js
new file mode 100644
index 0000000000..1edaaaef27
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-key-object-mutated-later.js
@@ -0,0 +1,15 @@
+import {identity, mutate} from 'shared-runtime';
+
+function Component(props) {
+ const key = {};
+ const context = {
+ [key]: identity([props.value]),
+ };
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-member.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-member.expect.md
new file mode 100644
index 0000000000..810b03e529
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-member.expect.md
@@ -0,0 +1,53 @@
+
+## Input
+
+```javascript
+import {identity, mutate, mutateAndReturn} from 'shared-runtime';
+
+function Component(props) {
+ const key = {a: 'key'};
+ const context = {
+ [key.a]: identity([props.value]),
+ };
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { identity, mutate, mutateAndReturn } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(2);
+ let context;
+ if ($[0] !== props.value) {
+ const key = { a: "key" };
+ context = { [key.a]: identity([props.value]) };
+
+ mutate(key);
+ $[0] = props.value;
+ $[1] = context;
+ } else {
+ context = $[1];
+ }
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"key":[42]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-member.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-member.js
new file mode 100644
index 0000000000..95a1d43462
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/object-expression-computed-member.js
@@ -0,0 +1,15 @@
+import {identity, mutate, mutateAndReturn} from 'shared-runtime';
+
+function Component(props) {
+ const key = {a: 'key'};
+ const context = {
+ [key.a]: identity([props.value]),
+ };
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.expect.md
new file mode 100644
index 0000000000..3af2b9b8b1
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.expect.md
@@ -0,0 +1,60 @@
+
+## Input
+
+```javascript
+// @inferEffectDependencies
+import {useEffect, useState} from 'react';
+import {print} from 'shared-runtime';
+
+/*
+ * setState types are not enough to determine to omit from deps. Must also take reactivity into account.
+ */
+function ReactiveRefInEffect(props) {
+ const [_state1, setState1] = useRef('initial value');
+ const [_state2, setState2] = useRef('initial value');
+ let setState;
+ if (props.foo) {
+ setState = setState1;
+ } else {
+ setState = setState2;
+ }
+ useEffect(() => print(setState));
+}
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
+import { useEffect, useState } from "react";
+import { print } from "shared-runtime";
+
+/*
+ * setState types are not enough to determine to omit from deps. Must also take reactivity into account.
+ */
+function ReactiveRefInEffect(props) {
+ const $ = _c(2);
+ const [, setState1] = useRef("initial value");
+ const [, setState2] = useRef("initial value");
+ let setState;
+ if (props.foo) {
+ setState = setState1;
+ } else {
+ setState = setState2;
+ }
+ let t0;
+ if ($[0] !== setState) {
+ t0 = () => print(setState);
+ $[0] = setState;
+ $[1] = t0;
+ } else {
+ t0 = $[1];
+ }
+ useEffect(t0, [setState]);
+}
+
+```
+
+### 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/new-mutability/reactive-setState.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.js
new file mode 100644
index 0000000000..46a83d8ad4
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/reactive-setState.js
@@ -0,0 +1,18 @@
+// @inferEffectDependencies
+import {useEffect, useState} from 'react';
+import {print} from 'shared-runtime';
+
+/*
+ * setState types are not enough to determine to omit from deps. Must also take reactivity into account.
+ */
+function ReactiveRefInEffect(props) {
+ const [_state1, setState1] = useRef('initial value');
+ const [_state2, setState2] = useRef('initial value');
+ let setState;
+ if (props.foo) {
+ setState = setState1;
+ } else {
+ setState = setState2;
+ }
+ useEffect(() => print(setState));
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md
new file mode 100644
index 0000000000..bd70c0138d
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md
@@ -0,0 +1,64 @@
+
+## Input
+
+```javascript
+// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
+import {print} from 'shared-runtime';
+import useEffectWrapper from 'useEffectWrapper';
+
+function Foo({propVal}) {
+ const arr = [propVal];
+ useEffectWrapper(() => print(arr));
+
+ const arr2 = [];
+ useEffectWrapper(() => arr2.push(propVal));
+ arr2.push(2);
+ return {arr, arr2};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Foo,
+ params: [{propVal: 1}],
+ sequentialRenders: [{propVal: 1}, {propVal: 2}],
+};
+
+```
+
+## Code
+
+```javascript
+// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
+import { print } from "shared-runtime";
+import useEffectWrapper from "useEffectWrapper";
+
+function Foo({ propVal }) {
+ const arr = [propVal];
+ useEffectWrapper(() => print(arr));
+
+ const arr2 = [];
+ useEffectWrapper(() => arr2.push(propVal));
+ arr2.push(2);
+ return { arr, arr2 };
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Foo,
+ params: [{ propVal: 1 }],
+ sequentialRenders: [{ propVal: 1 }, { propVal: 2 }],
+};
+
+```
+
+## Logs
+
+```
+{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}}
+{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]}
+{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
+```
+
+### Eval output
+(kind: ok) {"arr":[1],"arr2":[2]}
+{"arr":[2],"arr2":[2]}
+logs: [[ 1 ],[ 2 ]]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.js
new file mode 100644
index 0000000000..d1dda06a04
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.js
@@ -0,0 +1,19 @@
+// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
+import {print} from 'shared-runtime';
+import useEffectWrapper from 'useEffectWrapper';
+
+function Foo({propVal}) {
+ const arr = [propVal];
+ useEffectWrapper(() => print(arr));
+
+ const arr2 = [];
+ useEffectWrapper(() => arr2.push(propVal));
+ arr2.push(2);
+ return {arr, arr2};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Foo,
+ params: [{propVal: 1}],
+ sequentialRenders: [{propVal: 1}, {propVal: 2}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.expect.md
new file mode 100644
index 0000000000..92dbf9843a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.expect.md
@@ -0,0 +1,80 @@
+
+## Input
+
+```javascript
+// @enableFire
+import {fire} from 'react';
+
+function Component({bar, baz}) {
+ const foo = () => {
+ console.log(bar);
+ };
+ useEffect(() => {
+ fire(foo(bar));
+ fire(baz(bar));
+ });
+
+ useEffect(() => {
+ fire(foo(bar));
+ });
+
+ return null;
+}
+
+```
+
+## Code
+
+```javascript
+import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
+import { fire } from "react";
+
+function Component(t0) {
+ const $ = _c(9);
+ const { bar, baz } = t0;
+ let t1;
+ if ($[0] !== bar) {
+ t1 = () => {
+ console.log(bar);
+ };
+ $[0] = bar;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ const foo = t1;
+ const t2 = useFire(foo);
+ const t3 = useFire(baz);
+ let t4;
+ if ($[2] !== bar || $[3] !== t2 || $[4] !== t3) {
+ t4 = () => {
+ t2(bar);
+ t3(bar);
+ };
+ $[2] = bar;
+ $[3] = t2;
+ $[4] = t3;
+ $[5] = t4;
+ } else {
+ t4 = $[5];
+ }
+ useEffect(t4);
+ let t5;
+ if ($[6] !== bar || $[7] !== t2) {
+ t5 = () => {
+ t2(bar);
+ };
+ $[6] = bar;
+ $[7] = t2;
+ $[8] = t5;
+ } else {
+ t5 = $[8];
+ }
+ useEffect(t5);
+ return null;
+}
+
+```
+
+### 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/new-mutability/shared-hook-calls.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.js
new file mode 100644
index 0000000000..5cb51e9bd3
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/shared-hook-calls.js
@@ -0,0 +1,18 @@
+// @enableFire
+import {fire} from 'react';
+
+function Component({bar, baz}) {
+ const foo = () => {
+ console.log(bar);
+ };
+ useEffect(() => {
+ fire(foo(bar));
+ fire(baz(bar));
+ });
+
+ useEffect(() => {
+ fire(foo(bar));
+ });
+
+ return null;
+}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md
new file mode 100644
index 0000000000..080cc0a74a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md
@@ -0,0 +1,94 @@
+
+## Input
+
+```javascript
+import {useCallback} from 'react';
+import {Stringify} from 'shared-runtime';
+
+function Foo({arr1, arr2, foo}) {
+ const x = [arr1];
+
+ let y = [];
+
+ const getVal1 = useCallback(() => {
+ return {x: 2};
+ }, []);
+
+ const getVal2 = useCallback(() => {
+ return [y];
+ }, [foo ? (y = x.concat(arr2)) : y]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Foo,
+ params: [{arr1: [1, 2], arr2: [3, 4], foo: true}],
+ sequentialRenders: [
+ {arr1: [1, 2], arr2: [3, 4], foo: true},
+ {arr1: [1, 2], arr2: [3, 4], foo: false},
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useCallback } from "react";
+import { Stringify } from "shared-runtime";
+
+function Foo(t0) {
+ const $ = _c(8);
+ const { arr1, arr2, foo } = t0;
+ let getVal1;
+ let t1;
+ if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
+ const x = [arr1];
+
+ let y = [];
+
+ getVal1 = _temp;
+
+ t1 = () => [y];
+ foo ? (y = x.concat(arr2)) : y;
+ $[0] = arr1;
+ $[1] = arr2;
+ $[2] = foo;
+ $[3] = getVal1;
+ $[4] = t1;
+ } else {
+ getVal1 = $[3];
+ t1 = $[4];
+ }
+ const getVal2 = t1;
+ let t2;
+ if ($[5] !== getVal1 || $[6] !== getVal2) {
+ t2 = ;
+ $[5] = getVal1;
+ $[6] = getVal2;
+ $[7] = t2;
+ } else {
+ t2 = $[7];
+ }
+ return t2;
+}
+function _temp() {
+ return { x: 2 };
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Foo,
+ params: [{ arr1: [1, 2], arr2: [3, 4], foo: true }],
+ sequentialRenders: [
+ { arr1: [1, 2], arr2: [3, 4], foo: true },
+ { arr1: [1, 2], arr2: [3, 4], foo: false },
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) {"val1":{"kind":"Function","result":{"x":2}},"val2":{"kind":"Function","result":[[[1,2],3,4]]},"shouldInvokeFns":true}
+{"val1":{"kind":"Function","result":{"x":2}},"val2":{"kind":"Function","result":[[]]},"shouldInvokeFns":true}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx
new file mode 100644
index 0000000000..ba0abc0d7c
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx
@@ -0,0 +1,27 @@
+import {useCallback} from 'react';
+import {Stringify} from 'shared-runtime';
+
+function Foo({arr1, arr2, foo}) {
+ const x = [arr1];
+
+ let y = [];
+
+ const getVal1 = useCallback(() => {
+ return {x: 2};
+ }, []);
+
+ const getVal2 = useCallback(() => {
+ return [y];
+ }, [foo ? (y = x.concat(arr2)) : y]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Foo,
+ params: [{arr1: [1, 2], arr2: [3, 4], foo: true}],
+ sequentialRenders: [
+ {arr1: [1, 2], arr2: [3, 4], foo: true},
+ {arr1: [1, 2], arr2: [3, 4], foo: false},
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md
new file mode 100644
index 0000000000..89a6ad80c3
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md
@@ -0,0 +1,77 @@
+
+## Input
+
+```javascript
+import {useCallback} from 'react';
+import {Stringify} from 'shared-runtime';
+
+// We currently produce invalid output (incorrect scoping for `y` declaration)
+function useFoo(arr1, arr2) {
+ const x = [arr1];
+
+ let y;
+ const getVal = useCallback(() => {
+ return {y};
+ }, [((y = x.concat(arr2)), y)]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [
+ [1, 2],
+ [3, 4],
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useCallback } from "react";
+import { Stringify } from "shared-runtime";
+
+// We currently produce invalid output (incorrect scoping for `y` declaration)
+function useFoo(arr1, arr2) {
+ const $ = _c(5);
+ let t0;
+ if ($[0] !== arr1 || $[1] !== arr2) {
+ const x = [arr1];
+
+ let y;
+ t0 = () => ({ y });
+
+ (y = x.concat(arr2)), y;
+ $[0] = arr1;
+ $[1] = arr2;
+ $[2] = t0;
+ } else {
+ t0 = $[2];
+ }
+ const getVal = t0;
+ let t1;
+ if ($[3] !== getVal) {
+ t1 = ;
+ $[3] = getVal;
+ $[4] = t1;
+ } else {
+ t1 = $[4];
+ }
+ return t1;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [
+ [1, 2],
+ [3, 4],
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) {"getVal":{"kind":"Function","result":{"y":[[1,2],3,4]}},"shouldInvokeFns":true}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx
new file mode 100644
index 0000000000..3ac3845c47
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx
@@ -0,0 +1,22 @@
+import {useCallback} from 'react';
+import {Stringify} from 'shared-runtime';
+
+// We currently produce invalid output (incorrect scoping for `y` declaration)
+function useFoo(arr1, arr2) {
+ const x = [arr1];
+
+ let y;
+ const getVal = useCallback(() => {
+ return {y};
+ }, [((y = x.concat(arr2)), y)]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [
+ [1, 2],
+ [3, 4],
+ ],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md
new file mode 100644
index 0000000000..3fffec6a7d
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md
@@ -0,0 +1,69 @@
+
+## Input
+
+```javascript
+import {useMemo} from 'react';
+
+function useFoo(arr1, arr2) {
+ const x = [arr1];
+
+ let y;
+ return useMemo(() => {
+ return {y};
+ }, [((y = x.concat(arr2)), y)]);
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [
+ [1, 2],
+ [3, 4],
+ ],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useMemo } from "react";
+
+function useFoo(arr1, arr2) {
+ const $ = _c(5);
+ let y;
+ if ($[0] !== arr1 || $[1] !== arr2) {
+ const x = [arr1];
+
+ (y = x.concat(arr2)), y;
+ $[0] = arr1;
+ $[1] = arr2;
+ $[2] = y;
+ } else {
+ y = $[2];
+ }
+ let t0;
+ let t1;
+ if ($[3] !== y) {
+ t1 = { y };
+ $[3] = y;
+ $[4] = t1;
+ } else {
+ t1 = $[4];
+ }
+ t0 = t1;
+ return t0;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [
+ [1, 2],
+ [3, 4],
+ ],
+};
+
+```
+
+### Eval output
+(kind: ok) {"y":[[1,2],3,4]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts
new file mode 100644
index 0000000000..8025d3680f
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts
@@ -0,0 +1,18 @@
+import {useMemo} from 'react';
+
+function useFoo(arr1, arr2) {
+ const x = [arr1];
+
+ let y;
+ return useMemo(() => {
+ return {y};
+ }, [((y = x.concat(arr2)), y)]);
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: useFoo,
+ params: [
+ [1, 2],
+ [3, 4],
+ ],
+};