[compiler] Fix for inferring props-derived-value as mutable (#35140)
Fix for the repro from the previous PR. A `Capture x -> y` effect should downgrade to `ImmutableCapture` when the source value is maybe-frozen. MaybeFrozen represents the union of a frozen value with a non-frozen value. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35140). * __->__ #35140 * #35139
Joseph Savona committed
Nov 14, 2025 at 12:14 UTC
19b769fa5f143f9c23424cd744d85e3742450235
3 files changed
+59
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
+1
@@ -954,6 +954,7 @@ function applyEffect(
954
case ValueKind.Primitive: {
955
break;
956
}
957
+ case ValueKind.MaybeFrozen:
958
case ValueKind.Frozen: {
959
sourceType = 'frozen';
960
break;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/repro-destructure-from-prop-with-default-value.expect.md
new
+45
@@ -0,0 +1,45 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+export function useFormatRelativeTime(opts = {}) {
6
+ const {timeZone, minimal} = opts;
7
+ const format = useCallback(function formatWithUnit() {}, [minimal]);
8
+ // We previously recorded `{timeZone}` as capturing timeZone into the object,
9
+ // then assumed that dateTimeFormat() mutates that object,
10
+ // which in turn could mutate timeZone and the object it came from,
11
+ // which meanteans that the value `minimal` is derived from can change.
12
+ //
13
+ // The fix was to record a Capture from a maybefrozen value as an ImmutableCapture
14
+ // which doesn't propagate mutations
15
+ dateTimeFormat({timeZone});
16
+ return format;
17
+}
18
+
19
+```
20
+
21
+## Code
22
+
23
+```javascript
24
+import { c as _c } from "react/compiler-runtime";
25
+export function useFormatRelativeTime(t0) {
26
+ const $ = _c(1);
27
+ const opts = t0 === undefined ? {} : t0;
28
+ const { timeZone, minimal } = opts;
29
+ let t1;
30
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31
+ t1 = function formatWithUnit() {};
32
+ $[0] = t1;
33
+ } else {
34
+ t1 = $[0];
35
+ }
36
+ const format = t1;
37
+
38
+ dateTimeFormat({ timeZone });
39
+ return format;
40
+}
41
+
42
+```
43
+
44
+### Eval output
45
+(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/repro-destructure-from-prop-with-default-value.js
new
+13
@@ -0,0 +1,13 @@
1
+export function useFormatRelativeTime(opts = {}) {
2
+ const {timeZone, minimal} = opts;
3
+ const format = useCallback(function formatWithUnit() {}, [minimal]);
4
+ // We previously recorded `{timeZone}` as capturing timeZone into the object,
5
+ // then assumed that dateTimeFormat() mutates that object,
6
+ // which in turn could mutate timeZone and the object it came from,
7
+ // which meanteans that the value `minimal` is derived from can change.
8
+ //
9
+ // The fix was to record a Capture from a maybefrozen value as an ImmutableCapture
10
+ // which doesn't propagate mutations
11
+ dateTimeFormat({timeZone});
12
+ return format;
13
+}