[compiler][fix] mutableOnlyIfOperandsAreMutable does not apply when operands are globals (#32695)
Globals, module locals, and other locally defined functions may mutate their arguments. See test fixtures for details --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32695). * #32698 * #32697 * #32696 * __->__ #32695
mofeiZ committed
Mar 23, 2025 at 23:07 UTC
febc09b480903bb803455dc38dc130007d3a2e91
12 files changed
+709
-30
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+98
-12
@@ -11,6 +11,7 @@ import {
11
BuiltInArrayId,
12
BuiltInFireId,
13
BuiltInMixedReadonlyId,
14
+ BuiltInObjectId,
15
BuiltInUseActionStateId,
16
BuiltInUseContextHookId,
17
BuiltInUseEffectHookId,
@@ -45,21 +46,17 @@ export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
46
47
// Hack until we add ObjectShapes for all globals
48
const UNTYPED_GLOBALS: Set<string> = new Set([
48
- 'String',
49
'Object',
50
'Function',
51
- 'Number',
51
'RegExp',
52
'Date',
53
'Error',
55
- 'Function',
54
'TypeError',
55
'RangeError',
56
'ReferenceError',
57
'SyntaxError',
58
'URIError',
59
'EvalError',
62
- 'Boolean',
60
'DataView',
61
'Float32Array',
62
'Float64Array',
@@ -75,16 +72,8 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
72
'Uint32Array',
73
'ArrayBuffer',
74
'JSON',
78
- 'parseFloat',
79
- 'parseInt',
75
'console',
81
- 'isNaN',
76
'eval',
83
- 'isFinite',
84
- 'encodeURI',
85
- 'decodeURI',
86
- 'encodeURIComponent',
87
- 'decodeURIComponent',
77
]);
78
79
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
@@ -101,6 +90,23 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
90
returnValueKind: ValueKind.Mutable,
91
}),
92
],
93
+ [
94
+ /**
95
+ * Object.fromEntries(iterable)
96
+ * iterable: An iterable, such as an Array or Map, containing a list of
97
+ * objects. Each object should have two properties.
98
+ * Returns a new object whose properties are given by the entries of the
99
+ * iterable.
100
+ */
101
+ 'fromEntries',
102
+ addFunction(DEFAULT_SHAPES, [], {
103
+ positionalParams: [Effect.ConditionallyMutate],
104
+ restParam: null,
105
+ returnType: {kind: 'Object', shapeId: BuiltInObjectId},
106
+ calleeEffect: Effect.Read,
107
+ returnValueKind: ValueKind.Mutable,
108
+ }),
109
+ ],
110
]),
111
],
112
[
@@ -372,6 +378,86 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
378
returnValueKind: ValueKind.Primitive,
379
}),
380
],
381
+ [
382
+ 'parseInt',
383
+ addFunction(DEFAULT_SHAPES, [], {
384
+ positionalParams: [],
385
+ restParam: Effect.Read,
386
+ returnType: {kind: 'Primitive'},
387
+ calleeEffect: Effect.Read,
388
+ returnValueKind: ValueKind.Primitive,
389
+ }),
390
+ ],
391
+ [
392
+ 'parseFloat',
393
+ addFunction(DEFAULT_SHAPES, [], {
394
+ positionalParams: [],
395
+ restParam: Effect.Read,
396
+ returnType: {kind: 'Primitive'},
397
+ calleeEffect: Effect.Read,
398
+ returnValueKind: ValueKind.Primitive,
399
+ }),
400
+ ],
401
+ [
402
+ 'isNaN',
403
+ addFunction(DEFAULT_SHAPES, [], {
404
+ positionalParams: [],
405
+ restParam: Effect.Read,
406
+ returnType: {kind: 'Primitive'},
407
+ calleeEffect: Effect.Read,
408
+ returnValueKind: ValueKind.Primitive,
409
+ }),
410
+ ],
411
+ [
412
+ 'isFinite',
413
+ addFunction(DEFAULT_SHAPES, [], {
414
+ positionalParams: [],
415
+ restParam: Effect.Read,
416
+ returnType: {kind: 'Primitive'},
417
+ calleeEffect: Effect.Read,
418
+ returnValueKind: ValueKind.Primitive,
419
+ }),
420
+ ],
421
+ [
422
+ 'encodeURI',
423
+ addFunction(DEFAULT_SHAPES, [], {
424
+ positionalParams: [],
425
+ restParam: Effect.Read,
426
+ returnType: {kind: 'Primitive'},
427
+ calleeEffect: Effect.Read,
428
+ returnValueKind: ValueKind.Primitive,
429
+ }),
430
+ ],
431
+ [
432
+ 'encodeURIComponent',
433
+ addFunction(DEFAULT_SHAPES, [], {
434
+ positionalParams: [],
435
+ restParam: Effect.Read,
436
+ returnType: {kind: 'Primitive'},
437
+ calleeEffect: Effect.Read,
438
+ returnValueKind: ValueKind.Primitive,
439
+ }),
440
+ ],
441
+ [
442
+ 'decodeURI',
443
+ addFunction(DEFAULT_SHAPES, [], {
444
+ positionalParams: [],
445
+ restParam: Effect.Read,
446
+ returnType: {kind: 'Primitive'},
447
+ calleeEffect: Effect.Read,
448
+ returnValueKind: ValueKind.Primitive,
449
+ }),
450
+ ],
451
+ [
452
+ 'decodeURIComponent',
453
+ addFunction(DEFAULT_SHAPES, [], {
454
+ positionalParams: [],
455
+ restParam: Effect.Read,
456
+ returnType: {kind: 'Primitive'},
457
+ calleeEffect: Effect.Read,
458
+ returnValueKind: ValueKind.Primitive,
459
+ }),
460
+ ],
461
// TODO: rest of Global objects
462
];
463
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts
+47
-7
@@ -251,7 +251,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
251
252
// Maintains a mapping of top-level variables to the kind of value they hold
253
class InferenceState {
254
- #env: Environment;
254
+ env: Environment;
255
256
// The kind of each value, based on its allocation site
257
#values: Map<InstructionValue, AbstractValue>;
@@ -267,7 +267,7 @@ class InferenceState {
267
values: Map<InstructionValue, AbstractValue>,
268
variables: Map<IdentifierId, Set<InstructionValue>>,
269
) {
270
- this.#env = env;
270
+ this.env = env;
271
this.#values = values;
272
this.#variables = variables;
273
}
@@ -409,8 +409,8 @@ class InferenceState {
409
});
410
if (
411
value.kind === 'FunctionExpression' &&
412
- (this.#env.config.enablePreserveExistingMemoizationGuarantees ||
413
- this.#env.config.enableTransitivelyFreezeFunctionExpressions)
412
+ (this.env.config.enablePreserveExistingMemoizationGuarantees ||
413
+ this.env.config.enableTransitivelyFreezeFunctionExpressions)
414
) {
415
for (const operand of value.loweredFunc.func.context) {
416
const operandValues = this.#variables.get(operand.identifier.id);
@@ -590,7 +590,7 @@ class InferenceState {
590
return null;
591
} else {
592
return new InferenceState(
593
- this.#env,
593
+ this.env,
594
nextValues ?? new Map(this.#values),
595
nextVariables ?? new Map(this.#variables),
596
);
@@ -604,7 +604,7 @@ class InferenceState {
604
*/
605
clone(): InferenceState {
606
return new InferenceState(
607
- this.#env,
607
+ this.env,
608
new Map(this.#values),
609
new Map(this.#variables),
610
);
@@ -2012,6 +2012,32 @@ export function getFunctionEffects(
2012
return results;
2013
}
2014
2015
+export function isKnownMutableEffect(effect: Effect): boolean {
2016
+ switch (effect) {
2017
+ case Effect.Store:
2018
+ case Effect.ConditionallyMutate:
2019
+ case Effect.Mutate: {
2020
+ return true;
2021
+ }
2022
+
2023
+ case Effect.Unknown: {
2024
+ CompilerError.invariant(false, {
2025
+ reason: 'Unexpected unknown effect',
2026
+ description: null,
2027
+ loc: GeneratedSource,
2028
+ suggestions: null,
2029
+ });
2030
+ }
2031
+ case Effect.Read:
2032
+ case Effect.Capture:
2033
+ case Effect.Freeze: {
2034
+ return false;
2035
+ }
2036
+ default: {
2037
+ assertExhaustive(effect, `Unexpected effect \`${effect}\``);
2038
+ }
2039
+ }
2040
+}
2041
/**
2042
* Returns true if all of the arguments are both non-mutable (immutable or frozen)
2043
* _and_ are not functions which might mutate their arguments. Note that function
@@ -2023,10 +2049,20 @@ function areArgumentsImmutableAndNonMutating(
2049
args: MethodCall['args'],
2050
): boolean {
2051
for (const arg of args) {
2052
+ if (arg.kind === 'Identifier' && arg.identifier.type.kind === 'Function') {
2053
+ const fnShape = state.env.getFunctionSignature(arg.identifier.type);
2054
+ if (fnShape != null) {
2055
+ return (
2056
+ !fnShape.positionalParams.some(isKnownMutableEffect) &&
2057
+ (fnShape.restParam == null ||
2058
+ !isKnownMutableEffect(fnShape.restParam))
2059
+ );
2060
+ }
2061
+ }
2062
const place = arg.kind === 'Identifier' ? arg : arg.place;
2063
+
2064
const kind = state.kind(place).kind;
2065
switch (kind) {
2029
- case ValueKind.Global:
2066
case ValueKind.Primitive:
2067
case ValueKind.Frozen: {
2068
/*
@@ -2037,6 +2073,10 @@ function areArgumentsImmutableAndNonMutating(
2073
break;
2074
}
2075
default: {
2076
+ /**
2077
+ * Globals, module locals, and other locally defined functions may
2078
+ * mutate their arguments.
2079
+ */
2080
return false;
2081
}
2082
}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md
+29
-10
@@ -8,7 +8,12 @@ function Component({value}) {
8
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
9
useIdentity();
10
const derived = Array.from(arr, mutateAndReturn);
11
- return <Stringify>{derived.at(-1)}</Stringify>;
11
+ return (
12
+ <Stringify>
13
+ {derived.at(0)}
14
+ {derived.at(-1)}
15
+ </Stringify>
16
+ );
17
}
18
19
export const FIXTURE_ENTRYPOINT = {
@@ -26,28 +31,42 @@ import { c as _c } from "react/compiler-runtime";
31
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
32
33
function Component(t0) {
29
- const $ = _c(4);
34
+ const $ = _c(7);
35
const { value } = t0;
36
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
37
useIdentity();
38
const derived = Array.from(arr, mutateAndReturn);
39
let t1;
40
if ($[0] !== derived) {
36
- t1 = derived.at(-1);
41
+ t1 = derived.at(0);
42
$[0] = derived;
43
$[1] = t1;
44
} else {
45
t1 = $[1];
46
}
47
let t2;
43
- if ($[2] !== t1) {
44
- t2 = <Stringify>{t1}</Stringify>;
45
- $[2] = t1;
48
+ if ($[2] !== derived) {
49
+ t2 = derived.at(-1);
50
+ $[2] = derived;
51
$[3] = t2;
52
} else {
53
t2 = $[3];
54
}
50
- return t2;
55
+ let t3;
56
+ if ($[4] !== t1 || $[5] !== t2) {
57
+ t3 = (
58
+ <Stringify>
59
+ {t1}
60
+ {t2}
61
+ </Stringify>
62
+ );
63
+ $[4] = t1;
64
+ $[5] = t2;
65
+ $[6] = t3;
66
+ } else {
67
+ t3 = $[6];
68
+ }
69
+ return t3;
70
}
71
72
export const FIXTURE_ENTRYPOINT = {
@@ -59,6 +78,6 @@ export const FIXTURE_ENTRYPOINT = {
78
```
79
80
### Eval output
62
-(kind: ok) <div>{"children":{"value":5,"wat0":"joe"}}</div>
63
-<div>{"children":{"value":6,"wat0":"joe"}}</div>
64
-<div>{"children":{"value":6,"wat0":"joe"}}</div>
\ No newline at end of file
81
+(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
82
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
83
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js
+6
-1
@@ -4,7 +4,12 @@ function Component({value}) {
4
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
5
useIdentity();
6
const derived = Array.from(arr, mutateAndReturn);
7
- return <Stringify>{derived.at(-1)}</Stringify>;
7
+ return (
8
+ <Stringify>
9
+ {derived.at(0)}
10
+ {derived.at(-1)}
11
+ </Stringify>
12
+ );
13
}
14
15
export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-filter-capture-mutate-bug.expect.md
new
+113
@@ -0,0 +1,113 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
6
+
7
+/**
8
+ * Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
9
+ * Found differences in evaluator results
10
+ * Non-forget (expected):
11
+ * (kind: ok)
12
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
13
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
14
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
15
+ * Forget:
16
+ * (kind: ok)
17
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
18
+ * <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
19
+ * <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
20
+
21
+ */
22
+function Component({value}) {
23
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
24
+ useIdentity(null);
25
+ const derived = arr.filter(mutateAndReturn);
26
+ return (
27
+ <Stringify>
28
+ {derived.at(0)}
29
+ {derived.at(-1)}
30
+ </Stringify>
31
+ );
32
+}
33
+
34
+export const FIXTURE_ENTRYPOINT = {
35
+ fn: Component,
36
+ params: [{value: 5}],
37
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
38
+};
39
+
40
+```
41
+
42
+## Code
43
+
44
+```javascript
45
+import { c as _c } from "react/compiler-runtime";
46
+import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
47
+
48
+/**
49
+ * Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
50
+ * Found differences in evaluator results
51
+ * Non-forget (expected):
52
+ * (kind: ok)
53
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
54
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
55
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
56
+ * Forget:
57
+ * (kind: ok)
58
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
59
+ * <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
60
+ * <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
61
+
62
+ */
63
+function Component(t0) {
64
+ const $ = _c(7);
65
+ const { value } = t0;
66
+ const arr = [{ value: "foo" }, { value: "bar" }, { value }];
67
+ useIdentity(null);
68
+ const derived = arr.filter(mutateAndReturn);
69
+ let t1;
70
+ if ($[0] !== derived) {
71
+ t1 = derived.at(0);
72
+ $[0] = derived;
73
+ $[1] = t1;
74
+ } else {
75
+ t1 = $[1];
76
+ }
77
+ let t2;
78
+ if ($[2] !== derived) {
79
+ t2 = derived.at(-1);
80
+ $[2] = derived;
81
+ $[3] = t2;
82
+ } else {
83
+ t2 = $[3];
84
+ }
85
+ let t3;
86
+ if ($[4] !== t1 || $[5] !== t2) {
87
+ t3 = (
88
+ <Stringify>
89
+ {t1}
90
+ {t2}
91
+ </Stringify>
92
+ );
93
+ $[4] = t1;
94
+ $[5] = t2;
95
+ $[6] = t3;
96
+ } else {
97
+ t3 = $[6];
98
+ }
99
+ return t3;
100
+}
101
+
102
+export const FIXTURE_ENTRYPOINT = {
103
+ fn: Component,
104
+ params: [{ value: 5 }],
105
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
106
+};
107
+
108
+```
109
+
110
+### Eval output
111
+(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
112
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
113
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-filter-capture-mutate-bug.tsx
new
+34
@@ -0,0 +1,34 @@
1
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
2
+
3
+/**
4
+ * Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
5
+ * Found differences in evaluator results
6
+ * Non-forget (expected):
7
+ * (kind: ok)
8
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
9
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
10
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
11
+ * Forget:
12
+ * (kind: ok)
13
+ * <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
14
+ * <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
15
+ * <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
16
+
17
+ */
18
+function Component({value}) {
19
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
20
+ useIdentity(null);
21
+ const derived = arr.filter(mutateAndReturn);
22
+ return (
23
+ <Stringify>
24
+ {derived.at(0)}
25
+ {derived.at(-1)}
26
+ </Stringify>
27
+ );
28
+}
29
+
30
+export const FIXTURE_ENTRYPOINT = {
31
+ fn: Component,
32
+ params: [{value: 5}],
33
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
34
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-filter-known-nonmutate-Boolean.expect.md
new
+118
@@ -0,0 +1,118 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {Stringify, useIdentity} from 'shared-runtime';
6
+
7
+/**
8
+ * Also see repro-array-map-known-mutate-shape, which calls a global function
9
+ * that mutates its operands.
10
+ */
11
+function Component({value}) {
12
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
13
+ useIdentity(null);
14
+ const derived = arr.filter(Boolean);
15
+ return (
16
+ <Stringify>
17
+ {derived.at(0)}
18
+ {derived.at(-1)}
19
+ </Stringify>
20
+ );
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{value: 5}],
26
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
27
+};
28
+
29
+```
30
+
31
+## Code
32
+
33
+```javascript
34
+import { c as _c } from "react/compiler-runtime";
35
+import { Stringify, useIdentity } from "shared-runtime";
36
+
37
+/**
38
+ * Also see repro-array-map-known-mutate-shape, which calls a global function
39
+ * that mutates its operands.
40
+ */
41
+function Component(t0) {
42
+ const $ = _c(13);
43
+ const { value } = t0;
44
+ let t1;
45
+ let t2;
46
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
47
+ t1 = { value: "foo" };
48
+ t2 = { value: "bar" };
49
+ $[0] = t1;
50
+ $[1] = t2;
51
+ } else {
52
+ t1 = $[0];
53
+ t2 = $[1];
54
+ }
55
+ let t3;
56
+ if ($[2] !== value) {
57
+ t3 = [t1, t2, { value }];
58
+ $[2] = value;
59
+ $[3] = t3;
60
+ } else {
61
+ t3 = $[3];
62
+ }
63
+ const arr = t3;
64
+ useIdentity(null);
65
+ let t4;
66
+ if ($[4] !== arr) {
67
+ t4 = arr.filter(Boolean);
68
+ $[4] = arr;
69
+ $[5] = t4;
70
+ } else {
71
+ t4 = $[5];
72
+ }
73
+ const derived = t4;
74
+ let t5;
75
+ if ($[6] !== derived) {
76
+ t5 = derived.at(0);
77
+ $[6] = derived;
78
+ $[7] = t5;
79
+ } else {
80
+ t5 = $[7];
81
+ }
82
+ let t6;
83
+ if ($[8] !== derived) {
84
+ t6 = derived.at(-1);
85
+ $[8] = derived;
86
+ $[9] = t6;
87
+ } else {
88
+ t6 = $[9];
89
+ }
90
+ let t7;
91
+ if ($[10] !== t5 || $[11] !== t6) {
92
+ t7 = (
93
+ <Stringify>
94
+ {t5}
95
+ {t6}
96
+ </Stringify>
97
+ );
98
+ $[10] = t5;
99
+ $[11] = t6;
100
+ $[12] = t7;
101
+ } else {
102
+ t7 = $[12];
103
+ }
104
+ return t7;
105
+}
106
+
107
+export const FIXTURE_ENTRYPOINT = {
108
+ fn: Component,
109
+ params: [{ value: 5 }],
110
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
111
+};
112
+
113
+```
114
+
115
+### Eval output
116
+(kind: ok) <div>{"children":[{"value":"foo"},{"value":5}]}</div>
117
+<div>{"children":[{"value":"foo"},{"value":6}]}</div>
118
+<div>{"children":[{"value":"foo"},{"value":6}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-filter-known-nonmutate-Boolean.tsx
new
+23
@@ -0,0 +1,23 @@
1
+import {Stringify, useIdentity} from 'shared-runtime';
2
+
3
+/**
4
+ * Also see repro-array-map-known-mutate-shape, which calls a global function
5
+ * that mutates its operands.
6
+ */
7
+function Component({value}) {
8
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
9
+ useIdentity(null);
10
+ const derived = arr.filter(Boolean);
11
+ return (
12
+ <Stringify>
13
+ {derived.at(0)}
14
+ {derived.at(-1)}
15
+ </Stringify>
16
+ );
17
+}
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{value: 5}],
22
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
23
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-map-capture-mutate-bug.expect.md
new
+91
@@ -0,0 +1,91 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
6
+
7
+/**
8
+ * Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
9
+ * function call which captures its callee when applying an operand.
10
+ */
11
+function Component({value}) {
12
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
13
+ useIdentity(null);
14
+ const derived = arr.map(mutateAndReturn);
15
+ return (
16
+ <Stringify>
17
+ {derived.at(0)}
18
+ {derived.at(-1)}
19
+ </Stringify>
20
+ );
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{value: 5}],
26
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
27
+};
28
+
29
+```
30
+
31
+## Code
32
+
33
+```javascript
34
+import { c as _c } from "react/compiler-runtime";
35
+import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
36
+
37
+/**
38
+ * Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
39
+ * function call which captures its callee when applying an operand.
40
+ */
41
+function Component(t0) {
42
+ const $ = _c(7);
43
+ const { value } = t0;
44
+ const arr = [{ value: "foo" }, { value: "bar" }, { value }];
45
+ useIdentity(null);
46
+ const derived = arr.map(mutateAndReturn);
47
+ let t1;
48
+ if ($[0] !== derived) {
49
+ t1 = derived.at(0);
50
+ $[0] = derived;
51
+ $[1] = t1;
52
+ } else {
53
+ t1 = $[1];
54
+ }
55
+ let t2;
56
+ if ($[2] !== derived) {
57
+ t2 = derived.at(-1);
58
+ $[2] = derived;
59
+ $[3] = t2;
60
+ } else {
61
+ t2 = $[3];
62
+ }
63
+ let t3;
64
+ if ($[4] !== t1 || $[5] !== t2) {
65
+ t3 = (
66
+ <Stringify>
67
+ {t1}
68
+ {t2}
69
+ </Stringify>
70
+ );
71
+ $[4] = t1;
72
+ $[5] = t2;
73
+ $[6] = t3;
74
+ } else {
75
+ t3 = $[6];
76
+ }
77
+ return t3;
78
+}
79
+
80
+export const FIXTURE_ENTRYPOINT = {
81
+ fn: Component,
82
+ params: [{ value: 5 }],
83
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
84
+};
85
+
86
+```
87
+
88
+### Eval output
89
+(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
90
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
91
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-map-capture-mutate-bug.tsx
new
+23
@@ -0,0 +1,23 @@
1
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
2
+
3
+/**
4
+ * Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
5
+ * function call which captures its callee when applying an operand.
6
+ */
7
+function Component({value}) {
8
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
9
+ useIdentity(null);
10
+ const derived = arr.map(mutateAndReturn);
11
+ return (
12
+ <Stringify>
13
+ {derived.at(0)}
14
+ {derived.at(-1)}
15
+ </Stringify>
16
+ );
17
+}
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{value: 5}],
22
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
23
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-map-known-mutate-shape.expect.md
new
+100
@@ -0,0 +1,100 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {Stringify, useIdentity} from 'shared-runtime';
6
+
7
+/**
8
+ * Also see repro-array-map-known-nonmutate-Boolean, which calls a global
9
+ * function that does *not* mutate its operands.
10
+ */
11
+function Component({value}) {
12
+ const arr = [
13
+ new Set([['foo', 2]]).values(),
14
+ new Set([['bar', 4]]).values(),
15
+ [['baz', value]],
16
+ ];
17
+ useIdentity(null);
18
+ const derived = arr.map(Object.fromEntries);
19
+ return (
20
+ <Stringify>
21
+ {derived.at(0)}
22
+ {derived.at(-1)}
23
+ </Stringify>
24
+ );
25
+}
26
+
27
+export const FIXTURE_ENTRYPOINT = {
28
+ fn: Component,
29
+ params: [{value: 5}],
30
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
31
+};
32
+
33
+```
34
+
35
+## Code
36
+
37
+```javascript
38
+import { c as _c } from "react/compiler-runtime";
39
+import { Stringify, useIdentity } from "shared-runtime";
40
+
41
+/**
42
+ * Also see repro-array-map-known-nonmutate-Boolean, which calls a global
43
+ * function that does *not* mutate its operands.
44
+ */
45
+function Component(t0) {
46
+ const $ = _c(7);
47
+ const { value } = t0;
48
+ const arr = [
49
+ new Set([["foo", 2]]).values(),
50
+ new Set([["bar", 4]]).values(),
51
+ [["baz", value]],
52
+ ];
53
+
54
+ useIdentity(null);
55
+ const derived = arr.map(Object.fromEntries);
56
+ let t1;
57
+ if ($[0] !== derived) {
58
+ t1 = derived.at(0);
59
+ $[0] = derived;
60
+ $[1] = t1;
61
+ } else {
62
+ t1 = $[1];
63
+ }
64
+ let t2;
65
+ if ($[2] !== derived) {
66
+ t2 = derived.at(-1);
67
+ $[2] = derived;
68
+ $[3] = t2;
69
+ } else {
70
+ t2 = $[3];
71
+ }
72
+ let t3;
73
+ if ($[4] !== t1 || $[5] !== t2) {
74
+ t3 = (
75
+ <Stringify>
76
+ {t1}
77
+ {t2}
78
+ </Stringify>
79
+ );
80
+ $[4] = t1;
81
+ $[5] = t2;
82
+ $[6] = t3;
83
+ } else {
84
+ t3 = $[6];
85
+ }
86
+ return t3;
87
+}
88
+
89
+export const FIXTURE_ENTRYPOINT = {
90
+ fn: Component,
91
+ params: [{ value: 5 }],
92
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
93
+};
94
+
95
+```
96
+
97
+### Eval output
98
+(kind: ok) <div>{"children":[{"foo":2},{"baz":5}]}</div>
99
+<div>{"children":[{"foo":2},{"baz":6}]}</div>
100
+<div>{"children":[{"foo":2},{"baz":6}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/repro-array-map-known-mutate-shape.tsx
new
+27
@@ -0,0 +1,27 @@
1
+import {Stringify, useIdentity} from 'shared-runtime';
2
+
3
+/**
4
+ * Also see repro-array-map-known-nonmutate-Boolean, which calls a global
5
+ * function that does *not* mutate its operands.
6
+ */
7
+function Component({value}) {
8
+ const arr = [
9
+ new Set([['foo', 2]]).values(),
10
+ new Set([['bar', 4]]).values(),
11
+ [['baz', value]],
12
+ ];
13
+ useIdentity(null);
14
+ const derived = arr.map(Object.fromEntries);
15
+ return (
16
+ <Stringify>
17
+ {derived.at(0)}
18
+ {derived.at(-1)}
19
+ </Stringify>
20
+ );
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{value: 5}],
26
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
27
+};