Merge 91b724c218 into sapling-pr-archive-mofeiZ

This commit is contained in:
mofeiZ
2025-03-04 17:22:15 -05:00
committed by GitHub
16 changed files with 730 additions and 53 deletions
@@ -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)
@@ -879,11 +879,31 @@ function inferBlock(
reason: new Set([ValueReason.Other]),
context: new Set(),
};
for (const element of instrValue.elements) {
if (element.kind === 'Spread') {
state.referenceAndRecordEffects(
freezeActions,
element.place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
} else if (element.kind === 'Identifier') {
state.referenceAndRecordEffects(
freezeActions,
element,
Effect.Capture,
ValueReason.Other,
);
} else {
let _: 'Hole' = element.kind;
}
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.Store;
continuation = {
kind: 'initialize',
valueKind,
effect: {kind: Effect.Capture, reason: ValueReason.Other},
lvalueEffect: Effect.Store,
kind: 'funeffects',
};
break;
}
@@ -1248,21 +1268,12 @@ function inferBlock(
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
if (effects !== null) {
state.referenceAndRecordEffects(
freezeActions,
place,
effects[i],
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
@@ -1314,7 +1325,10 @@ function inferBlock(
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([ValueReason.Other]),
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
@@ -1337,7 +1351,8 @@ function inferBlock(
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
// see call-spread-argument-mutable-iterator test fixture
arg.kind === 'Spread' ? Effect.ConditionallyMutate : Effect.Read,
ValueReason.Other,
);
}
@@ -1363,25 +1378,16 @@ function inferBlock(
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
if (effects !== null) {
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
effects[i],
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
@@ -2056,3 +2062,31 @@ function areArgumentsImmutableAndNonMutating(
}
return true;
}
function getArgumentEffect(
signatureEffect: Effect | null,
arg: Place | SpreadPattern,
): Effect {
if (signatureEffect != null) {
if (arg.kind === 'Identifier') {
return signatureEffect;
} else if (
signatureEffect === Effect.Mutate ||
signatureEffect === Effect.ConditionallyMutate
) {
return signatureEffect;
} else {
// see call-spread-argument-mutable-iterator test fixture
if (signatureEffect === Effect.Freeze) {
CompilerError.throwTodo({
reason: 'Support spread syntax for hook arguments',
loc: arg.place.loc,
});
}
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
return Effect.ConditionallyMutate;
}
} else {
return Effect.ConditionallyMutate;
}
}
@@ -0,0 +1,62 @@
## Input
```javascript
function useBar({arg}) {
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
const arr = [...mutableIterator];
obj.x = arg;
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useBar(t0) {
const $ = _c(2);
const { arg } = t0;
let arr;
if ($[0] !== arg) {
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
arr = [...mutableIterator];
obj.x = arg;
$[0] = arg;
$[1] = arr;
} else {
arr = $[1];
}
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{ arg: 3 }],
sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }],
};
```
### Eval output
(kind: ok) [{"x":3},5,4]
[{"x":3},5,4]
[{"x":4},5,4]
@@ -0,0 +1,20 @@
function useBar({arg}) {
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
const arr = [...mutableIterator];
obj.x = arg;
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};
@@ -0,0 +1,85 @@
## Input
```javascript
/**
* TODO: object spreads should have conditionally mutate semantics
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [3,1,5,4]
* [3,1,5,4]
* [4,1,5,4]
* Forget:
* (kind: ok) [3,1,5,4]
* [3,1,5,4]
* [4]
*/
function useBar({arg}) {
'use memo';
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const s = new Set([1, 5, 4]);
const mutableIterator = s.values();
return [arg, ...mutableIterator];
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* TODO: object spreads should have conditionally mutate semantics
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [3,1,5,4]
* [3,1,5,4]
* [4,1,5,4]
* Forget:
* (kind: ok) [3,1,5,4]
* [3,1,5,4]
* [4]
*/
function useBar(t0) {
"use memo";
const $ = _c(2);
const { arg } = t0;
let t1;
if ($[0] !== arg) {
const s = new Set([1, 5, 4]);
const mutableIterator = s.values();
t1 = [arg, ...mutableIterator];
$[0] = arg;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{ arg: 3 }],
sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }],
};
```
### Eval output
(kind: ok) [3,1,5,4]
[3,1,5,4]
[4,1,5,4]
@@ -0,0 +1,32 @@
/**
* TODO: object spreads should have conditionally mutate semantics
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [3,1,5,4]
* [3,1,5,4]
* [4,1,5,4]
* Forget:
* (kind: ok) [3,1,5,4]
* [3,1,5,4]
* [4]
*/
function useBar({arg}) {
'use memo';
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const s = new Set([1, 5, 4]);
const mutableIterator = s.values();
return [arg, ...mutableIterator];
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};
@@ -0,0 +1,42 @@
## Input
```javascript
import {useIdentity} from 'shared-runtime';
function useFoo() {
const it = new Set([1, 2]).values();
useIdentity();
return Math.max(...it);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
## Code
```javascript
import { useIdentity } from "shared-runtime";
function useFoo() {
const it = new Set([1, 2]).values();
useIdentity();
return Math.max(...it);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
### Eval output
(kind: ok) 2
2
@@ -0,0 +1,13 @@
import {useIdentity} from 'shared-runtime';
function useFoo() {
const it = new Set([1, 2]).values();
useIdentity();
return Math.max(...it);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
@@ -4,9 +4,10 @@
```javascript
import {makeArray} from 'shared-runtime';
function Component(props) {
const other = [0, 1];
function Component({}) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
const max = Math.max(...items.filter(Boolean));
const max = Math.max(2, items.push(5), ...other);
return max;
}
@@ -21,13 +22,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
3 | function Component(props) {
4 | const items = makeArray(0, 1, 2, null, 4, false, 6);
> 5 | const max = Math.max(...items.filter(Boolean));
| ^^^^^^^^ Invariant: [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier` (5:5)
6 | return max;
7 | }
8 |
4 | function Component({}) {
5 | const items = makeArray(0, 1, 2, null, 4, false, 6);
> 6 | const max = Math.max(2, items.push(5), ...other);
| ^^^^^^^^ Invariant: [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier` (6:6)
7 | return max;
8 | }
9 |
```
@@ -1,8 +1,9 @@
import {makeArray} from 'shared-runtime';
function Component(props) {
const other = [0, 1];
function Component({}) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
const max = Math.max(...items.filter(Boolean));
const max = Math.max(2, items.push(5), ...other);
return max;
}
@@ -0,0 +1,104 @@
## Input
```javascript
import {useIdentity, ValidateMemoization} from 'shared-runtime';
/**
* TODO fixture for granular iterator semantics:
* 1. ConditionallyMutate the iterator itself, depending on whether the iterator
* is a mutable iterator.
* 2. Capture effect on elements within the iterator.
*/
function Validate({x, input}) {
'use no memo';
return (
<>
<ValidateMemoization inputs={[]} output={x[0]} onlyCheckCompiled={true} />
<ValidateMemoization
inputs={[input]}
output={x[1]}
onlyCheckCompiled={true}
/>
</>
);
}
function useFoo(input) {
'use memo';
/**
* TODO: We should be able to memoize {} separately from `x`.
*/
const x = Array.from([{}]);
useIdentity();
x.push([input]);
return <Validate x={x} input={input} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [1],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useIdentity, ValidateMemoization } from "shared-runtime";
/**
* TODO fixture for granular iterator semantics:
* 1. ConditionallyMutate the iterator itself, depending on whether the iterator
* is a mutable iterator.
* 2. Capture effect on elements within the iterator.
*/
function Validate({ x, input }) {
"use no memo";
return (
<>
<ValidateMemoization inputs={[]} output={x[0]} onlyCheckCompiled={true} />
<ValidateMemoization
inputs={[input]}
output={x[1]}
onlyCheckCompiled={true}
/>
</>
);
}
function useFoo(input) {
"use memo";
const $ = _c(5);
const x = Array.from([{}]);
useIdentity();
let t0;
if ($[0] !== input) {
t0 = [input];
$[0] = input;
$[1] = t0;
} else {
t0 = $[1];
}
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 = {
fn: useFoo,
params: [1],
};
```
### Eval output
(kind: ok) <div>{"inputs":[],"output":{}}</div><div>{"inputs":[1],"output":[1]}</div>
@@ -0,0 +1,36 @@
import {useIdentity, ValidateMemoization} from 'shared-runtime';
/**
* TODO fixture for granular iterator semantics:
* 1. ConditionallyMutate the iterator itself, depending on whether the iterator
* is a mutable iterator.
* 2. Capture effect on elements within the iterator.
*/
function Validate({x, input}) {
'use no memo';
return (
<>
<ValidateMemoization inputs={[]} output={x[0]} onlyCheckCompiled={true} />
<ValidateMemoization
inputs={[input]}
output={x[1]}
onlyCheckCompiled={true}
/>
</>
);
}
function useFoo(input) {
'use memo';
/**
* TODO: We should be able to memoize {} separately from `x`.
*/
const x = Array.from([{}]);
useIdentity();
x.push([input]);
return <Validate x={x} input={input} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [1],
};
@@ -0,0 +1,48 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function Component(props) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
const max = Math.max(...items.filter(Boolean));
return max;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function Component(props) {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
t0 = Math.max(...items.filter(Boolean));
$[0] = t0;
} else {
t0 = $[0];
}
const max = t0;
return max;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
### Eval output
(kind: ok) 6
@@ -0,0 +1,13 @@
import {makeArray} from 'shared-runtime';
const other = [0, 1];
function Component({}) {
const items = makeArray(0, 1, 2, null, 4, false, 6);
const max = Math.max(2, items.push(5), ...other);
return max;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
@@ -0,0 +1,130 @@
## Input
```javascript
import {useIdentity, ValidateMemoization} from 'shared-runtime';
/**
* Fixture to assert that we can infer the type and effects of an array created
* with `Array.from`.
*/
function Validate({x, val1, val2}) {
'use no memo';
return (
<>
<ValidateMemoization
inputs={[val1]}
output={x[0]}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[val2]}
output={x[1]}
onlyCheckCompiled={true}
/>
</>
);
}
function useFoo({val1, val2}) {
'use memo';
const x = Array.from([]);
useIdentity();
x.push([val1]);
x.push([val2]);
return <Validate x={x} val1={val1} val2={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},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useIdentity, ValidateMemoization } from "shared-runtime";
/**
* Fixture to assert that we can infer the type and effects of an array created
* with `Array.from`.
*/
function Validate({ x, val1, val2 }) {
"use no memo";
return (
<>
<ValidateMemoization
inputs={[val1]}
output={x[0]}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[val2]}
output={x[1]}
onlyCheckCompiled={true}
/>
</>
);
}
function useFoo(t0) {
"use memo";
const $ = _c(8);
const { val1, val2 } = t0;
const x = Array.from([]);
useIdentity();
let t1;
if ($[0] !== val1) {
t1 = [val1];
$[0] = val1;
$[1] = t1;
} else {
t1 = $[1];
}
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 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[1],"output":[1]}</div><div>{"inputs":[2],"output":[2]}</div>
@@ -0,0 +1,42 @@
import {useIdentity, ValidateMemoization} from 'shared-runtime';
/**
* Fixture to assert that we can infer the type and effects of an array created
* with `Array.from`.
*/
function Validate({x, val1, val2}) {
'use no memo';
return (
<>
<ValidateMemoization
inputs={[val1]}
output={x[0]}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[val2]}
output={x[1]}
onlyCheckCompiled={true}
/>
</>
);
}
function useFoo({val1, val2}) {
'use memo';
const x = Array.from([]);
useIdentity();
x.push([val1]);
x.push([val2]);
return <Validate x={x} val1={val1} val2={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},
],
};