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