[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
This commit is contained in:
mofeiZ 2025-03-13 11:58:17 -04:00 committed by GitHub
parent ed1264f077
commit 38a7600920
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 385 additions and 20 deletions

View File

@ -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)

View File

@ -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 <Stringify>{derived.at(-1)}</Stringify>;
}
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 = <Stringify>{t1}</Stringify>;
$[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) <div>{"children":{"value":5,"id":2}}</div>
<div>{"children":{"value":6,"id":2}}</div>
<div>{"children":{"value":6,"id":2}}</div>

View File

@ -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 <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@ -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 <Stringify>{derived.at(-1)}</Stringify>;
}
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 = <Stringify>{t1}</Stringify>;
$[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) <div>{"children":{"value":5}}</div>
<div>{"children":{"value":6}}</div>
<div>{"children":{"value":6}}</div>

View File

@ -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 <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@ -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 <Stringify>{derived.at(-1)}</Stringify>;
}
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 = <Stringify>{t1}</Stringify>;
$[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) <div>{"children":{"value":5,"wat0":"joe"}}</div>
<div>{"children":{"value":6,"wat0":"joe"}}</div>
<div>{"children":{"value":6,"wat0":"joe"}}</div>

View File

@ -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 <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@ -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 = <Validate x={x} input={input} />;
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 = <Validate x={x} input={input} />;
$[2] = input;
$[3] = x;
$[4] = t1;
} else {
t1 = $[4];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {

View File

@ -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 = <Validate x={x} val1={val1} val2={val2} />;
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 = <Validate x={x} val1={val1} val2={val2} />;
$[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 },
],
};
```

View File

@ -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},
],
};