diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
index 3fe2e938ce..df8196c1d7 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
@@ -119,8 +119,8 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
],
/*
* https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.from
- * Array.from(arrayLike, optionalFn, optionalThis) not added because
- * the Effect of `arrayLike` is polymorphic i.e.
+ * Array.from(arrayLike, optionalFn, optionalThis)
+ * Note that the Effect of `arrayLike` is polymorphic i.e.
* - Effect.read if
* - it does not have an @iterator property and is array-like
* (i.e. has a length property)
@@ -128,6 +128,20 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
* - Effect.mutate if it is a self-mutative iterator (e.g. a generator
* function)
*/
+ [
+ 'from',
+ addFunction(DEFAULT_SHAPES, [], {
+ positionalParams: [
+ Effect.ConditionallyMutate,
+ Effect.ConditionallyMutate,
+ Effect.ConditionallyMutate,
+ ],
+ restParam: Effect.Read,
+ returnType: {kind: 'Object', shapeId: BuiltInArrayId},
+ calleeEffect: Effect.Read,
+ returnValueKind: ValueKind.Mutable,
+ }),
+ ],
[
'of',
// Array.of(element0, ..., elementN)
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md
new file mode 100644
index 0000000000..8892c8d484
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md
@@ -0,0 +1,91 @@
+
+## Input
+
+```javascript
+import {useIdentity, Stringify} from 'shared-runtime';
+
+/**
+ * TODO: Note that this `Array.from` is inferred to be mutating its first
+ * argument. This is because React Compiler's typing system does not yet support
+ * annotating a function with a set of argument match cases + distinct
+ * definitions (polymorphism).
+ *
+ * In this case, we should be able to infer that the `Array.from` call is
+ * not mutating its 0th argument.
+ * The 0th argument should be typed as having `effect:Mutate` only when
+ * (1) it might be a mutable iterable or
+ * (2) the 1st argument might mutate its callee
+ */
+function Component({value}) {
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
+ useIdentity();
+ const derived = Array.from(arr, (x, idx) => ({...x, id: idx}));
+ return {derived.at(-1)};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 5}],
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useIdentity, Stringify } from "shared-runtime";
+
+/**
+ * TODO: Note that this `Array.from` is inferred to be mutating its first
+ * argument. This is because React Compiler's typing system does not yet support
+ * annotating a function with a set of argument match cases + distinct
+ * definitions (polymorphism).
+ *
+ * In this case, we should be able to infer that the `Array.from` call is
+ * not mutating its 0th argument.
+ * The 0th argument should be typed as having `effect:Mutate` only when
+ * (1) it might be a mutable iterable or
+ * (2) the 1st argument might mutate its callee
+ */
+function Component(t0) {
+ const $ = _c(4);
+ const { value } = t0;
+ const arr = [{ value: "foo" }, { value: "bar" }, { value }];
+ useIdentity();
+ const derived = Array.from(arr, _temp);
+ let t1;
+ if ($[0] !== derived) {
+ t1 = derived.at(-1);
+ $[0] = derived;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ let t2;
+ if ($[2] !== t1) {
+ t2 = {t1};
+ $[2] = t1;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ return t2;
+}
+function _temp(x, idx) {
+ return { ...x, id: idx };
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 5 }],
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
+};
+
+```
+
+### Eval output
+(kind: ok)
{"children":{"value":5,"id":2}}
+{"children":{"value":6,"id":2}}
+{"children":{"value":6,"id":2}}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js
new file mode 100644
index 0000000000..f2b364bc60
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js
@@ -0,0 +1,26 @@
+import {useIdentity, Stringify} from 'shared-runtime';
+
+/**
+ * TODO: Note that this `Array.from` is inferred to be mutating its first
+ * argument. This is because React Compiler's typing system does not yet support
+ * annotating a function with a set of argument match cases + distinct
+ * definitions (polymorphism).
+ *
+ * In this case, we should be able to infer that the `Array.from` call is
+ * not mutating its 0th argument.
+ * The 0th argument should be typed as having `effect:Mutate` only when
+ * (1) it might be a mutable iterable or
+ * (2) the 1st argument might mutate its callee
+ */
+function Component({value}) {
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
+ useIdentity();
+ const derived = Array.from(arr, (x, idx) => ({...x, id: idx}));
+ return {derived.at(-1)};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 5}],
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md
new file mode 100644
index 0000000000..66d0b42584
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md
@@ -0,0 +1,88 @@
+
+## Input
+
+```javascript
+import {useIdentity, Stringify} from 'shared-runtime';
+
+/**
+ * TODO: Note that this `Array.from` is inferred to be mutating its first
+ * argument. This is because React Compiler's typing system does not yet support
+ * annotating a function with a set of argument match cases + distinct
+ * definitions (polymorphism)
+ *
+ * In this case, we should be able to infer that the `Array.from` call is
+ * not mutating its 0th argument.
+ * The 0th argument should be typed as having `effect:Mutate` only when
+ * (1) it might be a mutable iterable or
+ * (2) the 1st argument might mutate its callee
+ */
+function Component({value}) {
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
+ useIdentity();
+ const derived = Array.from(arr);
+ return {derived.at(-1)};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 5}],
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useIdentity, Stringify } from "shared-runtime";
+
+/**
+ * TODO: Note that this `Array.from` is inferred to be mutating its first
+ * argument. This is because React Compiler's typing system does not yet support
+ * annotating a function with a set of argument match cases + distinct
+ * definitions (polymorphism)
+ *
+ * In this case, we should be able to infer that the `Array.from` call is
+ * not mutating its 0th argument.
+ * The 0th argument should be typed as having `effect:Mutate` only when
+ * (1) it might be a mutable iterable or
+ * (2) the 1st argument might mutate its callee
+ */
+function Component(t0) {
+ const $ = _c(4);
+ const { value } = t0;
+ const arr = [{ value: "foo" }, { value: "bar" }, { value }];
+ useIdentity();
+ const derived = Array.from(arr);
+ let t1;
+ if ($[0] !== derived) {
+ t1 = derived.at(-1);
+ $[0] = derived;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ let t2;
+ if ($[2] !== t1) {
+ t2 = {t1};
+ $[2] = t1;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ return t2;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 5 }],
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"children":{"value":5}}
+{"children":{"value":6}}
+{"children":{"value":6}}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js
new file mode 100644
index 0000000000..c9b09c384d
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js
@@ -0,0 +1,26 @@
+import {useIdentity, Stringify} from 'shared-runtime';
+
+/**
+ * TODO: Note that this `Array.from` is inferred to be mutating its first
+ * argument. This is because React Compiler's typing system does not yet support
+ * annotating a function with a set of argument match cases + distinct
+ * definitions (polymorphism)
+ *
+ * In this case, we should be able to infer that the `Array.from` call is
+ * not mutating its 0th argument.
+ * The 0th argument should be typed as having `effect:Mutate` only when
+ * (1) it might be a mutable iterable or
+ * (2) the 1st argument might mutate its callee
+ */
+function Component({value}) {
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
+ useIdentity();
+ const derived = Array.from(arr);
+ return {derived.at(-1)};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 5}],
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md
new file mode 100644
index 0000000000..586124280a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md
@@ -0,0 +1,64 @@
+
+## Input
+
+```javascript
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
+
+function Component({value}) {
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
+ useIdentity();
+ const derived = Array.from(arr, mutateAndReturn);
+ return {derived.at(-1)};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 5}],
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
+
+function Component(t0) {
+ const $ = _c(4);
+ const { value } = t0;
+ const arr = [{ value: "foo" }, { value: "bar" }, { value }];
+ useIdentity();
+ const derived = Array.from(arr, mutateAndReturn);
+ let t1;
+ if ($[0] !== derived) {
+ t1 = derived.at(-1);
+ $[0] = derived;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ let t2;
+ if ($[2] !== t1) {
+ t2 = {t1};
+ $[2] = t1;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ return t2;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 5 }],
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"children":{"value":5,"wat0":"joe"}}
+{"children":{"value":6,"wat0":"joe"}}
+{"children":{"value":6,"wat0":"joe"}}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js
new file mode 100644
index 0000000000..edb4e37125
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js
@@ -0,0 +1,14 @@
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
+
+function Component({value}) {
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
+ useIdentity();
+ const derived = Array.from(arr, mutateAndReturn);
+ return {derived.at(-1)};
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 5}],
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md
index 1ba01dc5bf..ea3f1d4f38 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md
@@ -68,21 +68,29 @@ function Validate({ x, input }) {
}
function useFoo(input) {
"use memo";
- const $ = _c(3);
+ const $ = _c(5);
const x = Array.from([{}]);
useIdentity();
- x.push([input]);
let t0;
- if ($[0] !== input || $[1] !== x) {
- t0 = ;
+ if ($[0] !== input) {
+ t0 = [input];
$[0] = input;
- $[1] = x;
- $[2] = t0;
+ $[1] = t0;
} else {
- t0 = $[2];
+ t0 = $[1];
}
- return t0;
+ x.push(t0);
+ let t1;
+ if ($[2] !== input || $[3] !== x) {
+ t1 = ;
+ $[2] = input;
+ $[3] = x;
+ $[4] = t1;
+ } else {
+ t1 = $[4];
+ }
+ return t1;
}
export const FIXTURE_ENTRYPOINT = {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md
similarity index 71%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md
index 6061464afc..5209fd953e 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md
@@ -37,6 +37,12 @@ function useFoo({val1, val2}) {
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{val1: 1, val2: 2}],
+ params: [
+ {val1: 1, val2: 2},
+ {val1: 1, val2: 2},
+ {val1: 1, val2: 3},
+ {val1: 4, val2: 2},
+ ],
};
```
@@ -71,29 +77,51 @@ function Validate({ x, val1, val2 }) {
}
function useFoo(t0) {
"use memo";
- const $ = _c(4);
+ const $ = _c(8);
const { val1, val2 } = t0;
const x = Array.from([]);
useIdentity();
- x.push([val1]);
- x.push([val2]);
let t1;
- if ($[0] !== val1 || $[1] !== val2 || $[2] !== x) {
- t1 = ;
+ if ($[0] !== val1) {
+ t1 = [val1];
$[0] = val1;
- $[1] = val2;
- $[2] = x;
- $[3] = t1;
+ $[1] = t1;
} else {
- t1 = $[3];
+ t1 = $[1];
}
- return t1;
+ x.push(t1);
+ let t2;
+ if ($[2] !== val2) {
+ t2 = [val2];
+ $[2] = val2;
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ x.push(t2);
+ let t3;
+ if ($[4] !== val1 || $[5] !== val2 || $[6] !== x) {
+ t3 = ;
+ $[4] = val1;
+ $[5] = val2;
+ $[6] = x;
+ $[7] = t3;
+ } else {
+ t3 = $[7];
+ }
+ return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ val1: 1, val2: 2 }],
+ params: [
+ { val1: 1, val2: 2 },
+ { val1: 1, val2: 2 },
+ { val1: 1, val2: 3 },
+ { val1: 4, val2: 2 },
+ ],
};
```
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js
similarity index 87%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js
index d1a80a4ea7..dfd4e0e0f1 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js
@@ -33,4 +33,10 @@ function useFoo({val1, val2}) {
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{val1: 1, val2: 2}],
+ params: [
+ {val1: 1, val2: 2},
+ {val1: 1, val2: 2},
+ {val1: 1, val2: 3},
+ {val1: 4, val2: 2},
+ ],
};