[compiler][optim] Add map and set constructors (#32697)

* Adds `isConstructor: boolean` to `FunctionType`. With this PR, each
typed function can either be a constructor (currently only known
globals) or non constructor. Alternatively, we prefer to encode
polymorphic types / effects (and match the closest subtype)

* Add Map and Set globals + built-ins
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32697).
* #32698
* __->__ #32697
This commit is contained in:
mofeiZ 2025-03-23 23:19:01 -04:00 committed by GitHub
parent 45463ab3ac
commit a8e503dce0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1017 additions and 51 deletions

View File

@ -10,8 +10,10 @@ import {
BUILTIN_SHAPES,
BuiltInArrayId,
BuiltInFireId,
BuiltInMapId,
BuiltInMixedReadonlyId,
BuiltInObjectId,
BuiltInSetId,
BuiltInUseActionStateId,
BuiltInUseContextHookId,
BuiltInUseEffectHookId,
@ -458,6 +460,38 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Primitive,
}),
],
[
'Map',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutate],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
[
'Set',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutate],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
// TODO: rest of Global objects
];

View File

@ -10,7 +10,7 @@ import * as t from '@babel/types';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {Environment, ReactFunctionType} from './Environment';
import {HookKind} from './ObjectShape';
import type {HookKind} from './ObjectShape';
import {Type, makeType} from './Types';
import {z} from 'zod';
@ -829,6 +829,13 @@ export type CallExpression = {
typeArguments?: Array<t.FlowType>;
};
export type NewExpression = {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
};
export type LoadLocal = {
kind: 'LoadLocal';
place: Place;
@ -894,12 +901,7 @@ export type InstructionValue =
right: Place;
loc: SourceLocation;
}
| {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
}
| NewExpression
| CallExpression
| MethodCall
| {
@ -1649,6 +1651,14 @@ export function isArrayType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
}
export function isMapType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInMap';
}
export function isSetType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInSet';
}
export function isPropsType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
}

View File

@ -44,6 +44,7 @@ export function addFunction(
properties: Iterable<[string, BuiltInType | PolyType]>,
fn: Omit<FunctionSignature, 'hookKind'>,
id: string | null = null,
isConstructor: boolean = false,
): FunctionType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, properties, {
@ -54,6 +55,7 @@ export function addFunction(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor,
};
}
@ -73,6 +75,7 @@ export function addHook(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor: false,
};
}
@ -198,6 +201,8 @@ export type ObjectShape = {
export type ShapeRegistry = Map<string, ObjectShape>;
export const BuiltInPropsId = 'BuiltInProps';
export const BuiltInArrayId = 'BuiltInArray';
export const BuiltInSetId = 'BuiltInSet';
export const BuiltInMapId = 'BuiltInMap';
export const BuiltInFunctionId = 'BuiltInFunction';
export const BuiltInJsxId = 'BuiltInJsx';
export const BuiltInObjectId = 'BuiltInObject';
@ -451,6 +456,313 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
*/
]);
/* Built-in Set shape */
addObject(BUILTIN_SHAPES, BuiltInSetId, [
[
/**
* add(value)
* Parameters
* value: the value of the element to add to the Set object.
* Returns the Set object with added value.
*/
'add',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Store,
// returnValueKind is technically dependent on the ValueKind of the set itself
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* setInstance.delete(value)
* Returns true if value was already in Set; otherwise false.
*/
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
['size', PRIMITIVE_TYPE],
[
/**
* difference(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in this set but not in the other set.
*/
'difference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* union(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in either this set or the other set.
*/
'union',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* symmetricalDifference(other)
* Parameters
* other: A Set object, or set-like object.
* A new Set object containing elements which are in either this set or the other set, but not in both.
*/
'symmetricalDifference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* isSubsetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in this set are also in the other set, and false otherwise.
*/
'isSubsetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* isSupersetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in the other set are also in this set, and false otherwise.
*/
'isSupersetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* forEach(callbackFn)
* forEach(callbackFn, thisArg)
*/
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInMapId, [
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'get',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* Params
* key: the key of the element to add to the Map object. The key may be
* any JavaScript type (any primitive value or any type of JavaScript
* object).
* value: the value of the element to add to the Map object.
* Returns the Map object.
*/
'set',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture, Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
}),
],
['size', PRIMITIVE_TYPE],
[
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
['0', {kind: 'Poly'}],
[

View File

@ -38,6 +38,7 @@ export type FunctionType = {
kind: 'Function';
shapeId: string | null;
return: Type;
isConstructor: boolean;
};
export type ObjectType = {
@ -111,6 +112,7 @@ export function duplicateType(type: Type): Type {
kind: 'Function',
return: duplicateType(type.return),
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'Object': {

View File

@ -12,6 +12,7 @@ import {
BasicBlock,
BlockId,
CallExpression,
NewExpression,
Effect,
FunctionEffect,
GeneratedSource,
@ -39,7 +40,6 @@ import {
printSourceLocation,
} from '../HIR/PrintHIR';
import {
eachCallArgument,
eachInstructionOperand,
eachInstructionValueOperand,
eachPatternOperand,
@ -905,43 +905,12 @@ function inferBlock(
break;
}
case 'NewExpression': {
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
const valueKind: AbstractValue = {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
state.referenceAndRecordEffects(
inferCallEffects(
state,
instr as TInstruction<NewExpression>,
freezeActions,
instrValue.callee,
Effect.Read,
ValueReason.Other,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
);
for (const operand of eachCallArgument(instrValue.args)) {
state.referenceAndRecordEffects(
freezeActions,
operand,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@ -1844,7 +1813,7 @@ export function getFunctionCallSignature(
* @returns Inferred effects of function arguments, or null if inference fails.
*/
export function getFunctionEffects(
fn: MethodCall | CallExpression,
fn: MethodCall | CallExpression | NewExpression,
sig: FunctionSignature,
): Array<Effect> | null {
const results = [];
@ -1989,7 +1958,10 @@ function getArgumentEffect(
function inferCallEffects(
state: InferenceState,
instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
instr:
| TInstruction<CallExpression>
| TInstruction<MethodCall>
| TInstruction<NewExpression>,
freezeActions: Array<FreezeAction>,
signature: FunctionSignature | null,
): void {
@ -2062,9 +2034,7 @@ function inferCallEffects(
hasCaptureArgument ||= place.effect === Effect.Capture;
}
const callee =
instrValue.kind === 'CallExpression'
? instrValue.callee
: instrValue.receiver;
instrValue.kind === 'MethodCall' ? instrValue.receiver : instrValue.callee;
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
@ -2073,10 +2043,26 @@ function inferCallEffects(
ValueReason.Other,
);
} else {
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
state.referenceAndRecordEffects(
freezeActions,
callee,
Effect.ConditionallyMutate,
instrValue.kind === 'NewExpression'
? Effect.Read
: Effect.ConditionallyMutate,
ValueReason.Other,
);
}

View File

@ -261,6 +261,7 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@ -277,6 +278,7 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@ -333,6 +335,7 @@ function* generateInstructionTypes(
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: false,
});
yield equation(left, returnType);
@ -405,6 +408,7 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: BuiltInFunctionId,
return: value.loweredFunc.func.returnType,
isConstructor: false,
});
break;
}
@ -425,9 +429,20 @@ function* generateInstructionTypes(
yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId});
break;
}
case 'NewExpression': {
const returnType = makeType();
yield equation(value.callee.identifier.type, {
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: true,
});
yield equation(left, returnType);
break;
}
case 'PropertyStore':
case 'DeclareLocal':
case 'NewExpression':
case 'RegExpLiteral':
case 'MetaProperty':
case 'ComputedStore':
@ -505,7 +520,11 @@ class Unifier {
return;
}
if (tB.kind === 'Function' && tA.kind === 'Function') {
if (
tB.kind === 'Function' &&
tA.kind === 'Function' &&
tA.isConstructor === tB.isConstructor
) {
this.unify(tA.return, tB.return);
return;
}
@ -648,6 +667,7 @@ class Unifier {
kind: 'Function',
return: returnType,
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'ObjectMethod':

View File

@ -0,0 +1,77 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Map();
s.set(el1, makeArray(el1));
s.set(el2, makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(7);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Map();
let t1;
if ($[3] !== el1) {
t1 = makeArray(el1);
$[3] = el1;
$[4] = t1;
} else {
t1 = $[4];
}
s.set(el1, t1);
let t2;
if ($[5] !== el2) {
t2 = makeArray(el2);
$[5] = el2;
$[6] = t2;
} else {
t2 = $[6];
}
s.set(el2, t2);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@ -0,0 +1,17 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Map();
s.set(el1, makeArray(el1));
s.set(el2, makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@ -0,0 +1,76 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
const arr = makeArray(el1);
s.add(arr);
// Mutate after store
arr.push(el2);
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(5);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Set();
const arr = makeArray(el1);
s.add(arr);
arr.push(el2);
let t1;
if ($[3] !== el2) {
t1 = makeArray(el2);
$[3] = el2;
$[4] = t1;
} else {
t1 = $[4];
}
s.add(t1);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@ -0,0 +1,21 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
const arr = makeArray(el1);
s.add(arr);
// Mutate after store
arr.push(el2);
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@ -0,0 +1,98 @@
## Input
```javascript
const MODULE_LOCAL = new Set([4, 5, 6]);
function useFoo({propArr}: {propArr: Array<number>}) {
/* TODO: Array can be memoized separately of the Set */
const s1 = new Set([1, 2, 3]);
s1.add(propArr[0]);
/* but `.values` cannot be memoized separately */
const s2 = new Set(MODULE_LOCAL.values());
s2.add(propArr[1]);
const s3 = new Set(s2.values());
s3.add(propArr[2]);
/**
* TODO: s3 should be memoized separately of s4
*/
const s4 = new Set(s3);
s4.add(propArr[3]);
return [s1, s2, s3, s4];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [{propArr: [7, 8, 9]}, {propArr: [7, 8, 10]}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
const MODULE_LOCAL = new Set([4, 5, 6]);
function useFoo(t0) {
const $ = _c(13);
const { propArr } = t0;
let s1;
if ($[0] !== propArr[0]) {
s1 = new Set([1, 2, 3]);
s1.add(propArr[0]);
$[0] = propArr[0];
$[1] = s1;
} else {
s1 = $[1];
}
let s2;
let s3;
let s4;
if ($[2] !== propArr[1] || $[3] !== propArr[2] || $[4] !== propArr[3]) {
s2 = new Set(MODULE_LOCAL.values());
s2.add(propArr[1]);
s3 = new Set(s2.values());
s3.add(propArr[2]);
s4 = new Set(s3);
s4.add(propArr[3]);
$[2] = propArr[1];
$[3] = propArr[2];
$[4] = propArr[3];
$[5] = s2;
$[6] = s3;
$[7] = s4;
} else {
s2 = $[5];
s3 = $[6];
s4 = $[7];
}
let t1;
if ($[8] !== s1 || $[9] !== s2 || $[10] !== s3 || $[11] !== s4) {
t1 = [s1, s2, s3, s4];
$[8] = s1;
$[9] = s2;
$[10] = s3;
$[11] = s4;
$[12] = t1;
} else {
t1 = $[12];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ propArr: [7, 8, 9] }],
sequentialRenders: [{ propArr: [7, 8, 9] }, { propArr: [7, 8, 10] }],
};
```
### Eval output
(kind: ok) [{"kind":"Set","value":[1,2,3,7]},{"kind":"Set","value":[4,5,6,8]},{"kind":"Set","value":[4,5,6,8,9]},{"kind":"Set","value":[4,5,6,8,9,null]}]
[{"kind":"Set","value":[1,2,3,7]},{"kind":"Set","value":[4,5,6,8]},{"kind":"Set","value":[4,5,6,8,10]},{"kind":"Set","value":[4,5,6,8,10,null]}]

View File

@ -0,0 +1,26 @@
const MODULE_LOCAL = new Set([4, 5, 6]);
function useFoo({propArr}: {propArr: Array<number>}) {
/* TODO: Array can be memoized separately of the Set */
const s1 = new Set([1, 2, 3]);
s1.add(propArr[0]);
/* but `.values` cannot be memoized separately */
const s2 = new Set(MODULE_LOCAL.values());
s2.add(propArr[1]);
const s3 = new Set(s2.values());
s3.add(propArr[2]);
/**
* TODO: s3 should be memoized separately of s4
*/
const s4 = new Set(s3);
s4.add(propArr[3]);
return [s1, s2, s3, s4];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [{propArr: [7, 8, 9]}, {propArr: [7, 8, 10]}],
};

View File

@ -0,0 +1,77 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
s.add(makeArray(el1));
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(7);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Set();
let t1;
if ($[3] !== el1) {
t1 = makeArray(el1);
$[3] = el1;
$[4] = t1;
} else {
t1 = $[4];
}
s.add(t1);
let t2;
if ($[5] !== el2) {
t2 = makeArray(el2);
$[5] = el2;
$[6] = t2;
} else {
t2 = $[6];
}
s.add(t2);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@ -0,0 +1,17 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
s.add(makeArray(el1));
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@ -0,0 +1,82 @@
## Input
```javascript
import {makeArray, mutate} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
const s2 = new Set(s1);
// this may also may mutate s1
mutate(s2);
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray, mutate } from "shared-runtime";
function useFoo(t0) {
const $ = _c(6);
const { propArr } = t0;
let s1;
let s2;
if ($[0] !== propArr[0]) {
s1 = new Set([1, 2, 3]);
s1.add(makeArray(propArr[0]));
s2 = new Set(s1);
mutate(s2);
$[0] = propArr[0];
$[1] = s1;
$[2] = s2;
} else {
s1 = $[1];
s2 = $[2];
}
let t1;
if ($[3] !== s1 || $[4] !== s2) {
t1 = [s1, s2];
$[3] = s1;
$[4] = s2;
$[5] = t1;
} else {
t1 = $[5];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ propArr: [7, 8, 9] }],
sequentialRenders: [
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 10] },
],
};
```
### Eval output
(kind: ok) [{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]

View File

@ -0,0 +1,22 @@
import {makeArray, mutate} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
const s2 = new Set(s1);
// this may also may mutate s1
mutate(s2);
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};

View File

@ -0,0 +1,65 @@
## Input
```javascript
import {makeArray, useHook} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
useHook();
const s2 = new Set();
for (const el of s1.values()) {
s2.add(el);
}
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};
```
## Code
```javascript
import { makeArray, useHook } from "shared-runtime";
function useFoo(t0) {
const { propArr } = t0;
const s1 = new Set([1, 2, 3]);
s1.add(makeArray(propArr[0]));
useHook();
const s2 = new Set();
for (const el of s1.values()) {
s2.add(el);
}
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ propArr: [7, 8, 9] }],
sequentialRenders: [
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 10] },
],
};
```
### Eval output
(kind: ok) [{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]

View File

@ -0,0 +1,24 @@
import {makeArray, useHook} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
useHook();
const s2 = new Set();
for (const el of s1.values()) {
s2.add(el);
}
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};