@samitouri / QOS-React / commits / 19b769fa5f

[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 +}