Compare commits

...

5 Commits

Author SHA1 Message Date
Mofei Zhang
c19f052b2d [compiler][ez] Move compiler gating tests
Move all gating tests to `gating/`
2025-03-13 11:52:25 -04:00
Mofei Zhang
d2d5350e65 [compiler][optim] more shapes for mixedreadonly
- Add `at`, `indexOf`, and `includes`
- Optimize MixedReadOnly which is currently only used by hook return values. Hook return values are typed as Frozen, this change propagates that to return values of aliasing function calls (such as `at`). One potential issue is that  developers may pass `enableAssumeHooksFollowRulesOfReact:false` and set `transitiveMixedData`, expecting their transitive mixed data to be mutable. This is a bit of an edge case and already doesn't have clear semantics.
2025-03-13 11:52:25 -04:00
Mofei Zhang
8ba22587b5 [compiler][optim] infer mixedReadOnly for numeric and computed properties
Expand type inference to infer mixedReadOnly types for numeric and computed property accesses.
```js
function Component({idx})
  const data = useFragment(...)
  // we want to type `posts` correctly as Array
  const posts = data.viewers[idx].posts.slice(0, 5);
  // ...
}
```
2025-03-13 11:52:25 -04:00
Mofei Zhang
7606b29048 [compiler][optim] Add shape for Array.from
(see title)
2025-03-13 11:52:25 -04:00
Mofei Zhang
54e602d891 [compiler] Patch array and argument spread mutability
Array and argument spreads may mutate stateful iterables. Spread sites should have `ConditionallyMutate` effects (e.g. mutate if the ValueKind is mutable, otherwise read).

See
- [ecma spec (13.2.4.1 Runtime Semantics: ArrayAccumulation. SpreadElement : ... AssignmentExpression)](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-arrayaccumulation).
- [ecma spec 13.3.8.1 Runtime Semantics: ArgumentListEvaluation](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-argumentlistevaluation)

Note that
- Object and JSX Attribute spreads do not evaluate iterables (srcs [mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#description), [ecma](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-propertydefinitionevaluation))
- An ideal mutability inference system could model known collections (i.e. Arrays or Sets) as a "mutated collection of non-mutable objects" (see `todo-granular-iterator-semantics`), but this is not what we do today. As such, an array / argument spread will always extend the range of built-in arrays, sets, etc
- Due to HIR limitations, call expressions with argument spreads may cause unnecessary bailouts and/or scope merging when we know the call itself has `freeze`, `capture`, or `read` semantics (e.g. `useHook(...mutableValue)`)
  We can deal with this by rewriting these call instructions to (1) create an intermediate array to consume the iterator and (2) capture and spread the array at the callsite
2025-03-13 11:52:25 -04:00
70 changed files with 1291 additions and 280 deletions

View File

@@ -663,7 +663,7 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
source: 'react-compiler-runtime',
importSpecifierName: 'shouldInstrument',
},
globalGating: '__DEV__',
globalGating: 'DEV',
},
enableEmitHookGuards: {
source: 'react-compiler-runtime',
@@ -1126,9 +1126,32 @@ export class Environment {
);
}
getFallthroughPropertyType(
receiver: Type,
_property: Type,
): BuiltInType | PolyType | null {
let shapeId = null;
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
shapeId = receiver.shapeId;
}
if (shapeId !== null) {
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
description: null,
loc: null,
suggestions: null,
});
return shape.properties.get('*') ?? null;
}
return null;
}
getPropertyType(
receiver: Type,
property: string,
property: string | number,
): BuiltInType | PolyType | null {
let shapeId = null;
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
@@ -1146,17 +1169,19 @@ export class Environment {
loc: null,
suggestions: null,
});
let value =
shape.properties.get(property) ?? shape.properties.get('*') ?? null;
if (value === null && isHookName(property)) {
value = this.#getCustomHookType();
if (typeof property === 'string') {
return (
shape.properties.get(property) ??
shape.properties.get('*') ??
(isHookName(property) ? this.#getCustomHookType() : null)
);
} else {
return shape.properties.get('*') ?? null;
}
return value;
} else if (isHookName(property)) {
} else if (typeof property === 'string' && isHookName(property)) {
return this.#getCustomHookType();
} else {
return null;
}
return null;
}
getFunctionSignature(type: FunctionType): FunctionSignature | null {

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

@@ -535,6 +535,30 @@ addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
['*', {kind: 'Object', shapeId: BuiltInRefValueId}],
]);
/**
* MixedReadOnly =
* | primitive
* | simple objects (Record<string, MixedReadOnly>)
* | Array<MixedReadOnly>
*
* APIs such as Relay — but also Flux and other data stores — often return a
* union of types with some interesting properties in terms of analysis.
*
* Given this constraint, if data came from Relay, then we should be able to
* infer things like `data.items.map(): Array`. That may seem like a leap at
* first but remember, we assume you're not patching builtins. Thus the only way
* data.items.map can exist and be a function, given the above set of data types
* and builtin JS methods, is if `data.items` was an Array, and `data.items.map`
* is therefore calling Array.prototype.map. Then we know that function returns
* an Array as well. This relies on the fact that map() is being called, so if
* data.items was some other type it would error at runtime - so it's sound.
*
* Note that this shape is currently only used for hook return values, which
* means that it's safe to type aliasing method-call return kinds as `Frozen`.
*
* Also note that all newly created arrays from method-calls (e.g. `.map`)
* have the appropriate mutable `BuiltInArray` shape
*/
addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
[
'toString',
@@ -546,6 +570,36 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
returnValueKind: ValueKind.Primitive,
}),
],
[
'indexOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'includes',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'at',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMixedReadonlyId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Frozen,
}),
],
[
'map',
addFunction(BUILTIN_SHAPES, [], {
@@ -642,9 +696,9 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: {kind: 'Poly'},
returnType: {kind: 'Object', shapeId: BuiltInMixedReadonlyId},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
returnValueKind: ValueKind.Frozen,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),

View File

@@ -60,7 +60,15 @@ export type PropType = {
kind: 'Property';
objectType: Type;
objectName: string;
propertyName: PropertyLiteral;
propertyName:
| {
kind: 'literal';
value: PropertyLiteral;
}
| {
kind: 'computed';
value: Type;
};
};
export type ObjectMethod = {

View File

@@ -872,11 +872,33 @@ 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,
isArrayType(element.place.identifier)
? Effect.Capture
: 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;
}
@@ -1241,21 +1263,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) {
@@ -1307,7 +1320,10 @@ function inferBlock(
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([ValueReason.Other]),
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
@@ -1356,25 +1372,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) {
@@ -2049,3 +2056,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;
}
}

View File

@@ -307,11 +307,26 @@ function* generateInstructionTypes(
kind: 'Property',
objectType: value.object.identifier.type,
objectName: getName(names, value.object.identifier.id),
propertyName: value.property,
propertyName: {
kind: 'literal',
value: value.property,
},
});
break;
}
case 'ComputedLoad': {
yield equation(left, {
kind: 'Property',
objectType: value.object.identifier.type,
objectName: getName(names, value.object.identifier.id),
propertyName: {
kind: 'computed',
value: value.property.identifier.type,
},
});
break;
}
case 'MethodCall': {
const returnType = makeType();
yield equation(value.property.identifier.type, {
@@ -336,7 +351,10 @@ function* generateInstructionTypes(
kind: 'Property',
objectType: value.value.identifier.type,
objectName: getName(names, value.value.identifier.id),
propertyName: makePropertyLiteral(propertyName),
propertyName: {
kind: 'literal',
value: makePropertyLiteral(propertyName),
},
});
} else {
break;
@@ -353,7 +371,10 @@ function* generateInstructionTypes(
kind: 'Property',
objectType: value.value.identifier.type,
objectName: getName(names, value.value.identifier.id),
propertyName: makePropertyLiteral(property.key.name),
propertyName: {
kind: 'literal',
value: makePropertyLiteral(property.key.name),
},
});
}
}
@@ -410,7 +431,6 @@ function* generateInstructionTypes(
case 'RegExpLiteral':
case 'MetaProperty':
case 'ComputedStore':
case 'ComputedLoad':
case 'Await':
case 'GetIterator':
case 'IteratorNext':
@@ -454,12 +474,13 @@ class Unifier {
return;
}
const objectType = this.get(tB.objectType);
let propertyType;
if (typeof tB.propertyName === 'number') {
propertyType = null;
} else {
propertyType = this.env.getPropertyType(objectType, tB.propertyName);
}
const propertyType =
tB.propertyName.kind === 'literal'
? this.env.getPropertyType(objectType, tB.propertyName.value)
: this.env.getFallthroughPropertyType(
objectType,
tB.propertyName.value,
);
if (propertyType !== null) {
this.unify(tA, propertyType);
}
@@ -677,7 +698,11 @@ class Unifier {
const RefLikeNameRE = /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/;
function isRefLikeName(t: PropType): boolean {
return RefLikeNameRE.test(t.objectName) && t.propertyName === 'current';
return (
t.propertyName.kind === 'literal' &&
RefLikeNameRE.test(t.objectName) &&
t.propertyName.value === 'current'
);
}
function tryUnionTypes(ty1: Type, ty2: Type): Type | null {

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

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

View File

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

View File

@@ -55,26 +55,20 @@ import { c as _c } from "react/compiler-runtime"; /**
function useBar(t0) {
"use memo";
const $ = _c(3);
const $ = _c(2);
const { arg } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] !== arg) {
const s = new Set([1, 5, 4]);
t1 = s.values();
$[0] = t1;
const mutableIterator = s.values();
t1 = [arg, ...mutableIterator];
$[0] = arg;
$[1] = t1;
} else {
t1 = $[0];
t1 = $[1];
}
const mutableIterator = t1;
let t2;
if ($[1] !== arg) {
t2 = [arg, ...mutableIterator];
$[1] = arg;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -84,4 +78,8 @@ export const FIXTURE_ENTRYPOINT = {
};
```
### Eval output
(kind: ok) [3,1,5,4]
[3,1,5,4]
[4,1,5,4]

View File

@@ -1,35 +0,0 @@
## Input
```javascript
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
const ErrorView = isForgetEnabled_Fixtures()
? (error, _retry) => {
const $ = _c(2);
let t0;
if ($[0] !== error) {
t0 = <MessageBox error={error} />;
$[0] = error;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
: (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;
```

View File

@@ -1,4 +0,0 @@
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ import {
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget
function useFoo(props) {
if (__DEV__ && shouldInstrument)
if (DEV && shouldInstrument)
useRenderCounter("useFoo", "/codegen-emit-imports-same-source.ts");
const $ = _c(2);
let t0;

View File

@@ -28,7 +28,7 @@ import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget
function Bar(props) {
"use forget";
if (__DEV__ && shouldInstrument)
if (DEV && shouldInstrument)
useRenderCounter("Bar", "/codegen-instrument-forget-test.ts");
const $ = _c(2);
let t0;
@@ -48,7 +48,7 @@ function NoForget(props) {
function Foo(props) {
"use forget";
if (__DEV__ && shouldInstrument)
if (DEV && shouldInstrument)
useRenderCounter("Foo", "/codegen-instrument-forget-test.ts");
const $ = _c(2);
let t0;

View File

@@ -0,0 +1,33 @@
## Input
```javascript
import {useIdentity} from 'shared-runtime';
function Component() {
const items = makeArray(0, 1, 2, null, 4, false, 6);
return useIdentity(...items.values());
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
sequentialRenders: [{}, {}],
};
```
## Error
```
3 | function Component() {
4 | const items = makeArray(0, 1, 2, null, 4, false, 6);
> 5 | return useIdentity(...items.values());
| ^^^^^^^^^^^^^^ Todo: Support spread syntax for hook arguments (5:5)
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -0,0 +1,12 @@
import {useIdentity} from 'shared-runtime';
function Component() {
const items = makeArray(0, 1, 2, null, 4, false, 6);
return useIdentity(...items.values());
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
sequentialRenders: [{}, {}],
};

View File

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

View File

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

View File

@@ -1,14 +0,0 @@
// @gating @compilationMode(annotation)
export default function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
}
function NoForget(props) {
return <Bar>{props.noForget}</Bar>;
}
export function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}

View File

@@ -0,0 +1,50 @@
## Input
```javascript
// @gating
import {Stringify} from 'shared-runtime';
const ErrorView = ({error, _retry}) => <Stringify error={error}></Stringify>;
export default ErrorView;
export const FIXTURE_ENTRYPOINT = {
fn: eval('ErrorView'),
params: [{}],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()
? (t0) => {
const $ = _c(2);
const { error } = t0;
let t1;
if ($[0] !== error) {
t1 = <Stringify error={error} />;
$[0] = error;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
: ({ error, _retry }) => <Stringify error={error}></Stringify>;
export default ErrorView;
export const FIXTURE_ENTRYPOINT = {
fn: eval("ErrorView"),
params: [{}],
};
```
### Eval output
(kind: ok) <div>{}</div>

View File

@@ -0,0 +1,10 @@
// @gating
import {Stringify} from 'shared-runtime';
const ErrorView = ({error, _retry}) => <Stringify error={error}></Stringify>;
export default ErrorView;
export const FIXTURE_ENTRYPOINT = {
fn: eval('ErrorView'),
params: [{}],
};

View File

@@ -15,9 +15,22 @@ function NoForget(props) {
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
if (props.bar < 0) {
return props.children;
}
return (
<Foo bar={props.bar - 1}>
<NoForget />
</Foo>
);
}
global.DEV = true;
export const FIXTURE_ENTRYPOINT = {
fn: eval('Foo'),
params: [{bar: 2}],
};
```
## Code
@@ -29,7 +42,7 @@ import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget
const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";
if (__DEV__ && shouldInstrument)
if (DEV && shouldInstrument)
useRenderCounter("Bar", "/codegen-instrument-forget-gating-test.ts");
const $ = _c(2);
let t0;
@@ -53,23 +66,50 @@ function NoForget(props) {
const Foo = isForgetEnabled_Fixtures()
? function Foo(props) {
"use forget";
if (__DEV__ && shouldInstrument)
if (DEV && shouldInstrument)
useRenderCounter("Foo", "/codegen-instrument-forget-gating-test.ts");
const $ = _c(2);
let t0;
if ($[0] !== props.bar) {
t0 = <Foo>{props.bar}</Foo>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
const $ = _c(3);
if (props.bar < 0) {
return props.children;
}
return t0;
const t0 = props.bar - 1;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <NoForget />;
$[0] = t1;
} else {
t1 = $[0];
}
let t2;
if ($[1] !== t0) {
t2 = <Foo bar={t0}>{t1}</Foo>;
$[1] = t0;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
: function Foo(props) {
"use forget";
return <Foo>{props.bar}</Foo>;
if (props.bar < 0) {
return props.children;
}
return (
<Foo bar={props.bar - 1}>
<NoForget />
</Foo>
);
};
global.DEV = true;
export const FIXTURE_ENTRYPOINT = {
fn: eval("Foo"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div></div>

View File

@@ -11,5 +11,18 @@ function NoForget(props) {
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
if (props.bar < 0) {
return props.children;
}
return (
<Foo bar={props.bar - 1}>
<NoForget />
</Foo>
);
}
global.DEV = true;
export const FIXTURE_ENTRYPOINT = {
fn: eval('Foo'),
params: [{bar: 2}],
};

View File

@@ -17,6 +17,11 @@ function Foo(props) {
return <Foo>{props.bar}</Foo>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};
```
## Code
@@ -66,5 +71,12 @@ const Foo = isForgetEnabled_Fixtures()
return <Foo>{props.bar}</Foo>;
};
export const FIXTURE_ENTRYPOINT = {
fn: eval("Bar"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div>2</div>

View File

@@ -12,3 +12,8 @@ function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};

View File

@@ -12,11 +12,23 @@ function NoForget(props) {
return <Bar>{props.noForget}</Bar>;
}
export function Foo(props) {
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
if (props.bar < 0) {
return props.children;
}
return (
<Foo bar={props.bar - 1}>
<NoForget />
</Foo>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};
```
## Code
@@ -47,25 +59,50 @@ export default Bar;
function NoForget(props) {
return <Bar>{props.noForget}</Bar>;
}
export const Foo = isForgetEnabled_Fixtures()
const Foo = isForgetEnabled_Fixtures()
? function Foo(props) {
"use forget";
const $ = _c(2);
let t0;
if ($[0] !== props.bar) {
t0 = <Foo>{props.bar}</Foo>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
const $ = _c(3);
if (props.bar < 0) {
return props.children;
}
return t0;
const t0 = props.bar - 1;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <NoForget />;
$[0] = t1;
} else {
t1 = $[0];
}
let t2;
if ($[1] !== t0) {
t2 = <Foo bar={t0}>{t1}</Foo>;
$[1] = t0;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
: function Foo(props) {
"use forget";
return <Foo>{props.bar}</Foo>;
if (props.bar < 0) {
return props.children;
}
return (
<Foo bar={props.bar - 1}>
<NoForget />
</Foo>
);
};
export const FIXTURE_ENTRYPOINT = {
fn: eval("Bar"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div>2</div>

View File

@@ -0,0 +1,26 @@
// @gating @compilationMode(annotation)
export default function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
}
function NoForget(props) {
return <Bar>{props.noForget}</Bar>;
}
function Foo(props) {
'use forget';
if (props.bar < 0) {
return props.children;
}
return (
<Foo bar={props.bar - 1}>
<NoForget />
</Foo>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};

View File

@@ -17,6 +17,11 @@ export function Foo(props) {
return <Foo>{props.bar}</Foo>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};
```
## Code
@@ -66,5 +71,12 @@ export const Foo = isForgetEnabled_Fixtures()
return <Foo>{props.bar}</Foo>;
};
export const FIXTURE_ENTRYPOINT = {
fn: eval("Bar"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div>2</div>

View File

@@ -12,3 +12,8 @@ export function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};

View File

@@ -17,6 +17,11 @@ function Foo(props) {
return <Foo>{props.bar}</Foo>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};
```
## Code
@@ -65,5 +70,12 @@ const Foo = isForgetEnabled_Fixtures()
return <Foo>{props.bar}</Foo>;
};
export const FIXTURE_ENTRYPOINT = {
fn: eval("Bar"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div>2</div>

View File

@@ -12,3 +12,8 @@ function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};

View File

@@ -13,6 +13,11 @@ component Component(value: string) {
export default memo<Props>(Component);
export const FIXTURE_ENTRYPOINT = {
fn: eval('Component'),
params: [{value: 'foo'}],
};
```
## Code
@@ -43,5 +48,12 @@ const Component = isForgetEnabled_Fixtures()
export default memo<Props>(Component);
export const FIXTURE_ENTRYPOINT = {
fn: eval("Component"),
params: [{ value: "foo" }],
};
```
### Eval output
(kind: ok) <div>foo</div>

View File

@@ -8,3 +8,8 @@ component Component(value: string) {
}
export default memo<Props>(Component);
export const FIXTURE_ENTRYPOINT = {
fn: eval('Component'),
params: [{value: 'foo'}],
};

View File

@@ -35,4 +35,7 @@ export default React.forwardRef(
);
```
### Eval output
(kind: exception) Fixture not implemented
logs: ['forwardRef render functions accept exactly two parameters: props and ref. %s','Did you forget to use the ref parameter?']

View File

@@ -3,17 +3,17 @@
```javascript
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
import {Stringify} from 'shared-runtime';
const Renderer = props => (
const ErrorView = (error, _retry) => <Stringify error={error}></Stringify>;
export default props => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
export default Renderer;
```
## Code
@@ -21,12 +21,14 @@ export default Renderer;
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()
? (error, _retry) => {
const $ = _c(2);
let t0;
if ($[0] !== error) {
t0 = <MessageBox error={error} />;
t0 = <Stringify error={error} />;
$[0] = error;
$[1] = t0;
} else {
@@ -34,9 +36,9 @@ const ErrorView = isForgetEnabled_Fixtures()
}
return t0;
}
: (error, _retry) => <MessageBox error={error}></MessageBox>;
: (error, _retry) => <Stringify error={error}></Stringify>;
const Renderer = isForgetEnabled_Fixtures()
export default isForgetEnabled_Fixtures()
? (props) => {
const $ = _c(1);
let t0;
@@ -59,7 +61,8 @@ const Renderer = isForgetEnabled_Fixtures()
<ErrorView></ErrorView>
</Foo>
);
export default Renderer;
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,11 @@
// @gating
import {Stringify} from 'shared-runtime';
const ErrorView = (error, _retry) => <Stringify error={error}></Stringify>;
export default props => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);

View File

@@ -3,15 +3,22 @@
```javascript
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
import {Stringify} from 'shared-runtime';
const ErrorView = (error, _retry) => <Stringify error={error}></Stringify>;
export const Renderer = props => (
<Foo>
<Bar></Bar>
<div>
<span></span>
<ErrorView></ErrorView>
</Foo>
</div>
);
export const FIXTURE_ENTRYPOINT = {
fn: eval('Renderer'),
params: [{}],
};
```
## Code
@@ -19,12 +26,14 @@ export const Renderer = props => (
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()
? (error, _retry) => {
const $ = _c(2);
let t0;
if ($[0] !== error) {
t0 = <MessageBox error={error} />;
t0 = <Stringify error={error} />;
$[0] = error;
$[1] = t0;
} else {
@@ -32,7 +41,7 @@ const ErrorView = isForgetEnabled_Fixtures()
}
return t0;
}
: (error, _retry) => <MessageBox error={error}></MessageBox>;
: (error, _retry) => <Stringify error={error}></Stringify>;
export const Renderer = isForgetEnabled_Fixtures()
? (props) => {
@@ -40,10 +49,10 @@ export const Renderer = isForgetEnabled_Fixtures()
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
<Foo>
<Bar />
<div>
<span />
<ErrorView />
</Foo>
</div>
);
$[0] = t0;
} else {
@@ -52,11 +61,17 @@ export const Renderer = isForgetEnabled_Fixtures()
return t0;
}
: (props) => (
<Foo>
<Bar></Bar>
<div>
<span></span>
<ErrorView></ErrorView>
</Foo>
</div>
);
export const FIXTURE_ENTRYPOINT = {
fn: eval("Renderer"),
params: [{}],
};
```
### Eval output
(kind: ok) <div><span></span><div>{"error":{}}</div></div>

View File

@@ -0,0 +1,16 @@
// @gating
import {Stringify} from 'shared-runtime';
const ErrorView = (error, _retry) => <Stringify error={error}></Stringify>;
export const Renderer = props => (
<div>
<span></span>
<ErrorView></ErrorView>
</div>
);
export const FIXTURE_ENTRYPOINT = {
fn: eval('Renderer'),
params: [{}],
};

View File

@@ -3,15 +3,24 @@
```javascript
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
import {Stringify} from 'shared-runtime';
export default Renderer = props => (
<Foo>
<Bar></Bar>
const ErrorView = (error, _retry) => <Stringify error={error}></Stringify>;
const Renderer = props => (
<div>
<span></span>
<ErrorView></ErrorView>
</Foo>
</div>
);
export default Renderer;
export const FIXTURE_ENTRYPOINT = {
fn: eval('Renderer'),
params: [{}],
};
```
## Code
@@ -19,12 +28,14 @@ export default Renderer = props => (
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()
? (error, _retry) => {
const $ = _c(2);
let t0;
if ($[0] !== error) {
t0 = <MessageBox error={error} />;
t0 = <Stringify error={error} />;
$[0] = error;
$[1] = t0;
} else {
@@ -32,18 +43,18 @@ const ErrorView = isForgetEnabled_Fixtures()
}
return t0;
}
: (error, _retry) => <MessageBox error={error}></MessageBox>;
: (error, _retry) => <Stringify error={error}></Stringify>;
export default Renderer = isForgetEnabled_Fixtures()
const Renderer = isForgetEnabled_Fixtures()
? (props) => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
<Foo>
<Bar />
<div>
<span />
<ErrorView />
</Foo>
</div>
);
$[0] = t0;
} else {
@@ -52,11 +63,19 @@ export default Renderer = isForgetEnabled_Fixtures()
return t0;
}
: (props) => (
<Foo>
<Bar></Bar>
<div>
<span></span>
<ErrorView></ErrorView>
</Foo>
</div>
);
export default Renderer;
export const FIXTURE_ENTRYPOINT = {
fn: eval("Renderer"),
params: [{}],
};
```
### Eval output
(kind: ok) <div><span></span><div>{"error":{}}</div></div>

View File

@@ -0,0 +1,18 @@
// @gating
import {Stringify} from 'shared-runtime';
const ErrorView = (error, _retry) => <Stringify error={error}></Stringify>;
const Renderer = props => (
<div>
<span></span>
<ErrorView></ErrorView>
</div>
);
export default Renderer;
export const FIXTURE_ENTRYPOINT = {
fn: eval('Renderer'),
params: [{}],
};

View File

@@ -1,9 +0,0 @@
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default Renderer = props => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);

View File

@@ -1,9 +0,0 @@
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export const Renderer = props => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);

View File

@@ -1,11 +0,0 @@
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
const Renderer = props => (
<Foo>
<Bar></Bar>
<ErrorView></ErrorView>
</Foo>
);
export default Renderer;

View File

@@ -0,0 +1,61 @@
## Input
```javascript
import {useFragment} from 'shared-runtime';
/**
* React compiler should infer that the returned value is a primitive and avoid
* memoizing it.
*/
function useRelayData({query, idx}) {
'use memo';
const data = useFragment('', query);
return data.a[idx].toString();
}
export const FIXTURE_ENTRYPOINT = {
fn: useRelayData,
params: [{query: '', idx: 0}],
sequentialRenders: [
{query: '', idx: 0},
{query: '', idx: 0},
{query: '', idx: 1},
],
};
```
## Code
```javascript
import { useFragment } from "shared-runtime";
/**
* React compiler should infer that the returned value is a primitive and avoid
* memoizing it.
*/
function useRelayData(t0) {
"use memo";
const { query, idx } = t0;
const data = useFragment("", query);
return data.a[idx].toString();
}
export const FIXTURE_ENTRYPOINT = {
fn: useRelayData,
params: [{ query: "", idx: 0 }],
sequentialRenders: [
{ query: "", idx: 0 },
{ query: "", idx: 0 },
{ query: "", idx: 1 },
],
};
```
### Eval output
(kind: ok) "1"
"1"
"2"

View File

@@ -0,0 +1,21 @@
import {useFragment} from 'shared-runtime';
/**
* React compiler should infer that the returned value is a primitive and avoid
* memoizing it.
*/
function useRelayData({query, idx}) {
'use memo';
const data = useFragment('', query);
return data.a[idx].toString();
}
export const FIXTURE_ENTRYPOINT = {
fn: useRelayData,
params: [{query: '', idx: 0}],
sequentialRenders: [
{query: '', idx: 0},
{query: '', idx: 0},
{query: '', idx: 1},
],
};

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

View File

@@ -222,7 +222,6 @@ const skipFilter = new Set([
'array-at-mutate-after-capture',
'array-join',
'array-push-effect',
'arrow-function-expr-gating-test',
'assignment-in-nested-if',
'await-side-effecting-promise',
'await',
@@ -335,16 +334,10 @@ const skipFilter = new Set([
'babel-existing-react-import',
'babel-existing-react-kitchensink-import',
'call',
'codegen-instrument-forget-gating-test',
'codegen-instrument-forget-test',
'conditional-on-mutable',
'constructor',
'frozen-after-alias',
'gating-test-export-default-function',
'gating-test-export-function-and-default',
'gating-test-export-function',
'gating-test',
'gating-with-hoisted-type-reference.flow',
'hook-call',
'hooks-freeze-arguments',
'hooks-freeze-possibly-mutable-arguments',
@@ -352,8 +345,6 @@ const skipFilter = new Set([
'independent',
'interdependent-across-if',
'interdependent',
'multi-arrow-expr-export-gating-test',
'multi-arrow-expr-gating-test',
'mutable-liverange-loop',
'sequence-expression',
'ssa-call-jsx-2',
@@ -361,7 +352,6 @@ const skipFilter = new Set([
'ssa-newexpression',
'ssa-shadowing',
'template-literal',
'multi-arrow-expr-export-default-gating-test',
// works, but appears differently when printing
// due to optional function argument
@@ -407,7 +397,6 @@ const skipFilter = new Set([
'infer-functions-hook-with-hook-call',
'infer-functions-hook-with-jsx',
'infer-function-expression-component',
'infer-function-expression-React-memo-gating',
'infer-skip-components-without-hooks-or-jsx',
'class-component-with-render-helper',
'fbt/fbtparam-with-jsx-element-content',
@@ -422,7 +411,7 @@ const skipFilter = new Set([
'transitive-freeze-function-expressions',
// nothing to compile/run
'repro-no-gating-import-without-compiled-functions',
'gating/repro-no-gating-import-without-compiled-functions',
// TODOs
'rules-of-hooks/todo.bail.rules-of-hooks-279ac76f53af',
@@ -462,7 +451,6 @@ const skipFilter = new Set([
// bugs
'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr',
'bug-array-spread-mutable-iterator',
`bug-capturing-func-maybealias-captured-mutate`,
'bug-aliased-capture-aliased-mutate',
'bug-aliased-capture-mutate',