@samitouri / QOS-React-2 / commits / 0860b9cc1f

[compiler] Add definitions for Object entries/keys/values (#34047)

Fixes remaining issue in #32261, where passing a previously useMemo()-d value to `Object.entries()` makes the compiler think the value is mutated and fail validatePreserveExistingMemo. While I was there I added Object.keys() and Object.values() too. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34047). * #34049 * __->__ #34047 * #34044

Joseph Savona committed Aug 1, 2025 at 12:59 UTC 0860b9cc1f4a7188b41204bddc57a127a8bbf6e9
17 files changed +818
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+93
@@ -114,6 +114,99 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
114 returnValueKind: ValueKind.Mutable,
115 }),
116 ],
117 + [
118 + 'entries',
119 + addFunction(DEFAULT_SHAPES, [], {
120 + positionalParams: [Effect.Capture],
121 + restParam: null,
122 + returnType: {kind: 'Object', shapeId: BuiltInArrayId},
123 + calleeEffect: Effect.Read,
124 + returnValueKind: ValueKind.Mutable,
125 + aliasing: {
126 + receiver: '@receiver',
127 + params: ['@object'],
128 + rest: null,
129 + returns: '@returns',
130 + temporaries: [],
131 + effects: [
132 + {
133 + kind: 'Create',
134 + into: '@returns',
135 + reason: ValueReason.KnownReturnSignature,
136 + value: ValueKind.Mutable,
137 + },
138 + // Object values are captured into the return
139 + {
140 + kind: 'Capture',
141 + from: '@object',
142 + into: '@returns',
143 + },
144 + ],
145 + },
146 + }),
147 + ],
148 + [
149 + 'keys',
150 + addFunction(DEFAULT_SHAPES, [], {
151 + positionalParams: [Effect.Read],
152 + restParam: null,
153 + returnType: {kind: 'Object', shapeId: BuiltInArrayId},
154 + calleeEffect: Effect.Read,
155 + returnValueKind: ValueKind.Mutable,
156 + aliasing: {
157 + receiver: '@receiver',
158 + params: ['@object'],
159 + rest: null,
160 + returns: '@returns',
161 + temporaries: [],
162 + effects: [
163 + {
164 + kind: 'Create',
165 + into: '@returns',
166 + reason: ValueReason.KnownReturnSignature,
167 + value: ValueKind.Mutable,
168 + },
169 + // Only keys are captured, and keys are immutable
170 + {
171 + kind: 'ImmutableCapture',
172 + from: '@object',
173 + into: '@returns',
174 + },
175 + ],
176 + },
177 + }),
178 + ],
179 + [
180 + 'values',
181 + addFunction(DEFAULT_SHAPES, [], {
182 + positionalParams: [Effect.Capture],
183 + restParam: null,
184 + returnType: {kind: 'Object', shapeId: BuiltInArrayId},
185 + calleeEffect: Effect.Read,
186 + returnValueKind: ValueKind.Mutable,
187 + aliasing: {
188 + receiver: '@receiver',
189 + params: ['@object'],
190 + rest: null,
191 + returns: '@returns',
192 + temporaries: [],
193 + effects: [
194 + {
195 + kind: 'Create',
196 + into: '@returns',
197 + reason: ValueReason.KnownReturnSignature,
198 + value: ValueKind.Mutable,
199 + },
200 + // Object values are captured into the return
201 + {
202 + kind: 'Capture',
203 + from: '@object',
204 + into: '@returns',
205 + },
206 + ],
207 + },
208 + }),
209 + ],
210 ]),
211 ],
212 [
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+1
@@ -142,6 +142,7 @@ function parseAliasingSignatureConfig(
142 const effects = typeConfig.effects.map(
143 (effect: AliasingEffectConfig): AliasingEffect => {
144 switch (effect.kind) {
145 + case 'ImmutableCapture':
146 case 'CreateFrom':
147 case 'Capture':
148 case 'Alias':
compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts
+15
@@ -111,6 +111,19 @@ export const AliasEffectSchema: z.ZodType<AliasEffectConfig> = z.object({
111 into: LifetimeIdSchema,
112 });
113
114 +export type ImmutableCaptureEffectConfig = {
115 + kind: 'ImmutableCapture';
116 + from: string;
117 + into: string;
118 +};
119 +
120 +export const ImmutableCaptureEffectSchema: z.ZodType<ImmutableCaptureEffectConfig> =
121 + z.object({
122 + kind: z.literal('ImmutableCapture'),
123 + from: LifetimeIdSchema,
124 + into: LifetimeIdSchema,
125 + });
126 +
127 export type CaptureEffectConfig = {
128 kind: 'Capture';
129 from: string;
@@ -187,6 +200,7 @@ export type AliasingEffectConfig =
200 | AssignEffectConfig
201 | AliasEffectConfig
202 | CaptureEffectConfig
203 + | ImmutableCaptureEffectConfig
204 | ImpureEffectConfig
205 | MutateEffectConfig
206 | MutateTransitiveConditionallyConfig
@@ -199,6 +213,7 @@ export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
213 AssignEffectSchema,
214 AliasEffectSchema,
215 CaptureEffectSchema,
216 + ImmutableCaptureEffectSchema,
217 ImpureEffectSchema,
218 MutateEffectSchema,
219 MutateTransitiveConditionallySchema,
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
7 +
8 +function Component(props) {
9 + const object = {object: props.object};
10 + const entries = useMemo(() => Object.entries(object), [object]);
11 + entries.map(([, value]) => {
12 + value.updated = true;
13 + });
14 + return <Stringify entries={entries} />;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{object: {key: makeObject_Primitives()}}],
20 +};
21 +
22 +```
23 +
24 +
25 +## Error
26 +
27 +```
28 +Found 2 errors:
29 +
30 +Memoization: Compilation skipped because existing memoization could not be preserved
31 +
32 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
33 +
34 +error.validate-object-entries-mutation.ts:6:57
35 + 4 | function Component(props) {
36 + 5 | const object = {object: props.object};
37 +> 6 | const entries = useMemo(() => Object.entries(object), [object]);
38 + | ^^^^^^ This dependency may be modified later
39 + 7 | entries.map(([, value]) => {
40 + 8 | value.updated = true;
41 + 9 | });
42 +
43 +Memoization: Compilation skipped because existing memoization could not be preserved
44 +
45 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
46 +
47 +error.validate-object-entries-mutation.ts:6:18
48 + 4 | function Component(props) {
49 + 5 | const object = {object: props.object};
50 +> 6 | const entries = useMemo(() => Object.entries(object), [object]);
51 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not preserve existing memoization
52 + 7 | entries.map(([, value]) => {
53 + 8 | value.updated = true;
54 + 9 | });
55 +```
56 +
57 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.js new
+16
@@ -0,0 +1,16 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
3 +
4 +function Component(props) {
5 + const object = {object: props.object};
6 + const entries = useMemo(() => Object.entries(object), [object]);
7 + entries.map(([, value]) => {
8 + value.updated = true;
9 + });
10 + return <Stringify entries={entries} />;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{object: {key: makeObject_Primitives()}}],
16 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
7 +
8 +function Component(props) {
9 + const object = {object: props.object};
10 + const values = useMemo(() => Object.values(object), [object]);
11 + values.map(value => {
12 + value.updated = true;
13 + });
14 + return <Stringify values={values} />;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{object: {key: makeObject_Primitives()}}],
20 +};
21 +
22 +```
23 +
24 +
25 +## Error
26 +
27 +```
28 +Found 2 errors:
29 +
30 +Memoization: Compilation skipped because existing memoization could not be preserved
31 +
32 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
33 +
34 +error.validate-object-values-mutation.ts:6:55
35 + 4 | function Component(props) {
36 + 5 | const object = {object: props.object};
37 +> 6 | const values = useMemo(() => Object.values(object), [object]);
38 + | ^^^^^^ This dependency may be modified later
39 + 7 | values.map(value => {
40 + 8 | value.updated = true;
41 + 9 | });
42 +
43 +Memoization: Compilation skipped because existing memoization could not be preserved
44 +
45 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
46 +
47 +error.validate-object-values-mutation.ts:6:17
48 + 4 | function Component(props) {
49 + 5 | const object = {object: props.object};
50 +> 6 | const values = useMemo(() => Object.values(object), [object]);
51 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not preserve existing memoization
52 + 7 | values.map(value => {
53 + 8 | value.updated = true;
54 + 9 | });
55 +```
56 +
57 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.js new
+16
@@ -0,0 +1,16 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
3 +
4 +function Component(props) {
5 + const object = {object: props.object};
6 + const values = useMemo(() => Object.values(object), [object]);
7 + values.map(value => {
8 + value.updated = true;
9 + });
10 + return <Stringify values={values} />;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{object: {key: makeObject_Primitives()}}],
16 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-entries-mutation.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
6 +
7 +function Component(props) {
8 + const object = {object: props.object};
9 + const entries = Object.entries(object);
10 + entries.map(([, value]) => {
11 + value.updated = true;
12 + });
13 + return <Stringify entries={entries} />;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{object: {key: makeObject_Primitives()}}],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { c as _c } from "react/compiler-runtime";
27 +import { makeObject_Primitives, Stringify } from "shared-runtime";
28 +
29 +function Component(props) {
30 + const $ = _c(2);
31 + let t0;
32 + if ($[0] !== props.object) {
33 + const object = { object: props.object };
34 + const entries = Object.entries(object);
35 + entries.map(_temp);
36 + t0 = <Stringify entries={entries} />;
37 + $[0] = props.object;
38 + $[1] = t0;
39 + } else {
40 + t0 = $[1];
41 + }
42 + return t0;
43 +}
44 +function _temp(t0) {
45 + const [, value] = t0;
46 + value.updated = true;
47 +}
48 +
49 +export const FIXTURE_ENTRYPOINT = {
50 + fn: Component,
51 + params: [{ object: { key: makeObject_Primitives() } }],
52 +};
53 +
54 +```
55 +
56 +### Eval output
57 +(kind: ok) <div>{"entries":[["object",{"key":{"a":0,"b":"value1","c":true},"updated":true}]]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-entries-mutation.js new
+15
@@ -0,0 +1,15 @@
1 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
2 +
3 +function Component(props) {
4 + const object = {object: props.object};
5 + const entries = Object.entries(object);
6 + entries.map(([, value]) => {
7 + value.updated = true;
8 + });
9 + return <Stringify entries={entries} />;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{object: {key: makeObject_Primitives()}}],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-keys.expect.md new
+108
@@ -0,0 +1,108 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useMemo} from 'react';
7 +import {Stringify} from 'shared-runtime';
8 +
9 +// derived from https://github.com/facebook/react/issues/32261
10 +function Component({items}) {
11 + const record = useMemo(
12 + () =>
13 + Object.fromEntries(
14 + items.map(item => [item.id, ref => <Stringify ref={ref} {...item} />])
15 + ),
16 + [items]
17 + );
18 +
19 + // Without a declaration for Object.entries(), this would be assumed to mutate
20 + // `record`, meaning existing memoization couldn't be preserved
21 + return (
22 + <div>
23 + {Object.keys(record).map(id => (
24 + <Stringify key={id} render={record[id]} />
25 + ))}
26 + </div>
27 + );
28 +}
29 +
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: Component,
32 + params: [
33 + {
34 + items: [
35 + {id: '0', name: 'Hello'},
36 + {id: '1', name: 'World!'},
37 + ],
38 + },
39 + ],
40 +};
41 +
42 +```
43 +
44 +## Code
45 +
46 +```javascript
47 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
48 +import { useMemo } from "react";
49 +import { Stringify } from "shared-runtime";
50 +
51 +// derived from https://github.com/facebook/react/issues/32261
52 +function Component(t0) {
53 + const $ = _c(7);
54 + const { items } = t0;
55 + let t1;
56 + if ($[0] !== items) {
57 + t1 = Object.fromEntries(items.map(_temp));
58 + $[0] = items;
59 + $[1] = t1;
60 + } else {
61 + t1 = $[1];
62 + }
63 + const record = t1;
64 + let t2;
65 + if ($[2] !== record) {
66 + t2 = Object.keys(record);
67 + $[2] = record;
68 + $[3] = t2;
69 + } else {
70 + t2 = $[3];
71 + }
72 + let t3;
73 + if ($[4] !== record || $[5] !== t2) {
74 + t3 = (
75 + <div>
76 + {t2.map((id) => (
77 + <Stringify key={id} render={record[id]} />
78 + ))}
79 + </div>
80 + );
81 + $[4] = record;
82 + $[5] = t2;
83 + $[6] = t3;
84 + } else {
85 + t3 = $[6];
86 + }
87 + return t3;
88 +}
89 +function _temp(item) {
90 + return [item.id, (ref) => <Stringify ref={ref} {...item} />];
91 +}
92 +
93 +export const FIXTURE_ENTRYPOINT = {
94 + fn: Component,
95 + params: [
96 + {
97 + items: [
98 + { id: "0", name: "Hello" },
99 + { id: "1", name: "World!" },
100 + ],
101 + },
102 + ],
103 +};
104 +
105 +```
106 +
107 +### Eval output
108 +(kind: ok) <div><div>{"render":"[[ function params=1 ]]"}</div><div>{"render":"[[ function params=1 ]]"}</div></div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-keys.js new
+36
@@ -0,0 +1,36 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {useMemo} from 'react';
3 +import {Stringify} from 'shared-runtime';
4 +
5 +// derived from https://github.com/facebook/react/issues/32261
6 +function Component({items}) {
7 + const record = useMemo(
8 + () =>
9 + Object.fromEntries(
10 + items.map(item => [item.id, ref => <Stringify ref={ref} {...item} />])
11 + ),
12 + [items]
13 + );
14 +
15 + // Without a declaration for Object.entries(), this would be assumed to mutate
16 + // `record`, meaning existing memoization couldn't be preserved
17 + return (
18 + <div>
19 + {Object.keys(record).map(id => (
20 + <Stringify key={id} render={record[id]} />
21 + ))}
22 + </div>
23 + );
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [
29 + {
30 + items: [
31 + {id: '0', name: 'Hello'},
32 + {id: '1', name: 'World!'},
33 + ],
34 + },
35 + ],
36 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-values-mutation.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
6 +
7 +function Component(props) {
8 + const object = {object: props.object};
9 + const entries = Object.entries(object);
10 + entries.map(([, value]) => {
11 + value.updated = true;
12 + });
13 + return <Stringify entries={entries} />;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{object: {key: makeObject_Primitives()}}],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { c as _c } from "react/compiler-runtime";
27 +import { makeObject_Primitives, Stringify } from "shared-runtime";
28 +
29 +function Component(props) {
30 + const $ = _c(2);
31 + let t0;
32 + if ($[0] !== props.object) {
33 + const object = { object: props.object };
34 + const entries = Object.entries(object);
35 + entries.map(_temp);
36 + t0 = <Stringify entries={entries} />;
37 + $[0] = props.object;
38 + $[1] = t0;
39 + } else {
40 + t0 = $[1];
41 + }
42 + return t0;
43 +}
44 +function _temp(t0) {
45 + const [, value] = t0;
46 + value.updated = true;
47 +}
48 +
49 +export const FIXTURE_ENTRYPOINT = {
50 + fn: Component,
51 + params: [{ object: { key: makeObject_Primitives() } }],
52 +};
53 +
54 +```
55 +
56 +### Eval output
57 +(kind: ok) <div>{"entries":[["object",{"key":{"a":0,"b":"value1","c":true},"updated":true}]]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-values-mutation.js new
+15
@@ -0,0 +1,15 @@
1 +import {makeObject_Primitives, Stringify} from 'shared-runtime';
2 +
3 +function Component(props) {
4 + const object = {object: props.object};
5 + const entries = Object.entries(object);
6 + entries.map(([, value]) => {
7 + value.updated = true;
8 + });
9 + return <Stringify entries={entries} />;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{object: {key: makeObject_Primitives()}}],
15 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-values.expect.md new
+103
@@ -0,0 +1,103 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useMemo} from 'react';
7 +import {Stringify} from 'shared-runtime';
8 +
9 +// derived from https://github.com/facebook/react/issues/32261
10 +function Component({items}) {
11 + const record = useMemo(
12 + () =>
13 + Object.fromEntries(
14 + items.map(item => [
15 + item.id,
16 + {id: item.id, render: ref => <Stringify ref={ref} {...item} />},
17 + ])
18 + ),
19 + [items]
20 + );
21 +
22 + // Without a declaration for Object.entries(), this would be assumed to mutate
23 + // `record`, meaning existing memoization couldn't be preserved
24 + return (
25 + <div>
26 + {Object.values(record).map(({id, render}) => (
27 + <Stringify key={id} render={render} />
28 + ))}
29 + </div>
30 + );
31 +}
32 +
33 +export const FIXTURE_ENTRYPOINT = {
34 + fn: Component,
35 + params: [
36 + {
37 + items: [
38 + {id: '0', name: 'Hello'},
39 + {id: '1', name: 'World!'},
40 + ],
41 + },
42 + ],
43 +};
44 +
45 +```
46 +
47 +## Code
48 +
49 +```javascript
50 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
51 +import { useMemo } from "react";
52 +import { Stringify } from "shared-runtime";
53 +
54 +// derived from https://github.com/facebook/react/issues/32261
55 +function Component(t0) {
56 + const $ = _c(4);
57 + const { items } = t0;
58 + let t1;
59 + if ($[0] !== items) {
60 + t1 = Object.fromEntries(items.map(_temp));
61 + $[0] = items;
62 + $[1] = t1;
63 + } else {
64 + t1 = $[1];
65 + }
66 + const record = t1;
67 + let t2;
68 + if ($[2] !== record) {
69 + t2 = <div>{Object.values(record).map(_temp2)}</div>;
70 + $[2] = record;
71 + $[3] = t2;
72 + } else {
73 + t2 = $[3];
74 + }
75 + return t2;
76 +}
77 +function _temp2(t0) {
78 + const { id, render } = t0;
79 + return <Stringify key={id} render={render} />;
80 +}
81 +function _temp(item) {
82 + return [
83 + item.id,
84 + { id: item.id, render: (ref) => <Stringify ref={ref} {...item} /> },
85 + ];
86 +}
87 +
88 +export const FIXTURE_ENTRYPOINT = {
89 + fn: Component,
90 + params: [
91 + {
92 + items: [
93 + { id: "0", name: "Hello" },
94 + { id: "1", name: "World!" },
95 + ],
96 + },
97 + ],
98 +};
99 +
100 +```
101 +
102 +### Eval output
103 +(kind: ok) <div><div>{"render":"[[ function params=1 ]]"}</div><div>{"render":"[[ function params=1 ]]"}</div></div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-values.js new
+39
@@ -0,0 +1,39 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {useMemo} from 'react';
3 +import {Stringify} from 'shared-runtime';
4 +
5 +// derived from https://github.com/facebook/react/issues/32261
6 +function Component({items}) {
7 + const record = useMemo(
8 + () =>
9 + Object.fromEntries(
10 + items.map(item => [
11 + item.id,
12 + {id: item.id, render: ref => <Stringify ref={ref} {...item} />},
13 + ])
14 + ),
15 + [items]
16 + );
17 +
18 + // Without a declaration for Object.entries(), this would be assumed to mutate
19 + // `record`, meaning existing memoization couldn't be preserved
20 + return (
21 + <div>
22 + {Object.values(record).map(({id, render}) => (
23 + <Stringify key={id} render={render} />
24 + ))}
25 + </div>
26 + );
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [
32 + {
33 + items: [
34 + {id: '0', name: 'Hello'},
35 + {id: '1', name: 'World!'},
36 + ],
37 + },
38 + ],
39 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-object-fromEntries-entries.expect.md new
+97
@@ -0,0 +1,97 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {useMemo} from 'react';
7 +import {Stringify} from 'shared-runtime';
8 +
9 +// derived from https://github.com/facebook/react/issues/32261
10 +function Component({items}) {
11 + const record = useMemo(
12 + () =>
13 + Object.fromEntries(
14 + items.map(item => [item.id, ref => <Stringify ref={ref} {...item} />])
15 + ),
16 + [items]
17 + );
18 +
19 + // Without a declaration for Object.entries(), this would be assumed to mutate
20 + // `record`, meaning existing memoization couldn't be preserved
21 + return (
22 + <div>
23 + {Object.entries(record).map(([id, render]) => (
24 + <Stringify key={id} render={render} />
25 + ))}
26 + </div>
27 + );
28 +}
29 +
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: Component,
32 + params: [
33 + {
34 + items: [
35 + {id: '0', name: 'Hello'},
36 + {id: '1', name: 'World!'},
37 + ],
38 + },
39 + ],
40 +};
41 +
42 +```
43 +
44 +## Code
45 +
46 +```javascript
47 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
48 +import { useMemo } from "react";
49 +import { Stringify } from "shared-runtime";
50 +
51 +// derived from https://github.com/facebook/react/issues/32261
52 +function Component(t0) {
53 + const $ = _c(4);
54 + const { items } = t0;
55 + let t1;
56 + if ($[0] !== items) {
57 + t1 = Object.fromEntries(items.map(_temp));
58 + $[0] = items;
59 + $[1] = t1;
60 + } else {
61 + t1 = $[1];
62 + }
63 + const record = t1;
64 + let t2;
65 + if ($[2] !== record) {
66 + t2 = <div>{Object.entries(record).map(_temp2)}</div>;
67 + $[2] = record;
68 + $[3] = t2;
69 + } else {
70 + t2 = $[3];
71 + }
72 + return t2;
73 +}
74 +function _temp2(t0) {
75 + const [id, render] = t0;
76 + return <Stringify key={id} render={render} />;
77 +}
78 +function _temp(item) {
79 + return [item.id, (ref) => <Stringify ref={ref} {...item} />];
80 +}
81 +
82 +export const FIXTURE_ENTRYPOINT = {
83 + fn: Component,
84 + params: [
85 + {
86 + items: [
87 + { id: "0", name: "Hello" },
88 + { id: "1", name: "World!" },
89 + ],
90 + },
91 + ],
92 +};
93 +
94 +```
95 +
96 +### Eval output
97 +(kind: ok) <div><div>{"render":"[[ function params=1 ]]"}</div><div>{"render":"[[ function params=1 ]]"}</div></div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-object-fromEntries-entries.js new
+36
@@ -0,0 +1,36 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {useMemo} from 'react';
3 +import {Stringify} from 'shared-runtime';
4 +
5 +// derived from https://github.com/facebook/react/issues/32261
6 +function Component({items}) {
7 + const record = useMemo(
8 + () =>
9 + Object.fromEntries(
10 + items.map(item => [item.id, ref => <Stringify ref={ref} {...item} />])
11 + ),
12 + [items]
13 + );
14 +
15 + // Without a declaration for Object.entries(), this would be assumed to mutate
16 + // `record`, meaning existing memoization couldn't be preserved
17 + return (
18 + <div>
19 + {Object.entries(record).map(([id, render]) => (
20 + <Stringify key={id} render={render} />
21 + ))}
22 + </div>
23 + );
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [
29 + {
30 + items: [
31 + {id: '0', name: 'Hello'},
32 + {id: '1', name: 'World!'},
33 + ],
34 + },
35 + ],
36 +};