mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge de74608536 into sapling-pr-archive-mofeiZ
This commit is contained in:
@@ -10,8 +10,10 @@ import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInFireId,
|
||||
BuiltInMapId,
|
||||
BuiltInMixedReadonlyId,
|
||||
BuiltInObjectId,
|
||||
BuiltInSetId,
|
||||
BuiltInUseActionStateId,
|
||||
BuiltInUseContextHookId,
|
||||
BuiltInUseEffectHookId,
|
||||
@@ -63,8 +65,6 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
|
||||
'Int8Array',
|
||||
'Int16Array',
|
||||
'Int32Array',
|
||||
'Map',
|
||||
'Set',
|
||||
'WeakMap',
|
||||
'Uint8Array',
|
||||
'Uint8ClampedArray',
|
||||
@@ -138,7 +138,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
'from',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutate,
|
||||
],
|
||||
@@ -458,6 +458,38 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'Map',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.ConditionallyMutateIterator],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInMapId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'Set',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.ConditionallyMutateIterator],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInSetId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
// TODO: rest of Global objects
|
||||
];
|
||||
|
||||
|
||||
@@ -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
|
||||
| {
|
||||
@@ -1394,6 +1396,7 @@ export enum Effect {
|
||||
Read = 'read',
|
||||
// This reference reads and stores the value
|
||||
Capture = 'capture',
|
||||
ConditionallyMutateIterator = 'mutate-iterator?',
|
||||
/*
|
||||
* This reference *may* write to (mutate) the value. This covers two similar cases:
|
||||
* - The compiler is being conservative and assuming that a value *may* be mutated
|
||||
@@ -1412,11 +1415,11 @@ export enum Effect {
|
||||
// This reference may alias to (mutate) the value
|
||||
Store = 'store',
|
||||
}
|
||||
|
||||
export const EffectSchema = z.enum([
|
||||
Effect.Read,
|
||||
Effect.Mutate,
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
Effect.Capture,
|
||||
Effect.Store,
|
||||
Effect.Freeze,
|
||||
@@ -1430,6 +1433,7 @@ export function isMutableEffect(
|
||||
case Effect.Capture:
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.ConditionallyMutateIterator:
|
||||
case Effect.Mutate: {
|
||||
return true;
|
||||
}
|
||||
@@ -1649,6 +1653,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';
|
||||
}
|
||||
|
||||
@@ -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,311 @@ 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: [],
|
||||
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,
|
||||
}),
|
||||
],
|
||||
/**
|
||||
* Iteraturs
|
||||
*/
|
||||
[
|
||||
'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,
|
||||
}),
|
||||
],
|
||||
/**
|
||||
* Iteraturs
|
||||
*/
|
||||
[
|
||||
'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'}],
|
||||
[
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
Identifier,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
isArrayType,
|
||||
isMapType,
|
||||
isRefOrRefValue,
|
||||
isSetType,
|
||||
makeInstructionId,
|
||||
Place,
|
||||
} from '../HIR/HIR';
|
||||
@@ -90,6 +93,17 @@ function inferPlace(
|
||||
infer(place, instrId);
|
||||
}
|
||||
return;
|
||||
case Effect.ConditionallyMutateIterator: {
|
||||
const identifier = place.identifier;
|
||||
if (
|
||||
!isArrayType(identifier) &&
|
||||
!isSetType(identifier) &&
|
||||
!isMapType(identifier)
|
||||
) {
|
||||
infer(place, instrId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.Mutate: {
|
||||
infer(place, instrId);
|
||||
|
||||
@@ -230,6 +230,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
case Effect.Capture:
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.ConditionallyMutateIterator:
|
||||
case Effect.Mutate: {
|
||||
if (isMutable(instruction, operand)) {
|
||||
reactiveIdentifiers.markReactive(operand);
|
||||
|
||||
+168
-192
@@ -12,6 +12,7 @@ import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
CallExpression,
|
||||
NewExpression,
|
||||
Effect,
|
||||
FunctionEffect,
|
||||
GeneratedSource,
|
||||
@@ -23,12 +24,15 @@ import {
|
||||
Phi,
|
||||
Place,
|
||||
SpreadPattern,
|
||||
TInstruction,
|
||||
Type,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
isArrayType,
|
||||
isMapType,
|
||||
isMutableEffect,
|
||||
isObjectType,
|
||||
isSetType,
|
||||
} from '../HIR/HIR';
|
||||
import {FunctionSignature} from '../HIR/ObjectShape';
|
||||
import {
|
||||
@@ -468,6 +472,25 @@ class InferenceState {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Effect.ConditionallyMutateIterator: {
|
||||
if (
|
||||
valueKind.kind === ValueKind.Mutable ||
|
||||
valueKind.kind === ValueKind.Context
|
||||
) {
|
||||
if (
|
||||
isArrayType(place.identifier) ||
|
||||
isSetType(place.identifier) ||
|
||||
isMapType(place.identifier)
|
||||
) {
|
||||
effect = Effect.Capture;
|
||||
} else {
|
||||
effect = Effect.ConditionallyMutate;
|
||||
}
|
||||
} else {
|
||||
effect = Effect.Read;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Effect.Mutate: {
|
||||
effect = Effect.Mutate;
|
||||
break;
|
||||
@@ -879,9 +902,7 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
element.place,
|
||||
isArrayType(element.place.identifier)
|
||||
? Effect.Capture
|
||||
: Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
ValueReason.Other,
|
||||
);
|
||||
} else if (element.kind === 'Identifier') {
|
||||
@@ -904,43 +925,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;
|
||||
}
|
||||
@@ -1238,62 +1228,12 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'CallExpression': {
|
||||
const signature = getFunctionCallSignature(
|
||||
env,
|
||||
instrValue.callee.identifier.type,
|
||||
inferCallEffects(
|
||||
state,
|
||||
instr as TInstruction<CallExpression>,
|
||||
freezeActions,
|
||||
getFunctionCallSignature(env, instrValue.callee.identifier.type),
|
||||
);
|
||||
|
||||
const effects =
|
||||
signature !== null ? getFunctionEffects(instrValue, signature) : null;
|
||||
const returnValueKind: AbstractValue =
|
||||
signature !== null
|
||||
? {
|
||||
kind: signature.returnValueKind,
|
||||
reason: new Set([
|
||||
signature.returnValueReason ??
|
||||
ValueReason.KnownReturnSignature,
|
||||
]),
|
||||
context: new Set(),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
let hasCaptureArgument = false;
|
||||
for (let i = 0; i < instrValue.args.length; i++) {
|
||||
const arg = instrValue.args[i];
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
place,
|
||||
getArgumentEffect(effects != null ? effects[i] : null, arg),
|
||||
ValueReason.Other,
|
||||
);
|
||||
hasCaptureArgument ||= place.effect === Effect.Capture;
|
||||
}
|
||||
if (signature !== null) {
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.callee,
|
||||
signature.calleeEffect,
|
||||
ValueReason.Other,
|
||||
);
|
||||
} else {
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.callee,
|
||||
Effect.ConditionallyMutate,
|
||||
ValueReason.Other,
|
||||
);
|
||||
}
|
||||
hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture;
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
break;
|
||||
}
|
||||
@@ -1311,102 +1251,12 @@ function inferBlock(
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
);
|
||||
|
||||
const signature = getFunctionCallSignature(
|
||||
env,
|
||||
instrValue.property.identifier.type,
|
||||
inferCallEffects(
|
||||
state,
|
||||
instr as TInstruction<MethodCall>,
|
||||
freezeActions,
|
||||
getFunctionCallSignature(env, instrValue.property.identifier.type),
|
||||
);
|
||||
|
||||
const returnValueKind: AbstractValue =
|
||||
signature !== null
|
||||
? {
|
||||
kind: signature.returnValueKind,
|
||||
reason: new Set([
|
||||
signature.returnValueReason ??
|
||||
ValueReason.KnownReturnSignature,
|
||||
]),
|
||||
context: new Set(),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
|
||||
if (
|
||||
signature !== null &&
|
||||
signature.mutableOnlyIfOperandsAreMutable &&
|
||||
areArgumentsImmutableAndNonMutating(state, instrValue.args)
|
||||
) {
|
||||
/*
|
||||
* None of the args are mutable or mutate their params, we can downgrade to
|
||||
* treating as all reads (except that the receiver may be captured)
|
||||
*/
|
||||
for (const arg of instrValue.args) {
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
place,
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
);
|
||||
}
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.receiver,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect =
|
||||
instrValue.receiver.effect === Effect.Capture
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
break;
|
||||
}
|
||||
|
||||
const effects =
|
||||
signature !== null ? getFunctionEffects(instrValue, signature) : null;
|
||||
let hasCaptureArgument = false;
|
||||
for (let i = 0; i < instrValue.args.length; i++) {
|
||||
const arg = instrValue.args[i];
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
/*
|
||||
* 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) {
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.receiver,
|
||||
signature.calleeEffect,
|
||||
ValueReason.Other,
|
||||
);
|
||||
} else {
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.receiver,
|
||||
Effect.ConditionallyMutate,
|
||||
ValueReason.Other,
|
||||
);
|
||||
}
|
||||
hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture;
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
break;
|
||||
}
|
||||
@@ -1813,7 +1663,13 @@ function inferBlock(
|
||||
kind === ValueKind.Mutable || kind === ValueKind.Context;
|
||||
let effect;
|
||||
let valueKind: AbstractValue;
|
||||
if (!isMutable || isArrayType(instrValue.collection.identifier)) {
|
||||
const iterator = instrValue.collection.identifier;
|
||||
if (
|
||||
!isMutable ||
|
||||
isArrayType(iterator) ||
|
||||
isMapType(iterator) ||
|
||||
isSetType(iterator)
|
||||
) {
|
||||
// Case 1, assume iterator is a separate mutable object
|
||||
effect = {
|
||||
kind: Effect.Read,
|
||||
@@ -1854,7 +1710,7 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.iterator,
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
ValueReason.Other,
|
||||
);
|
||||
/**
|
||||
@@ -1983,7 +1839,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 = [];
|
||||
@@ -2016,6 +1872,7 @@ export function isKnownMutableEffect(effect: Effect): boolean {
|
||||
switch (effect) {
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.ConditionallyMutateIterator:
|
||||
case Effect.Mutate: {
|
||||
return true;
|
||||
}
|
||||
@@ -2119,9 +1976,128 @@ function getArgumentEffect(
|
||||
});
|
||||
}
|
||||
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
|
||||
return Effect.ConditionallyMutate;
|
||||
return Effect.ConditionallyMutateIterator;
|
||||
}
|
||||
} else {
|
||||
return Effect.ConditionallyMutate;
|
||||
}
|
||||
}
|
||||
|
||||
function inferCallEffects(
|
||||
state: InferenceState,
|
||||
instr:
|
||||
| TInstruction<CallExpression>
|
||||
| TInstruction<MethodCall>
|
||||
| TInstruction<NewExpression>,
|
||||
freezeActions: Array<FreezeAction>,
|
||||
signature: FunctionSignature | null,
|
||||
): void {
|
||||
const instrValue = instr.value;
|
||||
const returnValueKind: AbstractValue =
|
||||
signature !== null
|
||||
? {
|
||||
kind: signature.returnValueKind,
|
||||
reason: new Set([
|
||||
signature.returnValueReason ?? ValueReason.KnownReturnSignature,
|
||||
]),
|
||||
context: new Set(),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
|
||||
if (
|
||||
instrValue.kind === 'MethodCall' &&
|
||||
signature !== null &&
|
||||
signature.mutableOnlyIfOperandsAreMutable &&
|
||||
areArgumentsImmutableAndNonMutating(state, instrValue.args)
|
||||
) {
|
||||
/*
|
||||
* None of the args are mutable or mutate their params, we can downgrade to
|
||||
* treating as all reads (except that the receiver may be captured)
|
||||
*/
|
||||
for (const arg of instrValue.args) {
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
place,
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
);
|
||||
}
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.receiver,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect =
|
||||
instrValue.receiver.effect === Effect.Capture
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
return;
|
||||
}
|
||||
|
||||
const effects =
|
||||
signature !== null ? getFunctionEffects(instrValue, signature) : null;
|
||||
let hasCaptureArgument = false;
|
||||
for (let i = 0; i < instrValue.args.length; i++) {
|
||||
const arg = instrValue.args[i];
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
const callee =
|
||||
instrValue.kind === 'MethodCall' ? instrValue.receiver : instrValue.callee;
|
||||
if (signature !== null) {
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
callee,
|
||||
signature.calleeEffect,
|
||||
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,
|
||||
instrValue.kind === 'NewExpression'
|
||||
? Effect.Read
|
||||
: Effect.ConditionallyMutate,
|
||||
ValueReason.Other,
|
||||
);
|
||||
}
|
||||
hasCaptureArgument ||= callee.effect === Effect.Capture;
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
}
|
||||
|
||||
@@ -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':
|
||||
@@ -506,8 +521,10 @@ class Unifier {
|
||||
}
|
||||
|
||||
if (tB.kind === 'Function' && tA.kind === 'Function') {
|
||||
this.unify(tA.return, tB.return);
|
||||
return;
|
||||
if (tA.isConstructor === tB.isConstructor) {
|
||||
this.unify(tA.return, tB.return);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,6 +665,7 @@ class Unifier {
|
||||
kind: 'Function',
|
||||
return: returnType,
|
||||
shapeId: type.shapeId,
|
||||
isConstructor: type.isConstructor,
|
||||
};
|
||||
}
|
||||
case 'ObjectMethod':
|
||||
|
||||
+44
-17
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
|
||||
* (2) the 1st argument might mutate its callee
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(10);
|
||||
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;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = { value: "foo" };
|
||||
t2 = { value: "bar" };
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[0];
|
||||
t2 = $[1];
|
||||
}
|
||||
return t2;
|
||||
let t3;
|
||||
if ($[2] !== value) {
|
||||
t3 = [t1, t2, { value }];
|
||||
$[2] = value;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
const arr = t3;
|
||||
useIdentity();
|
||||
let t4;
|
||||
if ($[4] !== arr) {
|
||||
t4 = Array.from(arr, _temp);
|
||||
$[4] = arr;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
const derived = t4;
|
||||
let t5;
|
||||
if ($[6] !== derived) {
|
||||
t5 = derived.at(-1);
|
||||
$[6] = derived;
|
||||
$[7] = t5;
|
||||
} else {
|
||||
t5 = $[7];
|
||||
}
|
||||
let t6;
|
||||
if ($[8] !== t5) {
|
||||
t6 = <Stringify>{t5}</Stringify>;
|
||||
$[8] = t5;
|
||||
$[9] = t6;
|
||||
} else {
|
||||
t6 = $[9];
|
||||
}
|
||||
return t6;
|
||||
}
|
||||
function _temp(x, idx) {
|
||||
return { ...x, id: idx };
|
||||
|
||||
+44
-17
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
|
||||
* (2) the 1st argument might mutate its callee
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(10);
|
||||
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;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = { value: "foo" };
|
||||
t2 = { value: "bar" };
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[0];
|
||||
t2 = $[1];
|
||||
}
|
||||
return t2;
|
||||
let t3;
|
||||
if ($[2] !== value) {
|
||||
t3 = [t1, t2, { value }];
|
||||
$[2] = value;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
const arr = t3;
|
||||
useIdentity();
|
||||
let t4;
|
||||
if ($[4] !== arr) {
|
||||
t4 = Array.from(arr);
|
||||
$[4] = arr;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
const derived = t4;
|
||||
let t5;
|
||||
if ($[6] !== derived) {
|
||||
t5 = derived.at(-1);
|
||||
$[6] = derived;
|
||||
$[7] = t5;
|
||||
} else {
|
||||
t5 = $[7];
|
||||
}
|
||||
let t6;
|
||||
if ($[8] !== t5) {
|
||||
t6 = <Stringify>{t5}</Stringify>;
|
||||
$[8] = t5;
|
||||
$[9] = t6;
|
||||
} else {
|
||||
t6 = $[9];
|
||||
}
|
||||
return t6;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+6
-5
@@ -7,7 +7,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
const derived = Array.from(arr).map(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
@@ -19,7 +19,7 @@ function Component({value}) {
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
|
||||
};
|
||||
|
||||
```
|
||||
@@ -35,7 +35,7 @@ function Component(t0) {
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
const derived = Array.from(arr).map(mutateAndReturn);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(0);
|
||||
@@ -72,7 +72,7 @@ function Component(t0) {
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 5 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
|
||||
};
|
||||
|
||||
```
|
||||
@@ -80,4 +80,5 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}</div>
|
||||
+2
-2
@@ -3,7 +3,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
const derived = Array.from(arr).map(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
@@ -15,5 +15,5 @@ function Component({value}) {
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
|
||||
};
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
|
||||
* the spread argument since it is a Set
|
||||
*/
|
||||
function useFoo() {
|
||||
const s = new Set([1, 2]);
|
||||
useIdentity(null);
|
||||
return [Math.max(...s), s];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
|
||||
* the spread argument since it is a Set
|
||||
*/
|
||||
function useFoo() {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Set([1, 2]);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const s = t0;
|
||||
useIdentity(null);
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [Math.max(...s), s];
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [2,{"kind":"Set","value":[1,2]}]
|
||||
[2,{"kind":"Set","value":[1,2]}]
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import {useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
|
||||
* the spread argument since it is a Set
|
||||
*/
|
||||
function useFoo() {
|
||||
const s = new Set([1, 2]);
|
||||
useIdentity(null);
|
||||
return [Math.max(...s), s];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
+77
@@ -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
|
||||
+17
@@ -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'},
|
||||
],
|
||||
};
|
||||
+76
@@ -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
|
||||
+21
@@ -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'},
|
||||
],
|
||||
};
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
const MODULE_LOCAL = new Set([4, 5, 6]);
|
||||
function useFoo({propArr}: {propArr: Array<number>}) {
|
||||
/* 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]);
|
||||
|
||||
/**
|
||||
* s4 should be memoized separately from s3
|
||||
*/
|
||||
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(15);
|
||||
const { propArr } = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [1, 2, 3];
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let s1;
|
||||
if ($[1] !== propArr[0]) {
|
||||
s1 = new Set(t1);
|
||||
s1.add(propArr[0]);
|
||||
$[1] = propArr[0];
|
||||
$[2] = s1;
|
||||
} else {
|
||||
s1 = $[2];
|
||||
}
|
||||
let s2;
|
||||
let s3;
|
||||
if ($[3] !== propArr[1] || $[4] !== propArr[2]) {
|
||||
s2 = new Set(MODULE_LOCAL.values());
|
||||
s2.add(propArr[1]);
|
||||
|
||||
s3 = new Set(s2.values());
|
||||
s3.add(propArr[2]);
|
||||
$[3] = propArr[1];
|
||||
$[4] = propArr[2];
|
||||
$[5] = s2;
|
||||
$[6] = s3;
|
||||
} else {
|
||||
s2 = $[5];
|
||||
s3 = $[6];
|
||||
}
|
||||
let s4;
|
||||
if ($[7] !== propArr[3] || $[8] !== s3) {
|
||||
s4 = new Set(s3);
|
||||
s4.add(propArr[3]);
|
||||
$[7] = propArr[3];
|
||||
$[8] = s3;
|
||||
$[9] = s4;
|
||||
} else {
|
||||
s4 = $[9];
|
||||
}
|
||||
let t2;
|
||||
if ($[10] !== s1 || $[11] !== s2 || $[12] !== s3 || $[13] !== s4) {
|
||||
t2 = [s1, s2, s3, s4];
|
||||
$[10] = s1;
|
||||
$[11] = s2;
|
||||
$[12] = s3;
|
||||
$[13] = s4;
|
||||
$[14] = t2;
|
||||
} else {
|
||||
t2 = $[14];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
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]}]
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const MODULE_LOCAL = new Set([4, 5, 6]);
|
||||
function useFoo({propArr}: {propArr: Array<number>}) {
|
||||
/* 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]);
|
||||
|
||||
/**
|
||||
* s4 should be memoized separately from s3
|
||||
*/
|
||||
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]}],
|
||||
};
|
||||
+77
@@ -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
|
||||
+17
@@ -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'},
|
||||
],
|
||||
};
|
||||
+82
@@ -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 ]]"]}]
|
||||
+22
@@ -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]},
|
||||
],
|
||||
};
|
||||
+65
@@ -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 ]]"]}]
|
||||
+24
@@ -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]},
|
||||
],
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = new Set(arr).forEach(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{[...derived]}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(2);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity();
|
||||
const derived = new Set(arr).forEach(mutateAndReturn);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = <Stringify>{[...derived]}</Stringify>;
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 5 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: derived is not iterable ]]
|
||||
[[ (exception in render) TypeError: derived is not iterable ]]
|
||||
[[ (exception in render) TypeError: derived is not iterable ]]
|
||||
[[ (exception in render) TypeError: derived is not iterable ]]
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = new Set(arr).forEach(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{[...derived]}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
|
||||
};
|
||||
+26
-20
@@ -5,7 +5,7 @@
|
||||
import {useIdentity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* TODO fixture for granular iterator semantics:
|
||||
* 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.
|
||||
@@ -26,7 +26,7 @@ function Validate({x, input}) {
|
||||
function useFoo(input) {
|
||||
'use memo';
|
||||
/**
|
||||
* TODO: We should be able to memoize {} separately from `x`.
|
||||
* We should be able to memoize {} separately from `x`.
|
||||
*/
|
||||
const x = Array.from([{}]);
|
||||
useIdentity();
|
||||
@@ -48,7 +48,7 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { useIdentity, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* TODO fixture for granular iterator semantics:
|
||||
* 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.
|
||||
@@ -68,29 +68,35 @@ function Validate({ x, input }) {
|
||||
}
|
||||
function useFoo(input) {
|
||||
"use memo";
|
||||
const $ = _c(5);
|
||||
|
||||
const x = Array.from([{}]);
|
||||
useIdentity();
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if ($[0] !== input) {
|
||||
t0 = [input];
|
||||
$[0] = input;
|
||||
$[1] = t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = [{}];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[0];
|
||||
}
|
||||
x.push(t0);
|
||||
const x = Array.from(t0);
|
||||
useIdentity();
|
||||
let t1;
|
||||
if ($[2] !== input || $[3] !== x) {
|
||||
t1 = <Validate x={x} input={input} />;
|
||||
$[2] = input;
|
||||
$[3] = x;
|
||||
$[4] = t1;
|
||||
if ($[1] !== input) {
|
||||
t1 = [input];
|
||||
$[1] = input;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
x.push(t1);
|
||||
let t2;
|
||||
if ($[3] !== input || $[4] !== x) {
|
||||
t2 = <Validate x={x} input={input} />;
|
||||
$[3] = input;
|
||||
$[4] = x;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import {useIdentity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* TODO fixture for granular iterator semantics:
|
||||
* 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.
|
||||
@@ -22,7 +22,7 @@ function Validate({x, input}) {
|
||||
function useFoo(input) {
|
||||
'use memo';
|
||||
/**
|
||||
* TODO: We should be able to memoize {} separately from `x`.
|
||||
* We should be able to memoize {} separately from `x`.
|
||||
*/
|
||||
const x = Array.from([{}]);
|
||||
useIdentity();
|
||||
|
||||
+29
-23
@@ -77,40 +77,46 @@ function Validate({ x, val1, val2 }) {
|
||||
}
|
||||
function useFoo(t0) {
|
||||
"use memo";
|
||||
const $ = _c(8);
|
||||
const $ = _c(9);
|
||||
const { val1, val2 } = t0;
|
||||
|
||||
const x = Array.from([]);
|
||||
useIdentity();
|
||||
let t1;
|
||||
if ($[0] !== val1) {
|
||||
t1 = [val1];
|
||||
$[0] = val1;
|
||||
$[1] = t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t1 = $[0];
|
||||
}
|
||||
x.push(t1);
|
||||
const x = Array.from(t1);
|
||||
useIdentity();
|
||||
let t2;
|
||||
if ($[2] !== val2) {
|
||||
t2 = [val2];
|
||||
$[2] = val2;
|
||||
$[3] = t2;
|
||||
if ($[1] !== val1) {
|
||||
t2 = [val1];
|
||||
$[1] = val1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t2 = $[2];
|
||||
}
|
||||
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;
|
||||
if ($[3] !== val2) {
|
||||
t3 = [val2];
|
||||
$[3] = val2;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
x.push(t3);
|
||||
let t4;
|
||||
if ($[5] !== val1 || $[6] !== val2 || $[7] !== x) {
|
||||
t4 = <Validate x={x} val1={val1} val2={val2} />;
|
||||
$[5] = val1;
|
||||
$[6] = val2;
|
||||
$[7] = x;
|
||||
$[8] = t4;
|
||||
} else {
|
||||
t4 = $[8];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
Reference in New Issue
Block a user