@samitouri / QOS-React / commits / 38a7600920

[compiler][optim] Add shape for Array.from (#32522)

(see title) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32522). * #32596 * #32595 * #32594 * #32593 * __->__ #32522 * #32521

mofeiZ committed Mar 13, 2025 at 11:58 UTC 38a76009202e161cb023a92c33052b9dac7daf14
10 files changed +385 -20
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+16 -2
@@ -119,8 +119,8 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
119 ],
120 /*
121 * https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.from
122 - * Array.from(arrayLike, optionalFn, optionalThis) not added because
123 - * the Effect of `arrayLike` is polymorphic i.e.
122 + * Array.from(arrayLike, optionalFn, optionalThis)
123 + * Note that the Effect of `arrayLike` is polymorphic i.e.
124 * - Effect.read if
125 * - it does not have an @iterator property and is array-like
126 * (i.e. has a length property)
@@ -128,6 +128,20 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
128 * - Effect.mutate if it is a self-mutative iterator (e.g. a generator
129 * function)
130 */
131 + [
132 + 'from',
133 + addFunction(DEFAULT_SHAPES, [], {
134 + positionalParams: [
135 + Effect.ConditionallyMutate,
136 + Effect.ConditionallyMutate,
137 + Effect.ConditionallyMutate,
138 + ],
139 + restParam: Effect.Read,
140 + returnType: {kind: 'Object', shapeId: BuiltInArrayId},
141 + calleeEffect: Effect.Read,
142 + returnValueKind: ValueKind.Mutable,
143 + }),
144 + ],
145 [
146 'of',
147 // Array.of(element0, ..., elementN)
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md new
+91
@@ -0,0 +1,91 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {useIdentity, Stringify} from 'shared-runtime';
6 +
7 +/**
8 + * TODO: Note that this `Array.from` is inferred to be mutating its first
9 + * argument. This is because React Compiler's typing system does not yet support
10 + * annotating a function with a set of argument match cases + distinct
11 + * definitions (polymorphism).
12 + *
13 + * In this case, we should be able to infer that the `Array.from` call is
14 + * not mutating its 0th argument.
15 + * The 0th argument should be typed as having `effect:Mutate` only when
16 + * (1) it might be a mutable iterable or
17 + * (2) the 1st argument might mutate its callee
18 + */
19 +function Component({value}) {
20 + const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
21 + useIdentity();
22 + const derived = Array.from(arr, (x, idx) => ({...x, id: idx}));
23 + return <Stringify>{derived.at(-1)}</Stringify>;
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [{value: 5}],
29 + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
30 +};
31 +
32 +```
33 +
34 +## Code
35 +
36 +```javascript
37 +import { c as _c } from "react/compiler-runtime";
38 +import { useIdentity, Stringify } from "shared-runtime";
39 +
40 +/**
41 + * TODO: Note that this `Array.from` is inferred to be mutating its first
42 + * argument. This is because React Compiler's typing system does not yet support
43 + * annotating a function with a set of argument match cases + distinct
44 + * definitions (polymorphism).
45 + *
46 + * In this case, we should be able to infer that the `Array.from` call is
47 + * not mutating its 0th argument.
48 + * The 0th argument should be typed as having `effect:Mutate` only when
49 + * (1) it might be a mutable iterable or
50 + * (2) the 1st argument might mutate its callee
51 + */
52 +function Component(t0) {
53 + const $ = _c(4);
54 + const { value } = t0;
55 + const arr = [{ value: "foo" }, { value: "bar" }, { value }];
56 + useIdentity();
57 + const derived = Array.from(arr, _temp);
58 + let t1;
59 + if ($[0] !== derived) {
60 + t1 = derived.at(-1);
61 + $[0] = derived;
62 + $[1] = t1;
63 + } else {
64 + t1 = $[1];
65 + }
66 + let t2;
67 + if ($[2] !== t1) {
68 + t2 = <Stringify>{t1}</Stringify>;
69 + $[2] = t1;
70 + $[3] = t2;
71 + } else {
72 + t2 = $[3];
73 + }
74 + return t2;
75 +}
76 +function _temp(x, idx) {
77 + return { ...x, id: idx };
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":5,"id":2}}</div>
90 +<div>{"children":{"value":6,"id":2}}</div>
91 +<div>{"children":{"value":6,"id":2}}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js new
+26
@@ -0,0 +1,26 @@
1 +import {useIdentity, Stringify} from 'shared-runtime';
2 +
3 +/**
4 + * TODO: Note that this `Array.from` is inferred to be mutating its first
5 + * argument. This is because React Compiler's typing system does not yet support
6 + * annotating a function with a set of argument match cases + distinct
7 + * definitions (polymorphism).
8 + *
9 + * In this case, we should be able to infer that the `Array.from` call is
10 + * not mutating its 0th argument.
11 + * The 0th argument should be typed as having `effect:Mutate` only when
12 + * (1) it might be a mutable iterable or
13 + * (2) the 1st argument might mutate its callee
14 + */
15 +function Component({value}) {
16 + const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
17 + useIdentity();
18 + const derived = Array.from(arr, (x, idx) => ({...x, id: idx}));
19 + return <Stringify>{derived.at(-1)}</Stringify>;
20 +}
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: Component,
24 + params: [{value: 5}],
25 + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
26 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md new
+88
@@ -0,0 +1,88 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {useIdentity, Stringify} from 'shared-runtime';
6 +
7 +/**
8 + * TODO: Note that this `Array.from` is inferred to be mutating its first
9 + * argument. This is because React Compiler's typing system does not yet support
10 + * annotating a function with a set of argument match cases + distinct
11 + * definitions (polymorphism)
12 + *
13 + * In this case, we should be able to infer that the `Array.from` call is
14 + * not mutating its 0th argument.
15 + * The 0th argument should be typed as having `effect:Mutate` only when
16 + * (1) it might be a mutable iterable or
17 + * (2) the 1st argument might mutate its callee
18 + */
19 +function Component({value}) {
20 + const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
21 + useIdentity();
22 + const derived = Array.from(arr);
23 + return <Stringify>{derived.at(-1)}</Stringify>;
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [{value: 5}],
29 + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
30 +};
31 +
32 +```
33 +
34 +## Code
35 +
36 +```javascript
37 +import { c as _c } from "react/compiler-runtime";
38 +import { useIdentity, Stringify } from "shared-runtime";
39 +
40 +/**
41 + * TODO: Note that this `Array.from` is inferred to be mutating its first
42 + * argument. This is because React Compiler's typing system does not yet support
43 + * annotating a function with a set of argument match cases + distinct
44 + * definitions (polymorphism)
45 + *
46 + * In this case, we should be able to infer that the `Array.from` call is
47 + * not mutating its 0th argument.
48 + * The 0th argument should be typed as having `effect:Mutate` only when
49 + * (1) it might be a mutable iterable or
50 + * (2) the 1st argument might mutate its callee
51 + */
52 +function Component(t0) {
53 + const $ = _c(4);
54 + const { value } = t0;
55 + const arr = [{ value: "foo" }, { value: "bar" }, { value }];
56 + useIdentity();
57 + const derived = Array.from(arr);
58 + let t1;
59 + if ($[0] !== derived) {
60 + t1 = derived.at(-1);
61 + $[0] = derived;
62 + $[1] = t1;
63 + } else {
64 + t1 = $[1];
65 + }
66 + let t2;
67 + if ($[2] !== t1) {
68 + t2 = <Stringify>{t1}</Stringify>;
69 + $[2] = t1;
70 + $[3] = t2;
71 + } else {
72 + t2 = $[3];
73 + }
74 + return t2;
75 +}
76 +
77 +export const FIXTURE_ENTRYPOINT = {
78 + fn: Component,
79 + params: [{ value: 5 }],
80 + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
81 +};
82 +
83 +```
84 +
85 +### Eval output
86 +(kind: ok) <div>{"children":{"value":5}}</div>
87 +<div>{"children":{"value":6}}</div>
88 +<div>{"children":{"value":6}}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js new
+26
@@ -0,0 +1,26 @@
1 +import {useIdentity, Stringify} from 'shared-runtime';
2 +
3 +/**
4 + * TODO: Note that this `Array.from` is inferred to be mutating its first
5 + * argument. This is because React Compiler's typing system does not yet support
6 + * annotating a function with a set of argument match cases + distinct
7 + * definitions (polymorphism)
8 + *
9 + * In this case, we should be able to infer that the `Array.from` call is
10 + * not mutating its 0th argument.
11 + * The 0th argument should be typed as having `effect:Mutate` only when
12 + * (1) it might be a mutable iterable or
13 + * (2) the 1st argument might mutate its callee
14 + */
15 +function Component({value}) {
16 + const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
17 + useIdentity();
18 + const derived = Array.from(arr);
19 + return <Stringify>{derived.at(-1)}</Stringify>;
20 +}
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: Component,
24 + params: [{value: 5}],
25 + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
26 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md new
+64
@@ -0,0 +1,64 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
6 +
7 +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>;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: Component,
16 + params: [{value: 5}],
17 + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { c as _c } from "react/compiler-runtime";
26 +import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
27 +
28 +function Component(t0) {
29 + const $ = _c(4);
30 + const { value } = t0;
31 + const arr = [{ value: "foo" }, { value: "bar" }, { value }];
32 + useIdentity();
33 + const derived = Array.from(arr, mutateAndReturn);
34 + let t1;
35 + if ($[0] !== derived) {
36 + t1 = derived.at(-1);
37 + $[0] = derived;
38 + $[1] = t1;
39 + } else {
40 + t1 = $[1];
41 + }
42 + let t2;
43 + if ($[2] !== t1) {
44 + t2 = <Stringify>{t1}</Stringify>;
45 + $[2] = t1;
46 + $[3] = t2;
47 + } else {
48 + t2 = $[3];
49 + }
50 + return t2;
51 +}
52 +
53 +export const FIXTURE_ENTRYPOINT = {
54 + fn: Component,
55 + params: [{ value: 5 }],
56 + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
57 +};
58 +
59 +```
60 +
61 +### 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
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js new
+14
@@ -0,0 +1,14 @@
1 +import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
2 +
3 +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>;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: Component,
12 + params: [{value: 5}],
13 + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md
+16 -8
@@ -68,21 +68,29 @@ function Validate({ x, input }) {
68 }
69 function useFoo(input) {
70 "use memo";
71 - const $ = _c(3);
71 + const $ = _c(5);
72
73 const x = Array.from([{}]);
74 useIdentity();
75 - x.push([input]);
75 let t0;
77 - if ($[0] !== input || $[1] !== x) {
78 - t0 = <Validate x={x} input={input} />;
76 + if ($[0] !== input) {
77 + t0 = [input];
78 $[0] = input;
80 - $[1] = x;
81 - $[2] = t0;
79 + $[1] = t0;
80 + } else {
81 + t0 = $[1];
82 + }
83 + x.push(t0);
84 + let t1;
85 + if ($[2] !== input || $[3] !== x) {
86 + t1 = <Validate x={x} input={input} />;
87 + $[2] = input;
88 + $[3] = x;
89 + $[4] = t1;
90 } else {
83 - t0 = $[2];
91 + t1 = $[4];
92 }
85 - return t0;
93 + return t1;
94 }
95
96 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md renamed
+38 -10
@@ -37,6 +37,12 @@ function useFoo({val1, val2}) {
37 export const FIXTURE_ENTRYPOINT = {
38 fn: useFoo,
39 params: [{val1: 1, val2: 2}],
40 + params: [
41 + {val1: 1, val2: 2},
42 + {val1: 1, val2: 2},
43 + {val1: 1, val2: 3},
44 + {val1: 4, val2: 2},
45 + ],
46 };
47
48 ```
@@ -71,29 +77,51 @@ function Validate({ x, val1, val2 }) {
77 }
78 function useFoo(t0) {
79 "use memo";
74 - const $ = _c(4);
80 + const $ = _c(8);
81 const { val1, val2 } = t0;
82
83 const x = Array.from([]);
84 useIdentity();
79 - x.push([val1]);
80 - x.push([val2]);
85 let t1;
82 - if ($[0] !== val1 || $[1] !== val2 || $[2] !== x) {
83 - t1 = <Validate x={x} val1={val1} val2={val2} />;
86 + if ($[0] !== val1) {
87 + t1 = [val1];
88 $[0] = val1;
85 - $[1] = val2;
86 - $[2] = x;
87 - $[3] = t1;
89 + $[1] = t1;
90 + } else {
91 + t1 = $[1];
92 + }
93 + x.push(t1);
94 + let t2;
95 + if ($[2] !== val2) {
96 + t2 = [val2];
97 + $[2] = val2;
98 + $[3] = t2;
99 + } else {
100 + t2 = $[3];
101 + }
102 + x.push(t2);
103 + let t3;
104 + if ($[4] !== val1 || $[5] !== val2 || $[6] !== x) {
105 + t3 = <Validate x={x} val1={val1} val2={val2} />;
106 + $[4] = val1;
107 + $[5] = val2;
108 + $[6] = x;
109 + $[7] = t3;
110 } else {
89 - t1 = $[3];
111 + t3 = $[7];
112 }
91 - return t1;
113 + return t3;
114 }
115
116 export const FIXTURE_ENTRYPOINT = {
117 fn: useFoo,
118 params: [{ val1: 1, val2: 2 }],
119 + params: [
120 + { val1: 1, val2: 2 },
121 + { val1: 1, val2: 2 },
122 + { val1: 1, val2: 3 },
123 + { val1: 4, val2: 2 },
124 + ],
125 };
126
127 ```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js renamed
+6
@@ -33,4 +33,10 @@ function useFoo({val1, val2}) {
33 export const FIXTURE_ENTRYPOINT = {
34 fn: useFoo,
35 params: [{val1: 1, val2: 2}],
36 + params: [
37 + {val1: 1, val2: 2},
38 + {val1: 1, val2: 2},
39 + {val1: 1, val2: 3},
40 + {val1: 4, val2: 2},
41 + ],
42 };