Compare commits

..

2 Commits

Author SHA1 Message Date
Joe Savona
d58c07b563 [compiler] New mutability/aliasing model
Squashed, review-friendly version of the stack from https://github.com/facebook/react/pull/33488.

This is new version of our mutability and inference model, designed to replace the core algorithm for determining the sets of instructions involved in constructing a given value or set of values. The new model replaces InferReferenceEffects, InferMutableRanges (and all of its subcomponents), and parts of AnalyzeFunctions. The new model does not use per-Place effect values, but in order to make this drop-in the end _result_ of the inference adds these per-Place effects.

I'll write up a larger document on the model, first i'm doing some housekeeping to rebase the PR.
2025-06-18 09:53:47 -07:00
Joe Savona
631b1cfc74 [compiler] Remove unnecessary fixture
This is covered by iife-inline-ternary
2025-06-18 09:53:45 -07:00
100 changed files with 318 additions and 3583 deletions

View File

@@ -72,7 +72,7 @@ export function lower(
env: Environment,
// Bindings captured from the outer function, in case lower() is called recursively (for lambdas)
bindings: Bindings | null = null,
capturedRefs: Map<t.Identifier, SourceLocation> = new Map(),
capturedRefs: Array<t.Identifier> = [],
): Result<HIRFunction, CompilerError> {
const builder = new HIRBuilder(env, {
bindings,
@@ -80,13 +80,13 @@ export function lower(
});
const context: HIRFunction['context'] = [];
for (const [ref, loc] of capturedRefs ?? []) {
for (const ref of capturedRefs ?? []) {
context.push({
kind: 'Identifier',
identifier: builder.resolveBinding(ref),
effect: Effect.Unknown,
reactive: false,
loc,
loc: ref.loc ?? GeneratedSource,
});
}
@@ -3439,12 +3439,10 @@ function lowerFunction(
* This isn't a problem in practice because use Babel's scope analysis to
* identify the correct references.
*/
const lowering = lower(
expr,
builder.environment,
builder.bindings,
new Map([...builder.context, ...capturedContext]),
);
const lowering = lower(expr, builder.environment, builder.bindings, [
...builder.context,
...capturedContext,
]);
let loweredFunc: HIRFunction;
if (lowering.isErr()) {
lowering
@@ -4162,11 +4160,6 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set<Scope> {
return scopes;
}
/**
* Returns a mapping of "context" identifiers — references to free variables that
* will become part of the function expression's `context` array — along with the
* source location of their first reference within the function.
*/
function gatherCapturedContext(
fn: NodePath<
| t.FunctionExpression
@@ -4175,8 +4168,8 @@ function gatherCapturedContext(
| t.ObjectMethod
>,
componentScope: Scope,
): Map<t.Identifier, SourceLocation> {
const capturedIds = new Map<t.Identifier, SourceLocation>();
): Array<t.Identifier> {
const capturedIds = new Set<t.Identifier>();
/*
* Capture all the scopes from the parent of this function up to and including
@@ -4219,15 +4212,8 @@ function gatherCapturedContext(
// Add the base identifier binding as a dependency.
const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name);
if (
binding !== undefined &&
pureScopes.has(binding.scope) &&
!capturedIds.has(binding.identifier)
) {
capturedIds.set(
binding.identifier,
path.node.loc ?? binding.identifier.loc ?? GeneratedSource,
);
if (binding !== undefined && pureScopes.has(binding.scope)) {
capturedIds.add(binding.identifier);
}
}
@@ -4264,7 +4250,7 @@ function gatherCapturedContext(
},
});
return capturedIds;
return [...capturedIds.keys()];
}
function notNull<T>(value: T | null): value is T {

View File

@@ -246,7 +246,7 @@ export const EnvironmentConfigSchema = z.object({
/**
* Enable a new model for mutability and aliasing inference
*/
enableNewMutationAliasingModel: z.boolean().default(true),
enableNewMutationAliasingModel: z.boolean().default(false),
/**
* Enables inference of optional dependency chains. Without this flag

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {Effect, ValueKind, ValueReason} from './HIR';
import {Effect, makeIdentifierId, ValueKind, ValueReason} from './HIR';
import {
BUILTIN_SHAPES,
BuiltInArrayId,
@@ -34,6 +34,7 @@ import {
addFunction,
addHook,
addObject,
signatureArgument,
} from './ObjectShape';
import {BuiltInType, ObjectType, PolyType} from './Types';
import {TypeConfig} from './TypeSchema';
@@ -645,35 +646,35 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
hookKind: 'useEffect',
returnValueKind: ValueKind.Frozen,
aliasing: {
receiver: '@receiver',
receiver: makeIdentifierId(0),
params: [],
rest: '@rest',
returns: '@returns',
temporaries: ['@effect'],
rest: makeIdentifierId(1),
returns: makeIdentifierId(2),
temporaries: [signatureArgument(3)],
effects: [
// Freezes the function and deps
{
kind: 'Freeze',
value: '@rest',
value: signatureArgument(1),
reason: ValueReason.Effect,
},
// Internally creates an effect object that captures the function and deps
{
kind: 'Create',
into: '@effect',
into: signatureArgument(3),
value: ValueKind.Frozen,
reason: ValueReason.KnownReturnSignature,
},
// The effect stores the function and dependencies
{
kind: 'Capture',
from: '@rest',
into: '@effect',
from: signatureArgument(1),
into: signatureArgument(3),
},
// Returns undefined
{
kind: 'Create',
into: '@returns',
into: signatureArgument(2),
value: ValueKind.Primitive,
reason: ValueReason.KnownReturnSignature,
},
@@ -905,7 +906,6 @@ export function installTypeConfig(
noAlias: typeConfig.noAlias === true,
mutableOnlyIfOperandsAreMutable:
typeConfig.mutableOnlyIfOperandsAreMutable === true,
aliasing: typeConfig.aliasing,
});
}
case 'hook': {
@@ -923,7 +923,6 @@ export function installTypeConfig(
),
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
noAlias: typeConfig.noAlias === true,
aliasing: typeConfig.aliasing,
});
}
case 'object': {

View File

@@ -1388,16 +1388,6 @@ export enum ValueReason {
*/
JsxCaptured = 'jsx-captured',
/**
* Argument to a hook
*/
HookCaptured = 'hook-captured',
/**
* Return value of a hook
*/
HookReturn = 'hook-return',
/**
* Passed to an effect
*/
@@ -1453,20 +1443,6 @@ export const ValueKindSchema = z.enum([
ValueKind.Context,
]);
export const ValueReasonSchema = z.enum([
ValueReason.Context,
ValueReason.Effect,
ValueReason.Global,
ValueReason.HookCaptured,
ValueReason.HookReturn,
ValueReason.JsxCaptured,
ValueReason.KnownReturnSignature,
ValueReason.Other,
ValueReason.ReactiveFunctionArgument,
ValueReason.ReducerState,
ValueReason.State,
]);
// The effect with which a value is modified.
export enum Effect {
// Default value: not allowed after lifetime inference

View File

@@ -106,7 +106,7 @@ export default class HIRBuilder {
#current: WipBlock;
#entry: BlockId;
#scopes: Array<Scope> = [];
#context: Map<t.Identifier, SourceLocation>;
#context: Array<t.Identifier>;
#bindings: Bindings;
#env: Environment;
#exceptionHandlerStack: Array<BlockId> = [];
@@ -121,7 +121,7 @@ export default class HIRBuilder {
return this.#env.nextIdentifierId;
}
get context(): Map<t.Identifier, SourceLocation> {
get context(): Array<t.Identifier> {
return this.#context;
}
@@ -137,13 +137,13 @@ export default class HIRBuilder {
env: Environment,
options?: {
bindings?: Bindings | null;
context?: Map<t.Identifier, SourceLocation>;
context?: Array<t.Identifier>;
entryBlockKind?: BlockKind;
},
) {
this.#env = env;
this.#bindings = options?.bindings ?? new Map();
this.#context = options?.context ?? new Map();
this.#context = options?.context ?? [];
this.#entry = makeBlockId(env.nextBlockId);
this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
}

View File

@@ -6,18 +6,14 @@
*/
import {CompilerError} from '../CompilerError';
import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects';
import {assertExhaustive} from '../Utils/utils';
import {AliasingSignature} from '../Inference/AliasingEffects';
import {
Effect,
GeneratedSource,
Hole,
makeDeclarationId,
makeIdentifierId,
makeInstructionId,
Place,
SourceLocation,
SpreadPattern,
ValueKind,
ValueReason,
} from './HIR';
@@ -29,7 +25,6 @@ import {
PolyType,
PrimitiveType,
} from './Types';
import {AliasingEffectConfig, AliasingSignatureConfig} from './TypeSchema';
/*
* This file exports types and defaults for JavaScript object shapes. These are
@@ -58,20 +53,13 @@ function createAnonId(): string {
export function addFunction(
registry: ShapeRegistry,
properties: Iterable<[string, BuiltInType | PolyType]>,
fn: Omit<FunctionSignature, 'hookKind' | 'aliasing'> & {
aliasing?: AliasingSignatureConfig | null | undefined;
},
fn: Omit<FunctionSignature, 'hookKind'>,
id: string | null = null,
isConstructor: boolean = false,
): FunctionType {
const shapeId = id ?? createAnonId();
const aliasing =
fn.aliasing != null
? parseAliasingSignatureConfig(fn.aliasing, '<builtin>', GeneratedSource)
: null;
addShape(registry, shapeId, properties, {
...fn,
aliasing,
hookKind: null,
});
return {
@@ -89,18 +77,11 @@ export function addFunction(
*/
export function addHook(
registry: ShapeRegistry,
fn: Omit<FunctionSignature, 'aliasing'> & {
hookKind: HookKind;
aliasing?: AliasingSignatureConfig | null | undefined;
},
fn: FunctionSignature & {hookKind: HookKind},
id: string | null = null,
): FunctionType {
const shapeId = id ?? createAnonId();
const aliasing =
fn.aliasing != null
? parseAliasingSignatureConfig(fn.aliasing, '<builtin>', GeneratedSource)
: null;
addShape(registry, shapeId, [], {...fn, aliasing});
addShape(registry, shapeId, [], fn);
return {
kind: 'Function',
return: fn.returnType,
@@ -109,129 +90,6 @@ export function addHook(
};
}
function parseAliasingSignatureConfig(
typeConfig: AliasingSignatureConfig,
moduleName: string,
loc: SourceLocation,
): AliasingSignature {
const lifetimes = new Map<string, Place>();
function define(temp: string): Place {
CompilerError.invariant(!lifetimes.has(temp), {
reason: `Invalid type configuration for module`,
description: `Expected aliasing signature to have unique names for receiver, params, rest, returns, and temporaries in module '${moduleName}'`,
loc,
});
const place = signatureArgument(lifetimes.size);
lifetimes.set(temp, place);
return place;
}
function lookup(temp: string): Place {
const place = lifetimes.get(temp);
CompilerError.invariant(place != null, {
reason: `Invalid type configuration for module`,
description: `Expected aliasing signature effects to reference known names from receiver/params/rest/returns/temporaries, but '${temp}' is not a known name in '${moduleName}'`,
loc,
});
return place;
}
const receiver = define(typeConfig.receiver);
const params = typeConfig.params.map(define);
const rest = typeConfig.rest != null ? define(typeConfig.rest) : null;
const returns = define(typeConfig.returns);
const temporaries = typeConfig.temporaries.map(define);
const effects = typeConfig.effects.map(
(effect: AliasingEffectConfig): AliasingEffect => {
switch (effect.kind) {
case 'CreateFrom':
case 'Capture':
case 'Alias':
case 'Assign': {
const from = lookup(effect.from);
const into = lookup(effect.into);
return {
kind: effect.kind,
from,
into,
};
}
case 'Mutate':
case 'MutateTransitiveConditionally': {
const value = lookup(effect.value);
return {kind: effect.kind, value};
}
case 'Create': {
const into = lookup(effect.into);
return {
kind: 'Create',
into,
reason: effect.reason,
value: effect.value,
};
}
case 'Freeze': {
const value = lookup(effect.value);
return {
kind: 'Freeze',
value,
reason: effect.reason,
};
}
case 'Impure': {
const place = lookup(effect.place);
return {
kind: 'Impure',
place,
error: CompilerError.throwTodo({
reason: 'Support impure effect declarations',
loc: GeneratedSource,
}),
};
}
case 'Apply': {
const receiver = lookup(effect.receiver);
const fn = lookup(effect.function);
const args: Array<Place | SpreadPattern | Hole> = effect.args.map(
arg => {
if (typeof arg === 'string') {
return lookup(arg);
} else if (arg.kind === 'Spread') {
return {kind: 'Spread', place: lookup(arg.place)};
} else {
return arg;
}
},
);
const into = lookup(effect.into);
return {
kind: 'Apply',
receiver,
function: fn,
mutatesFunction: effect.mutatesFunction,
args,
into,
loc,
signature: null,
};
}
default: {
assertExhaustive(
effect,
`Unexpected effect kind '${(effect as any).kind}'`,
);
}
}
},
);
return {
receiver: receiver.identifier.id,
params: params.map(p => p.identifier.id),
rest: rest != null ? rest.identifier.id : null,
returns: returns.identifier.id,
temporaries,
effects,
};
}
/*
* Add an object to an existing ShapeRegistry.
*
@@ -334,7 +192,8 @@ export type FunctionSignature = {
canonicalName?: string;
aliasing?: AliasingSignature | null | undefined;
aliasing?: AliasingSignature | null;
todo_aliasing?: AliasingSignature | null;
};
/*
@@ -461,24 +320,24 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
aliasing: {
receiver: '@receiver',
receiver: makeIdentifierId(0),
params: [],
rest: '@rest',
returns: '@returns',
rest: makeIdentifierId(1),
returns: makeIdentifierId(2),
temporaries: [],
effects: [
// Push directly mutates the array itself
{kind: 'Mutate', value: '@receiver'},
{kind: 'Mutate', value: signatureArgument(0)},
// The arguments are captured into the array
{
kind: 'Capture',
from: '@rest',
into: '@receiver',
from: signatureArgument(1),
into: signatureArgument(0),
},
// Returns the new length, a primitive
{
kind: 'Create',
into: '@returns',
into: signatureArgument(2),
value: ValueKind.Primitive,
reason: ValueReason.KnownReturnSignature,
},
@@ -515,56 +374,58 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
aliasing: {
receiver: '@receiver',
params: ['@callback'],
receiver: makeIdentifierId(0),
params: [makeIdentifierId(1)],
rest: null,
returns: '@returns',
returns: makeIdentifierId(2),
temporaries: [
// Temporary representing captured items of the receiver
'@item',
signatureArgument(3),
// Temporary representing the result of the callback
'@callbackReturn',
signatureArgument(4),
/*
* Undefined `this` arg to the callback. Note the signature does not
* support passing an explicit thisArg second param
*/
'@thisArg',
signatureArgument(5),
],
effects: [
// Map creates a new mutable array
{
kind: 'Create',
into: '@returns',
into: signatureArgument(2),
value: ValueKind.Mutable,
reason: ValueReason.KnownReturnSignature,
},
// The first arg to the callback is an item extracted from the receiver array
{
kind: 'CreateFrom',
from: '@receiver',
into: '@item',
from: signatureArgument(0),
into: signatureArgument(3),
},
// The undefined this for the callback
{
kind: 'Create',
into: '@thisArg',
into: signatureArgument(5),
value: ValueKind.Primitive,
reason: ValueReason.KnownReturnSignature,
},
// calls the callback, returning the result into a temporary
{
kind: 'Apply',
receiver: '@thisArg',
args: ['@item', {kind: 'Hole'}, '@receiver'],
function: '@callback',
into: '@callbackReturn',
receiver: signatureArgument(5),
args: [signatureArgument(3), {kind: 'Hole'}, signatureArgument(0)],
function: signatureArgument(1),
into: signatureArgument(4),
signature: null,
mutatesFunction: false,
loc: GeneratedSource,
},
// captures the result of the callback into the return array
{
kind: 'Capture',
from: '@callbackReturn',
into: '@returns',
from: signatureArgument(4),
into: signatureArgument(2),
},
],
},
@@ -716,28 +577,28 @@ addObject(BUILTIN_SHAPES, BuiltInSetId, [
// returnValueKind is technically dependent on the ValueKind of the set itself
returnValueKind: ValueKind.Mutable,
aliasing: {
receiver: '@receiver',
receiver: makeIdentifierId(0),
params: [],
rest: '@rest',
returns: '@returns',
rest: makeIdentifierId(1),
returns: makeIdentifierId(2),
temporaries: [],
effects: [
// Set.add returns the receiver Set
{
kind: 'Assign',
from: '@receiver',
into: '@returns',
from: signatureArgument(0),
into: signatureArgument(2),
},
// Set.add mutates the set itself
{
kind: 'Mutate',
value: '@receiver',
value: signatureArgument(0),
},
// Captures the rest params into the set
{
kind: 'Capture',
from: '@rest',
into: '@receiver',
from: signatureArgument(1),
into: signatureArgument(0),
},
],
},
@@ -1441,34 +1302,6 @@ export const DefaultNonmutatingHook = addHook(
calleeEffect: Effect.Read,
hookKind: 'Custom',
returnValueKind: ValueKind.Frozen,
aliasing: {
receiver: '@receiver',
params: [],
rest: '@rest',
returns: '@returns',
temporaries: [],
effects: [
// Freeze the arguments
{
kind: 'Freeze',
value: '@rest',
reason: ValueReason.HookCaptured,
},
// Returns a frozen value
{
kind: 'Create',
into: '@returns',
value: ValueKind.Frozen,
reason: ValueReason.HookReturn,
},
// May alias any arguments into the return
{
kind: 'Alias',
from: '@rest',
into: '@returns',
},
],
},
},
'DefaultNonmutatingHook',
);

View File

@@ -8,12 +8,7 @@
import {isValidIdentifier} from '@babel/types';
import {z} from 'zod';
import {Effect, ValueKind} from '..';
import {
EffectSchema,
ValueKindSchema,
ValueReason,
ValueReasonSchema,
} from './HIR';
import {EffectSchema, ValueKindSchema} from './HIR';
export type ObjectPropertiesConfig = {[key: string]: TypeConfig};
export const ObjectPropertiesSchema: z.ZodType<ObjectPropertiesConfig> = z
@@ -36,194 +31,6 @@ export const ObjectTypeSchema: z.ZodType<ObjectTypeConfig> = z.object({
properties: ObjectPropertiesSchema.nullable(),
});
export const LifetimeIdSchema = z.string().refine(id => id.startsWith('@'), {
message: "Placeholder names must start with '@'",
});
export type FreezeEffectConfig = {
kind: 'Freeze';
value: string;
reason: ValueReason;
};
export const FreezeEffectSchema: z.ZodType<FreezeEffectConfig> = z.object({
kind: z.literal('Freeze'),
value: LifetimeIdSchema,
reason: ValueReasonSchema,
});
export type MutateEffectConfig = {
kind: 'Mutate';
value: string;
};
export const MutateEffectSchema: z.ZodType<MutateEffectConfig> = z.object({
kind: z.literal('Mutate'),
value: LifetimeIdSchema,
});
export type MutateTransitiveConditionallyConfig = {
kind: 'MutateTransitiveConditionally';
value: string;
};
export const MutateTransitiveConditionallySchema: z.ZodType<MutateTransitiveConditionallyConfig> =
z.object({
kind: z.literal('MutateTransitiveConditionally'),
value: LifetimeIdSchema,
});
export type CreateEffectConfig = {
kind: 'Create';
into: string;
value: ValueKind;
reason: ValueReason;
};
export const CreateEffectSchema: z.ZodType<CreateEffectConfig> = z.object({
kind: z.literal('Create'),
into: LifetimeIdSchema,
value: ValueKindSchema,
reason: ValueReasonSchema,
});
export type AssignEffectConfig = {
kind: 'Assign';
from: string;
into: string;
};
export const AssignEffectSchema: z.ZodType<AssignEffectConfig> = z.object({
kind: z.literal('Assign'),
from: LifetimeIdSchema,
into: LifetimeIdSchema,
});
export type AliasEffectConfig = {
kind: 'Alias';
from: string;
into: string;
};
export const AliasEffectSchema: z.ZodType<AliasEffectConfig> = z.object({
kind: z.literal('Alias'),
from: LifetimeIdSchema,
into: LifetimeIdSchema,
});
export type CaptureEffectConfig = {
kind: 'Capture';
from: string;
into: string;
};
export const CaptureEffectSchema: z.ZodType<CaptureEffectConfig> = z.object({
kind: z.literal('Capture'),
from: LifetimeIdSchema,
into: LifetimeIdSchema,
});
export type CreateFromEffectConfig = {
kind: 'CreateFrom';
from: string;
into: string;
};
export const CreateFromEffectSchema: z.ZodType<CreateFromEffectConfig> =
z.object({
kind: z.literal('CreateFrom'),
from: LifetimeIdSchema,
into: LifetimeIdSchema,
});
export type ApplyArgConfig =
| string
| {kind: 'Spread'; place: string}
| {kind: 'Hole'};
export const ApplyArgSchema: z.ZodType<ApplyArgConfig> = z.union([
LifetimeIdSchema,
z.object({
kind: z.literal('Spread'),
place: LifetimeIdSchema,
}),
z.object({
kind: z.literal('Hole'),
}),
]);
export type ApplyEffectConfig = {
kind: 'Apply';
receiver: string;
function: string;
mutatesFunction: boolean;
args: Array<ApplyArgConfig>;
into: string;
};
export const ApplyEffectSchema: z.ZodType<ApplyEffectConfig> = z.object({
kind: z.literal('Apply'),
receiver: LifetimeIdSchema,
function: LifetimeIdSchema,
mutatesFunction: z.boolean(),
args: z.array(ApplyArgSchema),
into: LifetimeIdSchema,
});
export type ImpureEffectConfig = {
kind: 'Impure';
place: string;
};
export const ImpureEffectSchema: z.ZodType<ImpureEffectConfig> = z.object({
kind: z.literal('Impure'),
place: LifetimeIdSchema,
});
export type AliasingEffectConfig =
| FreezeEffectConfig
| CreateEffectConfig
| CreateFromEffectConfig
| AssignEffectConfig
| AliasEffectConfig
| CaptureEffectConfig
| ImpureEffectConfig
| MutateEffectConfig
| MutateTransitiveConditionallyConfig
| ApplyEffectConfig;
export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
FreezeEffectSchema,
CreateEffectSchema,
CreateFromEffectSchema,
AssignEffectSchema,
AliasEffectSchema,
CaptureEffectSchema,
ImpureEffectSchema,
MutateEffectSchema,
MutateTransitiveConditionallySchema,
ApplyEffectSchema,
]);
export type AliasingSignatureConfig = {
receiver: string;
params: Array<string>;
rest: string | null;
returns: string;
effects: Array<AliasingEffectConfig>;
temporaries: Array<string>;
};
export const AliasingSignatureSchema: z.ZodType<AliasingSignatureConfig> =
z.object({
receiver: LifetimeIdSchema,
params: z.array(LifetimeIdSchema),
rest: LifetimeIdSchema.nullable(),
returns: LifetimeIdSchema,
effects: z.array(AliasingEffectSchema),
temporaries: z.array(LifetimeIdSchema),
});
export type FunctionTypeConfig = {
kind: 'function';
positionalParams: Array<Effect>;
@@ -235,7 +42,6 @@ export type FunctionTypeConfig = {
mutableOnlyIfOperandsAreMutable?: boolean | null | undefined;
impure?: boolean | null | undefined;
canonicalName?: string | null | undefined;
aliasing?: AliasingSignatureConfig | null | undefined;
};
export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
kind: z.literal('function'),
@@ -248,7 +54,6 @@ export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
mutableOnlyIfOperandsAreMutable: z.boolean().nullable().optional(),
impure: z.boolean().nullable().optional(),
canonicalName: z.string().nullable().optional(),
aliasing: AliasingSignatureSchema.nullable().optional(),
});
export type HookTypeConfig = {
@@ -258,7 +63,6 @@ export type HookTypeConfig = {
returnType: TypeConfig;
returnValueKind?: ValueKind | null | undefined;
noAlias?: boolean | null | undefined;
aliasing?: AliasingSignatureConfig | null | undefined;
};
export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
kind: z.literal('hook'),
@@ -267,7 +71,6 @@ export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
returnType: z.lazy(() => TypeSchema),
returnValueKind: ValueKindSchema.nullable().optional(),
noAlias: z.boolean().nullable().optional(),
aliasing: AliasingSignatureSchema.nullable().optional(),
});
export type BuiltInTypeConfig =

View File

@@ -8,7 +8,6 @@
import {CompilerErrorDetailOptions} from '../CompilerError';
import {
FunctionExpression,
GeneratedSource,
Hole,
IdentifierId,
ObjectMethod,
@@ -19,7 +18,6 @@ import {
ValueReason,
} from '../HIR';
import {FunctionSignature} from '../HIR/ObjectShape';
import {printSourceLocation} from '../HIR/PrintHIR';
/**
* `AliasingEffect` describes a set of "effects" that an instruction/terminal has on one or
@@ -202,19 +200,10 @@ export function hashEffect(effect: AliasingEffect): string {
return [effect.kind, effect.value.identifier.id, effect.reason].join(':');
}
case 'Impure':
case 'Render': {
return [effect.kind, effect.place.identifier.id].join(':');
}
case 'Render':
case 'MutateFrozen':
case 'MutateGlobal': {
return [
effect.kind,
effect.place.identifier.id,
effect.error.severity,
effect.error.reason,
effect.error.description,
printSourceLocation(effect.error.loc ?? GeneratedSource),
].join(':');
return [effect.kind, effect.place.identifier.id].join(':');
}
case 'Mutate':
case 'MutateConditionally':

View File

@@ -42,16 +42,8 @@ export default function analyseFunctions(func: HIRFunction): void {
* Reset mutable range for outer inferReferenceEffects
*/
for (const operand of instr.value.loweredFunc.func.context) {
/**
* NOTE: inferReactiveScopeVariables makes identifiers in the scope
* point to the *same* mutableRange instance. Resetting start/end
* here is insufficient, because a later mutation of the range
* for any one identifier could affect the range for other identifiers.
*/
operand.identifier.mutableRange = {
start: makeInstructionId(0),
end: makeInstructionId(0),
};
operand.identifier.mutableRange.start = makeInstructionId(0);
operand.identifier.mutableRange.end = makeInstructionId(0);
operand.identifier.scope = null;
}
break;

View File

@@ -341,10 +341,6 @@ export function getWriteErrorReason(abstractValue: AbstractValue): string {
return "Mutating a value returned from 'useReducer()', which should not be mutated. Use the dispatch function to update instead";
} else if (abstractValue.reason.has(ValueReason.Effect)) {
return 'Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()';
} else if (abstractValue.reason.has(ValueReason.HookCaptured)) {
return 'Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook';
} else if (abstractValue.reason.has(ValueReason.HookReturn)) {
return 'Updating a value returned from a hook is not allowed. Consider moving the mutation into the hook where the value is constructed';
} else {
return 'This mutates a variable that React considers immutable';
}

View File

@@ -38,7 +38,6 @@ import {
import {
eachInstructionValueLValue,
eachInstructionValueOperand,
eachTerminalOperand,
eachTerminalSuccessor,
} from '../HIR/visitors';
import {Ok, Result} from '../Utils/Result';
@@ -50,14 +49,12 @@ import {
} from './InferReferenceEffects';
import {
assertExhaustive,
getOrInsertDefault,
getOrInsertWith,
Set_isSuperset,
} from '../Utils/utils';
import {
printAliasingEffect,
printAliasingSignature,
printFunction,
printIdentifier,
printInstruction,
printInstructionValue,
@@ -197,15 +194,12 @@ export function inferMutationAliasingEffects(
let count = 0;
while (queuedStates.size !== 0) {
count++;
if (count > 100) {
if (count > 1000) {
console.log(
'oops infinite loop',
fn.id,
typeof fn.loc !== 'symbol' ? fn.loc?.filename : null,
);
if (DEBUG) {
console.log(printFunction(fn));
}
throw new Error('infinite loop');
}
for (const [blockId, block] of fn.body.blocks) {
@@ -217,11 +211,6 @@ export function inferMutationAliasingEffects(
statesByBlock.set(blockId, incomingState);
const state = incomingState.clone();
if (DEBUG) {
console.log('*************');
console.log(`bb${block.id}`);
console.log('*************');
}
inferBlock(context, state, block);
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
@@ -232,19 +221,8 @@ export function inferMutationAliasingEffects(
return Ok(undefined);
}
function findHoistedContextDeclarations(
fn: HIRFunction,
): Map<DeclarationId, Place | null> {
const hoisted = new Map<DeclarationId, Place | null>();
function visit(place: Place): void {
if (
hoisted.has(place.identifier.declarationId) &&
hoisted.get(place.identifier.declarationId) == null
) {
// If this is the first load of the value, store the location
hoisted.set(place.identifier.declarationId, place);
}
}
function findHoistedContextDeclarations(fn: HIRFunction): Set<DeclarationId> {
const hoisted = new Set<DeclarationId>();
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
if (instr.value.kind === 'DeclareContext') {
@@ -254,17 +232,10 @@ function findHoistedContextDeclarations(
kind == InstructionKind.HoistedFunction ||
kind == InstructionKind.HoistedLet
) {
hoisted.set(instr.value.lvalue.place.identifier.declarationId, null);
}
} else {
for (const operand of eachInstructionValueOperand(instr.value)) {
visit(operand);
hoisted.add(instr.value.lvalue.place.identifier.declarationId);
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
visit(operand);
}
}
return hoisted;
}
@@ -274,40 +245,21 @@ class Context {
instructionSignatureCache: Map<Instruction, InstructionSignature> = new Map();
effectInstructionValueCache: Map<AliasingEffect, InstructionValue> =
new Map();
applySignatureCache: Map<
AliasingSignature,
Map<AliasingEffect, Array<AliasingEffect> | null>
> = new Map();
catchHandlers: Map<BlockId, Place> = new Map();
functionSignatureCache: Map<FunctionExpression, AliasingSignature> =
new Map();
isFuctionExpression: boolean;
fn: HIRFunction;
hoistedContextDeclarations: Map<DeclarationId, Place | null>;
hoistedContextDeclarations: Set<DeclarationId>;
constructor(
isFunctionExpression: boolean,
fn: HIRFunction,
hoistedContextDeclarations: Map<DeclarationId, Place | null>,
hoistedContextDeclarations: Set<DeclarationId>,
) {
this.isFuctionExpression = isFunctionExpression;
this.fn = fn;
this.hoistedContextDeclarations = hoistedContextDeclarations;
}
cacheApplySignature(
signature: AliasingSignature,
effect: Extract<AliasingEffect, {kind: 'Apply'}>,
f: () => Array<AliasingEffect> | null,
): Array<AliasingEffect> | null {
const inner = getOrInsertDefault(
this.applySignatureCache,
signature,
new Map(),
);
return getOrInsertWith(inner, effect, f);
}
internEffect(effect: AliasingEffect): AliasingEffect {
const hash = hashEffect(effect);
let interned = this.internedEffects.get(hash);
@@ -381,13 +333,11 @@ function inferBlock(
state.appendAlias(handlerParam, instr.lvalue);
const kind = state.kind(instr.lvalue).kind;
if (kind === ValueKind.Mutable || kind == ValueKind.Context) {
effects.push(
context.internEffect({
kind: 'Alias',
from: instr.lvalue,
into: handlerParam,
}),
);
effects.push({
kind: 'Alias',
from: instr.lvalue,
into: handlerParam,
});
}
}
}
@@ -396,11 +346,11 @@ function inferBlock(
} else if (terminal.kind === 'return') {
if (!context.isFuctionExpression) {
terminal.effects = [
context.internEffect({
{
kind: 'Freeze',
value: terminal.value,
reason: ValueReason.JsxCaptured,
}),
},
];
}
}
@@ -577,21 +527,20 @@ function applyEffect(
break;
}
case ValueKind.Frozen: {
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
break;
}
default: {
effects.push(effect);
effects.push({
// OK: recording information flow
kind: 'CreateFrom', // prev Alias
from: effect.from,
into: effect.into,
});
}
}
break;
@@ -690,17 +639,11 @@ function applyEffect(
}
case ValueKind.Frozen: {
isMutableReferenceType = false;
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
break;
}
default: {
@@ -722,17 +665,11 @@ function applyEffect(
const fromKind = fromValue.kind;
switch (fromKind) {
case ValueKind.Frozen: {
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
@@ -790,33 +727,23 @@ function applyEffect(
* We're calling a locally declared function, we already know it's effects!
* We just have to substitute in the args for the params
*/
const functionExpr = functionValues[0];
let signature = context.functionSignatureCache.get(functionExpr);
if (signature == null) {
signature = buildSignatureFromFunctionExpression(
state.env,
functionExpr,
);
context.functionSignatureCache.set(functionExpr, signature);
}
const signature = buildSignatureFromFunctionExpression(
state.env,
functionValues[0],
);
if (DEBUG) {
console.log(
`constructed alias signature:\n${printAliasingSignature(signature)}`,
);
}
const signatureEffects = context.cacheApplySignature(
const signatureEffects = computeEffectsForSignature(
state.env,
signature,
effect,
() =>
computeEffectsForSignature(
state.env,
signature,
effect.into,
effect.receiver,
effect.args,
functionExpr.loweredFunc.func.context,
effect.loc,
),
effect.into,
effect.receiver,
effect.args,
functionValues[0].loweredFunc.func.context,
effect.loc,
);
if (signatureEffects != null) {
if (DEBUG) {
@@ -835,24 +762,18 @@ function applyEffect(
break;
}
}
let signatureEffects = null;
if (effect.signature?.aliasing != null) {
const signature = effect.signature.aliasing;
signatureEffects = context.cacheApplySignature(
effect.signature.aliasing,
effect,
() =>
computeEffectsForSignature(
const signatureEffects =
effect.signature?.aliasing != null
? computeEffectsForSignature(
state.env,
signature,
effect.signature.aliasing,
effect.into,
effect.receiver,
effect.args,
[],
effect.loc,
),
);
}
)
: null;
if (signatureEffects != null) {
if (DEBUG) {
console.log('apply aliasing signature effects');
@@ -980,89 +901,27 @@ function applyEffect(
console.log(prettyFormat(state.debugAbstractValue(value)));
}
if (
mutationKind === 'mutate-frozen' &&
context.hoistedContextDeclarations.has(
effect.value.identifier.declarationId,
)
) {
const description =
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Variable \`${effect.value.identifier.name.value}\` is accessed before it is declared`
: null;
const hoistedAccess = context.hoistedContextDeclarations.get(
effect.value.identifier.declarationId,
);
if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
applyEffect(
context,
state,
{
kind: 'MutateFrozen',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason: `This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time`,
description,
loc: hoistedAccess.loc,
suggestions: null,
},
},
aliased,
effects,
);
}
applyEffect(
context,
state,
{
kind: 'MutateFrozen',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason: `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`,
description,
loc: effect.value.loc,
suggestions: null,
},
},
aliased,
effects,
);
} else {
const reason = getWriteErrorReason({
kind: value.kind,
reason: value.reason,
context: new Set(),
});
const description =
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Found mutation of \`${effect.value.identifier.name.value}\``
: null;
applyEffect(
context,
state,
{
kind:
value.kind === ValueKind.Frozen
? 'MutateFrozen'
: 'MutateGlobal',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason,
description,
loc: effect.value.loc,
suggestions: null,
},
},
aliased,
effects,
);
}
const reason = getWriteErrorReason({
kind: value.kind,
reason: value.reason,
context: new Set(),
});
effects.push({
kind:
value.kind === ValueKind.Frozen ? 'MutateFrozen' : 'MutateGlobal',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason,
description:
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Found mutation of \`${effect.value.identifier.name.value}\``
: null,
loc: effect.value.loc,
suggestions: null,
},
});
}
break;
}
@@ -2100,17 +1959,28 @@ function computeEffectsForLegacySignature(
break;
}
case Effect.ConditionallyMutateIterator: {
const mutateIterator = conditionallyMutateIterator(place);
if (mutateIterator != null) {
effects.push(mutateIterator);
// TODO: should we always push to captures?
if (
isArrayType(place.identifier) ||
isSetType(place.identifier) ||
isMapType(place.identifier)
) {
effects.push({
kind: 'Capture',
from: place,
into: lvalue,
});
} else {
effects.push({
kind: 'Capture',
from: place,
into: lvalue,
});
captures.push(place);
effects.push({
kind: 'MutateTransitiveConditionally',
value: place,
});
}
effects.push({
kind: 'Capture',
from: place,
into: lvalue,
});
break;
}
case Effect.Freeze: {
@@ -2300,7 +2170,6 @@ function computeEffectsForSignature(
return null;
}
// Build substitutions
const mutableSpreads = new Set<IdentifierId>();
const substitutions: Map<IdentifierId, Array<Place>> = new Map();
substitutions.set(signature.receiver, [receiver]);
substitutions.set(signature.returns, [lvalue]);
@@ -2318,13 +2187,6 @@ function computeEffectsForSignature(
}
const place = arg.kind === 'Identifier' ? arg : arg.place;
getOrInsertWith(substitutions, signature.rest, () => []).push(place);
if (arg.kind === 'Spread') {
const mutateIterator = conditionallyMutateIterator(arg.place);
if (mutateIterator != null) {
mutableSpreads.add(arg.place.identifier.id);
}
}
} else {
const param = params[i];
substitutions.set(param, [arg]);
@@ -2396,12 +2258,6 @@ function computeEffectsForSignature(
case 'Freeze': {
const values = substitutions.get(effect.value.identifier.id) ?? [];
for (const value of values) {
if (mutableSpreads.has(value.identifier.id)) {
CompilerError.throwTodo({
reason: 'Support spread syntax for hook arguments',
loc: value.loc,
});
}
effects.push({kind: 'Freeze', value, reason: effect.reason});
}
break;

View File

@@ -175,14 +175,21 @@ import {
* and mutability.
*/
function Component(t0) {
const $ = _c(2);
const $ = _c(4);
const { prop } = t0;
let t1;
if ($[0] !== prop) {
const obj = shallowCopy(prop);
const aliasedObj = identity(obj);
const id = [obj.id];
let t2;
if ($[2] !== obj) {
t2 = [obj.id];
$[2] = obj;
$[3] = t2;
} else {
t2 = $[3];
}
const id = t2;
mutate(aliasedObj);
setPropertyByKey(aliasedObj, "id", prop.id + 1);

View File

@@ -25,25 +25,17 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a) {
const $ = _c(4);
let t0;
if ($[0] !== a) {
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
const $ = _c(2);
let y;
if ($[2] !== x[0][1]) {
if ($[0] !== a) {
const x = [a];
y = {};
y = x[0][1];
$[2] = x[0][1];
$[3] = y;
$[0] = a;
$[1] = y;
} else {
y = $[3];
y = $[1];
}
return y;
}

View File

@@ -29,29 +29,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a, b) {
const $ = _c(6);
let t0;
if ($[0] !== a || $[1] !== b) {
t0 = [a, b];
$[0] = a;
$[1] = b;
$[2] = t0;
} else {
t0 = $[2];
}
const x = t0;
const $ = _c(3);
let y;
if ($[3] !== x[0][1] || $[4] !== x[1][0]) {
if ($[0] !== a || $[1] !== b) {
const x = [a, b];
y = {};
let t = {};
y = x[0][1];
t = x[1][0];
$[3] = x[0][1];
$[4] = x[1][0];
$[5] = y;
$[0] = a;
$[1] = b;
$[2] = y;
} else {
y = $[5];
y = $[2];
}
return y;
}

View File

@@ -25,25 +25,17 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a) {
const $ = _c(4);
let t0;
if ($[0] !== a) {
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
const $ = _c(2);
let y;
if ($[2] !== x[0].a[1]) {
if ($[0] !== a) {
const x = [a];
y = {};
y = x[0].a[1];
$[2] = x[0].a[1];
$[3] = y;
$[0] = a;
$[1] = y;
} else {
y = $[3];
y = $[1];
}
return y;
}

View File

@@ -24,25 +24,17 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a) {
const $ = _c(4);
let t0;
if ($[0] !== a) {
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
const $ = _c(2);
let y;
if ($[2] !== x[0]) {
if ($[0] !== a) {
const x = [a];
y = {};
y = x[0];
$[2] = x[0];
$[3] = y;
$[0] = a;
$[1] = y;
} else {
y = $[3];
y = $[1];
}
return y;
}

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
11 | });
12 |
> 13 | x.value += count;
| ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (13:13)
| ^ InvalidReact: This mutates a variable that React considers immutable (13:13)
14 | return <Stringify x={x} cb={cb} />;
15 | }
16 |

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
11 | });
12 |
> 13 | x.value += count;
| ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (13:13)
| ^ InvalidReact: This mutates a variable that React considers immutable (13:13)
14 | return <Stringify x={x} cb={cb} />;
15 | }
16 |

View File

@@ -38,15 +38,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
17 | * $2 = Function context=setState
18 | */
> 19 | useEffect(() => setState(2), []);
| ^^^^^^^^ InvalidReact: This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time. Variable `setState` is accessed before it is declared (19:19)
InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Variable `setState` is accessed before it is declared (21:21)
19 | useEffect(() => setState(2), []);
20 |
21 | const [state, setState] = useState(0);
> 21 | const [state, setState] = useState(0);
| ^^^^^^^^ InvalidReact: Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect(). Found mutation of `setState` (21:21)
22 | return <Stringify state={state} />;
23 | }
24 |
```

View File

@@ -20,7 +20,7 @@ function Component() {
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^^^ InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Date.now` is an impure function whose results may change on every call (4:4)
| ^^^^^^^^ InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Date.now` is an impure function whose results may change on every call (4:4)
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `performance.now` is an impure function whose results may change on every call (5:5)

View File

@@ -27,7 +27,7 @@ function SomeComponent() {
9 | return (
10 | <Button
> 11 | onPress={() => (sharedVal.value = Math.random())}
| ^^^^^^^^^ InvalidReact: Updating a value returned from a hook is not allowed. Consider moving the mutation into the hook where the value is constructed. Found mutation of `sharedVal` (11:11)
| ^^^^^^^^^ InvalidReact: Mutating a value returned from a function whose return value should not be mutated. Found mutation of `sharedVal` (11:11)
12 | title="Randomize"
13 | />
14 | );

View File

@@ -16,8 +16,6 @@ function useHook(a, b) {
1 | function useHook(a, b) {
> 2 | b.test = 1;
| ^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead (2:2)
InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead (3:3)
3 | a.test = 2;
4 | }
5 |

View File

@@ -21,8 +21,6 @@ function Component(props) {
4 | foo(() => {
> 5 | x.a = 10;
| ^ InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (5:5)
InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (6:6)
6 | x.a = 20;
7 | });
8 | }

View File

@@ -21,8 +21,6 @@ function Component() {
3 | // Cannot assign to globals
> 4 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4)
InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (5:5)
5 | moduleLocal = true;
6 | };
7 | foo();

View File

@@ -18,8 +18,6 @@ function Component() {
2 | // Cannot assign to globals
> 3 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4)
4 | moduleLocal = true;
5 | }
6 |

View File

@@ -22,7 +22,7 @@ function Component(props) {
7 | return hasErrors;
8 | }
> 9 | return hasErrors();
| ^^^^^^^^^ Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized. <unknown> hasErrors_0$15:TFunction (9:9)
| ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$15 (9:9)
10 | }
11 |
```

View File

@@ -34,13 +34,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
11 |
12 | function foo() {
> 13 | return bar();
| ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (13:13)
13 | return bar();
14 | }
15 | function bar() {
> 15 | function bar() {
| ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (15:15)
16 | return 42;
17 | }
18 |
```

View File

@@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}}}
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"}}}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"},"suggestions":null,"severity":"InvalidReact"}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"}}}
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"suggestions":null,"severity":"InvalidReact"}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -52,7 +52,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"}}}
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}

View File

@@ -34,28 +34,22 @@ import { print } from "shared-runtime";
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const $ = _c(4);
const $ = _c(2);
const [, setState1] = useRef("initial value");
const [, setState2] = useRef("initial value");
let setState;
if ($[0] !== props.foo) {
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
$[0] = props.foo;
$[1] = setState;
if (props.foo) {
setState = setState1;
} else {
setState = $[1];
setState = setState2;
}
let t0;
if ($[2] !== setState) {
if ($[0] !== setState) {
t0 = () => print(setState);
$[2] = setState;
$[3] = t0;
$[0] = setState;
$[1] = t0;
} else {
t0 = $[3];
t0 = $[1];
}
useEffect(t0, [setState]);
}

View File

@@ -1,215 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {
Stringify,
mutate,
identity,
shallowCopy,
setPropertyByKey,
} from 'shared-runtime';
/**
* This fixture is similar to `bug-aliased-capture-aliased-mutate` and
* `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on
* dependency extraction.
*
* NOTE: this fixture is currently valid, but will break with optimizations:
* - Scope and mutable-range based reordering may move the array creation
* *after* the `mutate(aliasedObj)` call. This is invalid if mutate
* reassigns inner properties.
* - RecycleInto or other deeper-equality optimizations may produce invalid
* output -- it may compare the array's contents / dependencies too early.
* - Runtime validation for immutable values will break if `mutate` does
* interior mutation of the value captured into the array.
*
* Before scope block creation, HIR looks like this:
* //
* // $1 is unscoped as obj's mutable range will be
* // extended in a later pass
* //
* $1 = LoadLocal obj@0[0:12]
* $2 = PropertyLoad $1.id
* //
* // $3 gets assigned a scope as Array is an allocating
* // instruction, but this does *not* get extended or
* // merged into the later mutation site.
* // (explained in `bug-aliased-capture-aliased-mutate`)
* //
* $3@1 = Array[$2]
* ...
* $10@0 = LoadLocal shallowCopy@0[0, 12]
* $11 = LoadGlobal mutate
* $12 = $11($10@0[0, 12])
*
* When filling in scope dependencies, we find that it's incorrect to depend on
* PropertyLoads from obj as it hasn't completed its mutable range. Following
* the immutable / mutable-new typing system, we check the identity of obj to
* detect whether it was newly created (and thus mutable) in this render pass.
*
* HIR with scopes looks like this.
* bb0:
* $1 = LoadLocal obj@0[0:12]
* $2 = PropertyLoad $1.id
* scopeTerminal deps=[obj@0] block=bb1 fallt=bb2
* bb1:
* $3@1 = Array[$2]
* goto bb2
* bb2:
* ...
*
* This is surprising as deps now is entirely decoupled from temporaries used
* by the block itself. scope @1's instructions now reference a value (1)
* produced outside its scope range and (2) not represented in its dependencies
*
* The right thing to do is to ensure that all Loads from a value get assigned
* the value's reactive scope. This also requires track mutating and aliasing
* separately from scope range. In this example, that would correctly merge
* the scopes of $3 with obj.
* Runtime validation and optimizations such as ReactiveGraph-based reordering
* require this as well.
*
* A tempting fix is to instead extend $3's ReactiveScope range up to include
* $2 (the PropertyLoad). This fixes dependency deduping but not reordering
* and mutability.
*/
function Component({prop}) {
let obj = shallowCopy(prop);
const aliasedObj = identity(obj);
// [obj.id] currently is assigned its own reactive scope
const id = [obj.id];
// Writing to the alias may reassign to previously captured references.
// The compiler currently produces valid output, but this breaks with
// reordering, recycleInto, and other potential optimizations.
mutate(aliasedObj);
setPropertyByKey(aliasedObj, 'id', prop.id + 1);
return <Stringify id={id} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prop: {id: 1}}],
sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import {
Stringify,
mutate,
identity,
shallowCopy,
setPropertyByKey,
} from "shared-runtime";
/**
* This fixture is similar to `bug-aliased-capture-aliased-mutate` and
* `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on
* dependency extraction.
*
* NOTE: this fixture is currently valid, but will break with optimizations:
* - Scope and mutable-range based reordering may move the array creation
* *after* the `mutate(aliasedObj)` call. This is invalid if mutate
* reassigns inner properties.
* - RecycleInto or other deeper-equality optimizations may produce invalid
* output -- it may compare the array's contents / dependencies too early.
* - Runtime validation for immutable values will break if `mutate` does
* interior mutation of the value captured into the array.
*
* Before scope block creation, HIR looks like this:
* //
* // $1 is unscoped as obj's mutable range will be
* // extended in a later pass
* //
* $1 = LoadLocal obj@0[0:12]
* $2 = PropertyLoad $1.id
* //
* // $3 gets assigned a scope as Array is an allocating
* // instruction, but this does *not* get extended or
* // merged into the later mutation site.
* // (explained in `bug-aliased-capture-aliased-mutate`)
* //
* $3@1 = Array[$2]
* ...
* $10@0 = LoadLocal shallowCopy@0[0, 12]
* $11 = LoadGlobal mutate
* $12 = $11($10@0[0, 12])
*
* When filling in scope dependencies, we find that it's incorrect to depend on
* PropertyLoads from obj as it hasn't completed its mutable range. Following
* the immutable / mutable-new typing system, we check the identity of obj to
* detect whether it was newly created (and thus mutable) in this render pass.
*
* HIR with scopes looks like this.
* bb0:
* $1 = LoadLocal obj@0[0:12]
* $2 = PropertyLoad $1.id
* scopeTerminal deps=[obj@0] block=bb1 fallt=bb2
* bb1:
* $3@1 = Array[$2]
* goto bb2
* bb2:
* ...
*
* This is surprising as deps now is entirely decoupled from temporaries used
* by the block itself. scope @1's instructions now reference a value (1)
* produced outside its scope range and (2) not represented in its dependencies
*
* The right thing to do is to ensure that all Loads from a value get assigned
* the value's reactive scope. This also requires track mutating and aliasing
* separately from scope range. In this example, that would correctly merge
* the scopes of $3 with obj.
* Runtime validation and optimizations such as ReactiveGraph-based reordering
* require this as well.
*
* A tempting fix is to instead extend $3's ReactiveScope range up to include
* $2 (the PropertyLoad). This fixes dependency deduping but not reordering
* and mutability.
*/
function Component(t0) {
const $ = _c(2);
const { prop } = t0;
let t1;
if ($[0] !== prop) {
const obj = shallowCopy(prop);
const aliasedObj = identity(obj);
const id = [obj.id];
mutate(aliasedObj);
setPropertyByKey(aliasedObj, "id", prop.id + 1);
t1 = <Stringify id={id} />;
$[0] = prop;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ prop: { id: 1 } }],
sequentialRenders: [
{ prop: { id: 1 } },
{ prop: { id: 1 } },
{ prop: { id: 2 } },
],
};
```
### Eval output
(kind: ok) <div>{"id":[1]}</div>
<div>{"id":[1]}</div>
<div>{"id":[2]}</div>

View File

@@ -1,94 +0,0 @@
// @enableNewMutationAliasingModel
import {
Stringify,
mutate,
identity,
shallowCopy,
setPropertyByKey,
} from 'shared-runtime';
/**
* This fixture is similar to `bug-aliased-capture-aliased-mutate` and
* `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on
* dependency extraction.
*
* NOTE: this fixture is currently valid, but will break with optimizations:
* - Scope and mutable-range based reordering may move the array creation
* *after* the `mutate(aliasedObj)` call. This is invalid if mutate
* reassigns inner properties.
* - RecycleInto or other deeper-equality optimizations may produce invalid
* output -- it may compare the array's contents / dependencies too early.
* - Runtime validation for immutable values will break if `mutate` does
* interior mutation of the value captured into the array.
*
* Before scope block creation, HIR looks like this:
* //
* // $1 is unscoped as obj's mutable range will be
* // extended in a later pass
* //
* $1 = LoadLocal obj@0[0:12]
* $2 = PropertyLoad $1.id
* //
* // $3 gets assigned a scope as Array is an allocating
* // instruction, but this does *not* get extended or
* // merged into the later mutation site.
* // (explained in `bug-aliased-capture-aliased-mutate`)
* //
* $3@1 = Array[$2]
* ...
* $10@0 = LoadLocal shallowCopy@0[0, 12]
* $11 = LoadGlobal mutate
* $12 = $11($10@0[0, 12])
*
* When filling in scope dependencies, we find that it's incorrect to depend on
* PropertyLoads from obj as it hasn't completed its mutable range. Following
* the immutable / mutable-new typing system, we check the identity of obj to
* detect whether it was newly created (and thus mutable) in this render pass.
*
* HIR with scopes looks like this.
* bb0:
* $1 = LoadLocal obj@0[0:12]
* $2 = PropertyLoad $1.id
* scopeTerminal deps=[obj@0] block=bb1 fallt=bb2
* bb1:
* $3@1 = Array[$2]
* goto bb2
* bb2:
* ...
*
* This is surprising as deps now is entirely decoupled from temporaries used
* by the block itself. scope @1's instructions now reference a value (1)
* produced outside its scope range and (2) not represented in its dependencies
*
* The right thing to do is to ensure that all Loads from a value get assigned
* the value's reactive scope. This also requires track mutating and aliasing
* separately from scope range. In this example, that would correctly merge
* the scopes of $3 with obj.
* Runtime validation and optimizations such as ReactiveGraph-based reordering
* require this as well.
*
* A tempting fix is to instead extend $3's ReactiveScope range up to include
* $2 (the PropertyLoad). This fixes dependency deduping but not reordering
* and mutability.
*/
function Component({prop}) {
let obj = shallowCopy(prop);
const aliasedObj = identity(obj);
// [obj.id] currently is assigned its own reactive scope
const id = [obj.id];
// Writing to the alias may reassign to previously captured references.
// The compiler currently produces valid output, but this breaks with
// reordering, recycleInto, and other potential optimizations.
mutate(aliasedObj);
setPropertyByKey(aliasedObj, 'id', prop.id + 1);
return <Stringify id={id} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prop: {id: 1}}],
sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}],
};

View File

@@ -1,134 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {Stringify} from 'shared-runtime';
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const getArrMap1 = () => arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const getArrMap2 = () => arr1.map(cb2);
return (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { Stringify } from "shared-runtime";
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo(t0) {
const $ = _c(13);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = () => arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const getArrMap1 = t2;
let t3;
if ($[5] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[5] = arr2;
$[6] = t3;
} else {
t3 = $[6];
}
const cb2 = t3;
let t4;
if ($[7] !== arr1 || $[8] !== cb2) {
t4 = () => arr1.map(cb2);
$[7] = arr1;
$[8] = cb2;
$[9] = t4;
} else {
t4 = $[9];
}
const getArrMap2 = t4;
let t5;
if ($[10] !== getArrMap1 || $[11] !== getArrMap2) {
t5 = (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
$[10] = getArrMap1;
$[11] = getArrMap2;
$[12] = t5;
} else {
t5 = $[12];
}
return t5;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) <div>{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}</div>
<div>{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}</div>
<div>{"getArrMap1":{"kind":"Function","result":[2,3]},"getArrMap2":{"kind":"Function","result":[0,1]},"shouldInvokeFns":true}</div>

View File

@@ -1,36 +0,0 @@
// @enableNewMutationAliasingModel
import {Stringify} from 'shared-runtime';
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const getArrMap1 = () => arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const getArrMap2 = () => arr1.map(cb2);
return (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,61 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
(function () {
y = x[0][1];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [['val1', 'val2']],
isComponent: false,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a) {
const $ = _c(4);
let t0;
if ($[0] !== a) {
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0][1]) {
y = {};
y = x[0][1];
$[2] = x[0][1];
$[3] = y;
} else {
y = $[3];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [["val1", "val2"]],
isComponent: false,
};
```
### Eval output
(kind: ok) "val2"

View File

@@ -1,16 +0,0 @@
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
(function () {
y = x[0][1];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [['val1', 'val2']],
isComponent: false,
};

View File

@@ -1,71 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a, b) {
let x = [a, b];
let y = {};
let t = {};
(function () {
y = x[0][1];
t = x[1][0];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [
[1, 2],
[2, 3],
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a, b) {
const $ = _c(6);
let t0;
if ($[0] !== a || $[1] !== b) {
t0 = [a, b];
$[0] = a;
$[1] = b;
$[2] = t0;
} else {
t0 = $[2];
}
const x = t0;
let y;
if ($[3] !== x[0][1] || $[4] !== x[1][0]) {
y = {};
let t = {};
y = x[0][1];
t = x[1][0];
$[3] = x[0][1];
$[4] = x[1][0];
$[5] = y;
} else {
y = $[5];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [
[1, 2],
[2, 3],
],
};
```
### Eval output
(kind: ok) 2

View File

@@ -1,20 +0,0 @@
// @enableNewMutationAliasingModel
function bar(a, b) {
let x = [a, b];
let y = {};
let t = {};
(function () {
y = x[0][1];
t = x[1][0];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [
[1, 2],
[2, 3],
],
};

View File

@@ -1,61 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
(function () {
y = x[0].a[1];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [{a: ['val1', 'val2']}],
isComponent: false,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a) {
const $ = _c(4);
let t0;
if ($[0] !== a) {
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0].a[1]) {
y = {};
y = x[0].a[1];
$[2] = x[0].a[1];
$[3] = y;
} else {
y = $[3];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [{ a: ["val1", "val2"] }],
isComponent: false,
};
```
### Eval output
(kind: ok) "val2"

View File

@@ -1,16 +0,0 @@
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
(function () {
y = x[0].a[1];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: [{a: ['val1', 'val2']}],
isComponent: false,
};

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
(function () {
y = x[0];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: ['TodoAdd'],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a) {
const $ = _c(4);
let t0;
if ($[0] !== a) {
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0]) {
y = {};
y = x[0];
$[2] = x[0];
$[3] = y;
} else {
y = $[3];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: ["TodoAdd"],
};
```
### Eval output
(kind: ok) "TodoAdd"

View File

@@ -1,15 +0,0 @@
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
(function () {
y = x[0];
})();
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: bar,
params: ['TodoAdd'],
};

View File

@@ -1,33 +0,0 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender @enableNewMutationAliasingModel
function Component() {
const date = Date.now();
const now = performance.now();
const rand = Math.random();
return <Foo date={date} now={now} rand={rand} />;
}
```
## Error
```
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^^^ InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Date.now` is an impure function whose results may change on every call (4:4)
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `performance.now` is an impure function whose results may change on every call (5:5)
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Math.random` is an impure function whose results may change on every call (6:6)
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
```

View File

@@ -1,8 +0,0 @@
// @validateNoImpureFunctionsInRender @enableNewMutationAliasingModel
function Component() {
const date = Date.now();
const now = performance.now();
const rand = Math.random();
return <Foo date={date} now={now} rand={rand} />;
}

View File

@@ -2,7 +2,6 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component() {
let local;
@@ -42,13 +41,13 @@ function Component() {
## Error
```
4 |
5 | const reassignLocal = newValue => {
> 6 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (6:6)
7 | };
8 |
9 | const onClick = newValue => {
3 |
4 | const reassignLocal = newValue => {
> 5 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (5:5)
6 | };
7 |
8 | const onClick = newValue => {
```

View File

@@ -1,4 +1,3 @@
// @enableNewMutationAliasingModel
function Component() {
let local;

View File

@@ -1,45 +0,0 @@
## Input
```javascript
//@flow @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {useIdentity} from 'shared-runtime';
function Component({content, refetch}) {
// This callback function accesses a hoisted const as a dependency,
// but it cannot reference it as a dependency since that would be a
// TDZ violation!
const onRefetch = useCallback(() => {
refetch(data);
}, [refetch]);
// The context variable gets frozen here since it's passed to a hook
const onSubmit = useIdentity(onRefetch);
// This has to error: onRefetch needs to memoize with `content` as a
// dependency, but the dependency comes later
const {data = null} = content;
return <Foo data={data} onSubmit={onSubmit} />;
}
```
## Error
```
9 | // TDZ violation!
10 | const onRefetch = useCallback(() => {
> 11 | refetch(data);
| ^^^^ InvalidReact: This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time. Variable `data` is accessed before it is declared (11:11)
InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Variable `data` is accessed before it is declared (19:19)
12 | }, [refetch]);
13 |
14 | // The context variable gets frozen here since it's passed to a hook
```

View File

@@ -1,22 +0,0 @@
//@flow @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {useIdentity} from 'shared-runtime';
function Component({content, refetch}) {
// This callback function accesses a hoisted const as a dependency,
// but it cannot reference it as a dependency since that would be a
// TDZ violation!
const onRefetch = useCallback(() => {
refetch(data);
}, [refetch]);
// The context variable gets frozen here since it's passed to a hook
const onSubmit = useIdentity(onRefetch);
// This has to error: onRefetch needs to memoize with `content` as a
// dependency, but the dependency comes later
const {data = null} = content;
return <Foo data={data} onSubmit={onSubmit} />;
}

View File

@@ -19,7 +19,7 @@ function Component({a, b}) {
3 | const x = {a};
4 | useFreeze(x);
> 5 | x.y = true;
| ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (5:5)
| ^ InvalidReact: This mutates a variable that React considers immutable (5:5)
6 | return <div>error</div>;
7 | }
8 |

View File

@@ -1,28 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function useHook(a, b) {
b.test = 1;
a.test = 2;
}
```
## Error
```
1 | // @enableNewMutationAliasingModel
2 | function useHook(a, b) {
> 3 | b.test = 1;
| ^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead (3:3)
InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead (4:4)
4 | a.test = 2;
5 | }
6 |
```

View File

@@ -1,5 +0,0 @@
// @enableNewMutationAliasingModel
function useHook(a, b) {
b.test = 1;
a.test = 2;
}

View File

@@ -1,32 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
let x = {a: 42};
function Component(props) {
foo(() => {
x.a = 10;
x.a = 20;
});
}
```
## Error
```
4 | function Component(props) {
5 | foo(() => {
> 6 | x.a = 10;
| ^ InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (6:6)
InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect (7:7)
7 | x.a = 20;
8 | });
9 | }
```

View File

@@ -1,9 +0,0 @@
// @enableNewMutationAliasingModel
let x = {a: 42};
function Component(props) {
foo(() => {
x.a = 10;
x.a = 20;
});
}

View File

@@ -1,32 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component() {
const foo = () => {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
};
foo();
}
```
## Error
```
3 | const foo = () => {
4 | // Cannot assign to globals
> 5 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (5:5)
InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (6:6)
6 | moduleLocal = true;
7 | };
8 | foo();
```

View File

@@ -1,9 +0,0 @@
// @enableNewMutationAliasingModel
function Component() {
const foo = () => {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
};
foo();
}

View File

@@ -1,29 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component() {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
}
```
## Error
```
2 | function Component() {
3 | // Cannot assign to globals
> 4 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4)
InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (5:5)
5 | moduleLocal = true;
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
// @enableNewMutationAliasingModel
function Component() {
// Cannot assign to globals
someUnknownGlobal = true;
moduleLocal = true;
}

View File

@@ -1,31 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component(props) {
function hasErrors() {
let hasErrors = false;
if (props.items == null) {
hasErrors = true;
}
return hasErrors;
}
return hasErrors();
}
```
## Error
```
8 | return hasErrors;
9 | }
> 10 | return hasErrors();
| ^^^^^^^^^ Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized. <unknown> hasErrors_0$15:TFunction (10:10)
11 | }
12 |
```

View File

@@ -1,11 +0,0 @@
// @enableNewMutationAliasingModel
function Component(props) {
function hasErrors() {
let hasErrors = false;
if (props.items == null) {
hasErrors = true;
}
return hasErrors;
}
return hasErrors();
}

View File

@@ -1,58 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0]?.value));
arr.push({value: foo});
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 1}],
};
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect } from "react";
import { print } from "shared-runtime";
function Component(t0) {
const { foo } = t0;
const arr = [];
useEffect(() => print(arr[0]?.value), [arr[0]?.value]);
arr.push({ value: foo });
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
};
```
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":171},"end":{"line":12,"column":1,"index":416},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":10,"column":2,"index":377},"end":{"line":10,"column":5,"index":380},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":336},"end":{"line":9,"column":39,"index":373},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":358},"end":{"line":9,"column":27,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":171},"end":{"line":12,"column":1,"index":416},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: ok) [{"value":1}]
logs: [1]

View File

@@ -1,17 +0,0 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0]?.value));
arr.push({value: foo});
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 1}],
};

View File

@@ -1,57 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
function Component({arrRef}) {
// Avoid taking arr.current as a dependency
useEffect(() => print(arrRef.current));
arrRef.current.val = 2;
return arrRef;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arrRef: {current: {val: 'initial ref value'}}}],
};
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect, useRef } from "react";
import { print } from "shared-runtime";
function Component(t0) {
const { arrRef } = t0;
useEffect(() => print(arrRef.current), [arrRef]);
arrRef.current.val = 2;
return arrRef;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arrRef: { current: { val: "initial ref value" } } }],
};
```
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":180},"end":{"line":11,"column":1,"index":343},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":301},"end":{"line":9,"column":16,"index":315},"filename":"mutate-after-useeffect-ref-access.ts"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":259},"end":{"line":8,"column":40,"index":297},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":281},"end":{"line":8,"column":30,"index":287},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":180},"end":{"line":11,"column":1,"index":343},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: ok) {"current":{"val":2}}
logs: [{ val: 2 }]

View File

@@ -1,16 +0,0 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
function Component({arrRef}) {
// Avoid taking arr.current as a dependency
useEffect(() => print(arrRef.current));
arrRef.current.val = 2;
return arrRef;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arrRef: {current: {val: 'initial ref value'}}}],
};

View File

@@ -1,56 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
function Component({foo}) {
const arr = [];
useEffect(() => {
arr.push(foo);
});
arr.push(2);
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 1}],
};
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect } from "react";
function Component(t0) {
const { foo } = t0;
const arr = [];
useEffect(() => {
arr.push(foo);
}, [arr, foo]);
arr.push(2);
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
};
```
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":133},"end":{"line":11,"column":1,"index":254},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":226},"end":{"line":9,"column":5,"index":229},"filename":"mutate-after-useeffect.ts","identifierName":"arr"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":181},"end":{"line":8,"column":4,"index":222},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":203},"end":{"line":7,"column":7,"index":206},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":203},"end":{"line":7,"column":7,"index":206},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":212},"end":{"line":7,"column":16,"index":215},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":133},"end":{"line":11,"column":1,"index":254},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: ok) [2]

View File

@@ -1,16 +0,0 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
function Component({foo}) {
const arr = [];
useEffect(() => {
arr.push(foo);
});
arr.push(2);
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 1}],
};

View File

@@ -1,54 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {identity, mutate} from 'shared-runtime';
function Component(props) {
const key = {};
const context = {
[key]: identity([props.value]),
};
mutate(key);
return context;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { identity, mutate } from "shared-runtime";
function Component(props) {
const $ = _c(2);
let context;
if ($[0] !== props.value) {
const key = {};
context = { [key]: identity([props.value]) };
mutate(key);
$[0] = props.value;
$[1] = context;
} else {
context = $[1];
}
return context;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
};
```
### Eval output
(kind: ok) {"[object Object]":[42]}

View File

@@ -1,16 +0,0 @@
// @enableNewMutationAliasingModel
import {identity, mutate} from 'shared-runtime';
function Component(props) {
const key = {};
const context = {
[key]: identity([props.value]),
};
mutate(key);
return context;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
};

View File

@@ -1,65 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {identity, mutate, mutateAndReturn} from 'shared-runtime';
function Component(props) {
const key = {a: 'key'};
const context = {
[key.a]: identity([props.value]),
};
mutate(key);
return context;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { identity, mutate, mutateAndReturn } from "shared-runtime";
function Component(props) {
const $ = _c(4);
let context;
if ($[0] !== props.value) {
const key = { a: "key" };
const t0 = key.a;
const t1 = identity([props.value]);
let t2;
if ($[2] !== t1) {
t2 = { [t0]: t1 };
$[2] = t1;
$[3] = t2;
} else {
t2 = $[3];
}
context = t2;
mutate(key);
$[0] = props.value;
$[1] = context;
} else {
context = $[1];
}
return context;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
};
```
### Eval output
(kind: ok) {"key":[42]}

View File

@@ -1,16 +0,0 @@
// @enableNewMutationAliasingModel
import {identity, mutate, mutateAndReturn} from 'shared-runtime';
function Component(props) {
const key = {a: 'key'};
const context = {
[key.a]: identity([props.value]),
};
mutate(key);
return context;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
};

View File

@@ -1,66 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @enableNewMutationAliasingModel
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
/*
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const [_state1, setState1] = useRef('initial value');
const [_state2, setState2] = useRef('initial value');
let setState;
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
useEffect(() => print(setState));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @enableNewMutationAliasingModel
import { useEffect, useState } from "react";
import { print } from "shared-runtime";
/*
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const $ = _c(4);
const [, setState1] = useRef("initial value");
const [, setState2] = useRef("initial value");
let setState;
if ($[0] !== props.foo) {
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
$[0] = props.foo;
$[1] = setState;
} else {
setState = $[1];
}
let t0;
if ($[2] !== setState) {
t0 = () => print(setState);
$[2] = setState;
$[3] = t0;
} else {
t0 = $[3];
}
useEffect(t0, [setState]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,18 +0,0 @@
// @inferEffectDependencies @enableNewMutationAliasingModel
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
/*
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const [_state1, setState1] = useRef('initial value');
const [_state2, setState2] = useRef('initial value');
let setState;
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
useEffect(() => print(setState));
}

View File

@@ -1,54 +0,0 @@
## Input
```javascript
// @flow @enableNewMutationAliasingModel
import fbt from 'fbt';
component Component() {
const sections = Object.keys(items);
for (let i = 0; i < sections.length; i += 3) {
chunks.push(
sections.slice(i, i + 3).map(section => {
return <Child />;
})
);
}
return <Child />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
function Component() {
const $ = _c(1);
const sections = Object.keys(items);
for (let i = 0; i < sections.length; i = i + 3, i) {
chunks.push(sections.slice(i, i + 3).map(_temp));
}
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Child />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp(section) {
return <Child />;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,17 +0,0 @@
// @flow @enableNewMutationAliasingModel
import fbt from 'fbt';
component Component() {
const sections = Object.keys(items);
for (let i = 0; i < sections.length; i += 3) {
chunks.push(
sections.slice(i, i + 3).map(section => {
return <Child />;
})
);
}
return <Child />;
}

View File

@@ -1,80 +0,0 @@
## Input
```javascript
//@flow @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel
component Component(
onAsyncSubmit?: (() => void) => void,
onClose: (isConfirmed: boolean) => void
) {
// When running inferReactiveScopeVariables,
// onAsyncSubmit and onClose update to share
// a mutableRange instance.
const onSubmit = useCallback(() => {
if (onAsyncSubmit) {
onAsyncSubmit(() => {
onClose(true);
});
return;
}
}, [onAsyncSubmit, onClose]);
// When running inferReactiveScopeVariables here,
// first the existing range gets updated (affecting
// onAsyncSubmit) and then onClose gets assigned a
// different mutable range instance, which is the
// one reset after AnalyzeFunctions.
// The fix is to fully reset mutable ranges *instances*
// after AnalyzeFunctions visit a function expression
return <Dialog onSubmit={onSubmit} onClose={() => onClose(false)} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(8);
const { onAsyncSubmit, onClose } = t0;
let t1;
if ($[0] !== onAsyncSubmit || $[1] !== onClose) {
t1 = () => {
if (onAsyncSubmit) {
onAsyncSubmit(() => {
onClose(true);
});
return;
}
};
$[0] = onAsyncSubmit;
$[1] = onClose;
$[2] = t1;
} else {
t1 = $[2];
}
const onSubmit = t1;
let t2;
if ($[3] !== onClose) {
t2 = () => onClose(false);
$[3] = onClose;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== onSubmit || $[6] !== t2) {
t3 = <Dialog onSubmit={onSubmit} onClose={t2} />;
$[5] = onSubmit;
$[6] = t2;
$[7] = t3;
} else {
t3 = $[7];
}
return t3;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,25 +0,0 @@
//@flow @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel
component Component(
onAsyncSubmit?: (() => void) => void,
onClose: (isConfirmed: boolean) => void
) {
// When running inferReactiveScopeVariables,
// onAsyncSubmit and onClose update to share
// a mutableRange instance.
const onSubmit = useCallback(() => {
if (onAsyncSubmit) {
onAsyncSubmit(() => {
onClose(true);
});
return;
}
}, [onAsyncSubmit, onClose]);
// When running inferReactiveScopeVariables here,
// first the existing range gets updated (affecting
// onAsyncSubmit) and then onClose gets assigned a
// different mutable range instance, which is the
// one reset after AnalyzeFunctions.
// The fix is to fully reset mutable ranges *instances*
// after AnalyzeFunctions visit a function expression
return <Dialog onSubmit={onSubmit} onClose={() => onClose(false)} />;
}

View File

@@ -1,53 +0,0 @@
## Input
```javascript
// @flow @enableNewMutationAliasingModel
import {identity, Stringify, useFragment} from 'shared-runtime';
component Example() {
const data = useFragment();
const {a, b} = identity(data);
const el = <Stringify tooltip={b} />;
identity(a.at(0));
return <Stringify icon={el} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity, Stringify, useFragment } from "shared-runtime";
function Example() {
const $ = _c(2);
const data = useFragment();
let t0;
if ($[0] !== data) {
const { a, b } = identity(data);
const el = <Stringify tooltip={b} />;
identity(a.at(0));
t0 = <Stringify icon={el} />;
$[0] = data;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,15 +0,0 @@
// @flow @enableNewMutationAliasingModel
import {identity, Stringify, useFragment} from 'shared-runtime';
component Example() {
const data = useFragment();
const {a, b} = identity(data);
const el = <Stringify tooltip={b} />;
identity(a.at(0));
return <Stringify icon={el} />;
}

View File

@@ -1,64 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
function Foo({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr));
const arr2 = [];
useEffectWrapper(() => arr2.push(propVal));
arr2.push(2);
return {arr, arr2};
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{propVal: 1}],
sequentialRenders: [{propVal: 1}, {propVal: 2}],
};
```
## Code
```javascript
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
function Foo({ propVal }) {
const arr = [propVal];
useEffectWrapper(() => print(arr));
const arr2 = [];
useEffectWrapper(() => arr2.push(propVal));
arr2.push(2);
return { arr, arr2 };
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ propVal: 1 }],
sequentialRenders: [{ propVal: 1 }, { propVal: 2 }],
};
```
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":195},"end":{"line":13,"column":1,"index":389},"filename":"retry-no-emit.ts"},"detail":{"reason":"Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":11,"column":2,"index":352},"end":{"line":11,"column":6,"index":356},"filename":"retry-no-emit.ts","identifierName":"arr2"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":248},"end":{"line":7,"column":36,"index":282},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":277},"end":{"line":7,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":306},"end":{"line":10,"column":44,"index":348},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":329},"end":{"line":10,"column":29,"index":333},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":329},"end":{"line":10,"column":29,"index":333},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":339},"end":{"line":10,"column":42,"index":346},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":195},"end":{"line":13,"column":1,"index":389},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: ok) {"arr":[1],"arr2":[2]}
{"arr":[2],"arr2":[2]}
logs: [[ 1 ],[ 2 ]]

View File

@@ -1,19 +0,0 @@
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
function Foo({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr));
const arr2 = [];
useEffectWrapper(() => arr2.push(propVal));
arr2.push(2);
return {arr, arr2};
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{propVal: 1}],
sequentialRenders: [{propVal: 1}, {propVal: 2}],
};

View File

@@ -1,80 +0,0 @@
## Input
```javascript
// @enableFire @enableNewMutationAliasingModel
import {fire} from 'react';
function Component({bar, baz}) {
const foo = () => {
console.log(bar);
};
useEffect(() => {
fire(foo(bar));
fire(baz(bar));
});
useEffect(() => {
fire(foo(bar));
});
return null;
}
```
## Code
```javascript
import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @enableNewMutationAliasingModel
import { fire } from "react";
function Component(t0) {
const $ = _c(9);
const { bar, baz } = t0;
let t1;
if ($[0] !== bar) {
t1 = () => {
console.log(bar);
};
$[0] = bar;
$[1] = t1;
} else {
t1 = $[1];
}
const foo = t1;
const t2 = useFire(foo);
const t3 = useFire(baz);
let t4;
if ($[2] !== bar || $[3] !== t2 || $[4] !== t3) {
t4 = () => {
t2(bar);
t3(bar);
};
$[2] = bar;
$[3] = t2;
$[4] = t3;
$[5] = t4;
} else {
t4 = $[5];
}
useEffect(t4);
let t5;
if ($[6] !== bar || $[7] !== t2) {
t5 = () => {
t2(bar);
};
$[6] = bar;
$[7] = t2;
$[8] = t5;
} else {
t5 = $[8];
}
useEffect(t5);
return null;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,18 +0,0 @@
// @enableFire @enableNewMutationAliasingModel
import {fire} from 'react';
function Component({bar, baz}) {
const foo = () => {
console.log(bar);
};
useEffect(() => {
fire(foo(bar));
fire(baz(bar));
});
useEffect(() => {
fire(foo(bar));
});
return null;
}

View File

@@ -1,117 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {
identity,
makeObject_Primitives,
typedIdentity,
useIdentity,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
// create a mutable value with input `a`
const x = makeObject_Primitives(a);
// freeze the value
useIdentity(x);
// known to pass-through via aliasing signature
const x2 = typedIdentity(x);
// Unknown function so we assume it conditionally mutates,
// but x2 is frozen so this downgrades to a read.
// x should *not* take b as a dependency
identity(x2, b);
return <ValidateMemoization inputs={[a]} output={x} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 1, b: 0},
{a: 1, b: 1},
{a: 0, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import {
identity,
makeObject_Primitives,
typedIdentity,
useIdentity,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(7);
const { a, b } = t0;
let t1;
if ($[0] !== a) {
t1 = makeObject_Primitives(a);
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
useIdentity(x);
const x2 = typedIdentity(x);
identity(x2, b);
let t2;
if ($[2] !== a) {
t2 = [a];
$[2] = a;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t2 || $[5] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} />;
$[4] = t2;
$[5] = x;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 1, b: 0 },
{ a: 1, b: 1 },
{ a: 0, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[1],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[1],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[0],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[0],"output":{"a":0,"b":"value1","c":true}}</div>

View File

@@ -1,39 +0,0 @@
// @enableNewMutationAliasingModel
import {
identity,
makeObject_Primitives,
typedIdentity,
useIdentity,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
// create a mutable value with input `a`
const x = makeObject_Primitives(a);
// freeze the value
useIdentity(x);
// known to pass-through via aliasing signature
const x2 = typedIdentity(x);
// Unknown function so we assume it conditionally mutates,
// but x2 is frozen so this downgrades to a read.
// x should *not* take b as a dependency
identity(x2, b);
return <ValidateMemoization inputs={[a]} output={x} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 1, b: 0},
{a: 1, b: 1},
{a: 0, b: 1},
{a: 0, b: 0},
],
};

View File

@@ -1,112 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {
identity,
makeObject_Primitives,
typedIdentity,
useIdentity,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
// create a mutable value with input `a`
const x = makeObject_Primitives(a);
// known to pass-through via aliasing signature
const x2 = typedIdentity(x);
// Unknown function so we assume it conditionally mutates,
// and x is still mutable so
identity(x2, b);
return <ValidateMemoization inputs={[a, b]} output={x} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 1, b: 0},
{a: 1, b: 1},
{a: 0, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import {
identity,
makeObject_Primitives,
typedIdentity,
useIdentity,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(9);
const { a, b } = t0;
let x;
if ($[0] !== a || $[1] !== b) {
x = makeObject_Primitives(a);
const x2 = typedIdentity(x);
identity(x2, b);
$[0] = a;
$[1] = b;
$[2] = x;
} else {
x = $[2];
}
let t1;
if ($[3] !== a || $[4] !== b) {
t1 = [a, b];
$[3] = a;
$[4] = b;
$[5] = t1;
} else {
t1 = $[5];
}
let t2;
if ($[6] !== t1 || $[7] !== x) {
t2 = <ValidateMemoization inputs={t1} output={x} />;
$[6] = t1;
$[7] = x;
$[8] = t2;
} else {
t2 = $[8];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 1, b: 0 },
{ a: 1, b: 1 },
{ a: 0, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[1,0],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[1,1],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[0,1],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[0,0],"output":{"a":0,"b":"value1","c":true}}</div>

View File

@@ -1,35 +0,0 @@
// @enableNewMutationAliasingModel
import {
identity,
makeObject_Primitives,
typedIdentity,
useIdentity,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
// create a mutable value with input `a`
const x = makeObject_Primitives(a);
// known to pass-through via aliasing signature
const x2 = typedIdentity(x);
// Unknown function so we assume it conditionally mutates,
// and x is still mutable so
identity(x2, b);
return <ValidateMemoization inputs={[a, b]} output={x} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 1, b: 0},
{a: 1, b: 1},
{a: 0, b: 1},
{a: 0, b: 0},
],
};

View File

@@ -1,102 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo({arr1, arr2, foo}) {
const x = [arr1];
let y = [];
const getVal1 = useCallback(() => {
return {x: 2};
}, []);
const getVal2 = useCallback(() => {
return [y];
}, [foo ? (y = x.concat(arr2)) : y]);
return <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{arr1: [1, 2], arr2: [3, 4], foo: true}],
sequentialRenders: [
{arr1: [1, 2], arr2: [3, 4], foo: true},
{arr1: [1, 2], arr2: [3, 4], foo: false},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(t0) {
const $ = _c(10);
const { arr1, arr2, foo } = t0;
let t1;
if ($[0] !== arr1) {
t1 = [arr1];
$[0] = arr1;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
let getVal1;
let t2;
if ($[2] !== arr2 || $[3] !== foo || $[4] !== x) {
let y = [];
getVal1 = _temp;
t2 = () => [y];
foo ? (y = x.concat(arr2)) : y;
$[2] = arr2;
$[3] = foo;
$[4] = x;
$[5] = getVal1;
$[6] = t2;
} else {
getVal1 = $[5];
t2 = $[6];
}
const getVal2 = t2;
let t3;
if ($[7] !== getVal1 || $[8] !== getVal2) {
t3 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
$[7] = getVal1;
$[8] = getVal2;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
}
function _temp() {
return { x: 2 };
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ arr1: [1, 2], arr2: [3, 4], foo: true }],
sequentialRenders: [
{ arr1: [1, 2], arr2: [3, 4], foo: true },
{ arr1: [1, 2], arr2: [3, 4], foo: false },
],
};
```
### Eval output
(kind: ok) <div>{"val1":{"kind":"Function","result":{"x":2}},"val2":{"kind":"Function","result":[[[1,2],3,4]]},"shouldInvokeFns":true}</div>
<div>{"val1":{"kind":"Function","result":{"x":2}},"val2":{"kind":"Function","result":[[]]},"shouldInvokeFns":true}</div>

View File

@@ -1,28 +0,0 @@
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo({arr1, arr2, foo}) {
const x = [arr1];
let y = [];
const getVal1 = useCallback(() => {
return {x: 2};
}, []);
const getVal2 = useCallback(() => {
return [y];
}, [foo ? (y = x.concat(arr2)) : y]);
return <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{arr1: [1, 2], arr2: [3, 4], foo: true}],
sequentialRenders: [
{arr1: [1, 2], arr2: [3, 4], foo: true},
{arr1: [1, 2], arr2: [3, 4], foo: false},
],
};

View File

@@ -1,85 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
// We currently produce invalid output (incorrect scoping for `y` declaration)
function useFoo(arr1, arr2) {
const x = [arr1];
let y;
const getVal = useCallback(() => {
return {y};
}, [((y = x.concat(arr2)), y)]);
return <Stringify getVal={getVal} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [
[1, 2],
[3, 4],
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { useCallback } from "react";
import { Stringify } from "shared-runtime";
// We currently produce invalid output (incorrect scoping for `y` declaration)
function useFoo(arr1, arr2) {
const $ = _c(7);
let t0;
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let t1;
if ($[2] !== arr2 || $[3] !== x) {
let y;
t1 = () => ({ y });
(y = x.concat(arr2)), y;
$[2] = arr2;
$[3] = x;
$[4] = t1;
} else {
t1 = $[4];
}
const getVal = t1;
let t2;
if ($[5] !== getVal) {
t2 = <Stringify getVal={getVal} shouldInvokeFns={true} />;
$[5] = getVal;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [
[1, 2],
[3, 4],
],
};
```
### Eval output
(kind: ok) <div>{"getVal":{"kind":"Function","result":{"y":[[1,2],3,4]}},"shouldInvokeFns":true}</div>

View File

@@ -1,23 +0,0 @@
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
// We currently produce invalid output (incorrect scoping for `y` declaration)
function useFoo(arr1, arr2) {
const x = [arr1];
let y;
const getVal = useCallback(() => {
return {y};
}, [((y = x.concat(arr2)), y)]);
return <Stringify getVal={getVal} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [
[1, 2],
[3, 4],
],
};

View File

@@ -1,77 +0,0 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {useMemo} from 'react';
function useFoo(arr1, arr2) {
const x = [arr1];
let y;
return useMemo(() => {
return {y};
}, [((y = x.concat(arr2)), y)]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [
[1, 2],
[3, 4],
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { useMemo } from "react";
function useFoo(arr1, arr2) {
const $ = _c(7);
let t0;
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== arr2 || $[3] !== x) {
(y = x.concat(arr2)), y;
$[2] = arr2;
$[3] = x;
$[4] = y;
} else {
y = $[4];
}
let t1;
let t2;
if ($[5] !== y) {
t2 = { y };
$[5] = y;
$[6] = t2;
} else {
t2 = $[6];
}
t1 = t2;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [
[1, 2],
[3, 4],
],
};
```
### Eval output
(kind: ok) {"y":[[1,2],3,4]}

View File

@@ -1,19 +0,0 @@
// @enableNewMutationAliasingModel
import {useMemo} from 'react';
function useFoo(arr1, arr2) {
const x = [arr1];
let y;
return useMemo(() => {
return {y};
}, [((y = x.concat(arr2)), y)]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [
[1, 2],
[3, 4],
],
};

View File

@@ -27,18 +27,34 @@ import { c as _c } from "react/compiler-runtime";
import { identity, mutate } from "shared-runtime";
function Component(props) {
const $ = _c(2);
let context;
if ($[0] !== props.value) {
const key = {};
context = { [key]: identity([props.value]) };
mutate(key);
$[0] = props.value;
$[1] = context;
const $ = _c(5);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
$[0] = t0;
} else {
context = $[1];
t0 = $[0];
}
const key = t0;
let t1;
if ($[1] !== props.value) {
t1 = identity([props.value]);
$[1] = props.value;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] !== t1) {
t2 = { [key]: t1 };
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
const context = t2;
mutate(key);
return context;
}

View File

@@ -27,22 +27,11 @@ import { c as _c } from "react/compiler-runtime";
import { identity, mutate, mutateAndReturn } from "shared-runtime";
function Component(props) {
const $ = _c(4);
const $ = _c(2);
let context;
if ($[0] !== props.value) {
const key = { a: "key" };
const t0 = key.a;
const t1 = identity([props.value]);
let t2;
if ($[2] !== t1) {
t2 = { [t0]: t1 };
$[2] = t1;
$[3] = t2;
} else {
t2 = $[3];
}
context = t2;
context = { [key.a]: identity([props.value]) };
mutate(key);
$[0] = props.value;

View File

@@ -40,46 +40,39 @@ import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(t0) {
const $ = _c(10);
const $ = _c(8);
const { arr1, arr2, foo } = t0;
let t1;
if ($[0] !== arr1) {
t1 = [arr1];
$[0] = arr1;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
let getVal1;
let t2;
if ($[2] !== arr2 || $[3] !== foo || $[4] !== x) {
let t1;
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
const x = [arr1];
let y = [];
getVal1 = _temp;
t2 = () => [y];
t1 = () => [y];
foo ? (y = x.concat(arr2)) : y;
$[2] = arr2;
$[3] = foo;
$[4] = x;
$[0] = arr1;
$[1] = arr2;
$[2] = foo;
$[3] = getVal1;
$[4] = t1;
} else {
getVal1 = $[3];
t1 = $[4];
}
const getVal2 = t1;
let t2;
if ($[5] !== getVal1 || $[6] !== getVal2) {
t2 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
$[5] = getVal1;
$[6] = t2;
$[6] = getVal2;
$[7] = t2;
} else {
getVal1 = $[5];
t2 = $[6];
t2 = $[7];
}
const getVal2 = t2;
let t3;
if ($[7] !== getVal1 || $[8] !== getVal2) {
t3 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
$[7] = getVal1;
$[8] = getVal2;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
return t2;
}
function _temp() {
return { x: 2 };

View File

@@ -36,38 +36,31 @@ import { Stringify } from "shared-runtime";
// We currently produce invalid output (incorrect scoping for `y` declaration)
function useFoo(arr1, arr2) {
const $ = _c(7);
const $ = _c(5);
let t0;
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let t1;
if ($[2] !== arr2 || $[3] !== x) {
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
let y;
t1 = () => ({ y });
t0 = () => ({ y });
(y = x.concat(arr2)), y;
$[2] = arr2;
$[3] = x;
$[0] = arr1;
$[1] = arr2;
$[2] = t0;
} else {
t0 = $[2];
}
const getVal = t0;
let t1;
if ($[3] !== getVal) {
t1 = <Stringify getVal={getVal} shouldInvokeFns={true} />;
$[3] = getVal;
$[4] = t1;
} else {
t1 = $[4];
}
const getVal = t1;
let t2;
if ($[5] !== getVal) {
t2 = <Stringify getVal={getVal} shouldInvokeFns={true} />;
$[5] = getVal;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
return t1;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -30,36 +30,29 @@ import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
function useFoo(arr1, arr2) {
const $ = _c(7);
let t0;
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
const $ = _c(5);
let y;
if ($[2] !== arr2 || $[3] !== x) {
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
(y = x.concat(arr2)), y;
$[2] = arr2;
$[3] = x;
$[4] = y;
$[0] = arr1;
$[1] = arr2;
$[2] = y;
} else {
y = $[4];
y = $[2];
}
let t0;
let t1;
let t2;
if ($[5] !== y) {
t2 = { y };
$[5] = y;
$[6] = t2;
if ($[3] !== y) {
t1 = { y };
$[3] = y;
$[4] = t1;
} else {
t2 = $[6];
t1 = $[4];
}
t1 = t2;
return t1;
t0 = t1;
return t0;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -69,22 +69,6 @@ export function makeSharedRuntimeTypeProvider({
returnValueKind: ValueKindEnum.Mutable,
noAlias: true,
},
typedIdentity: {
kind: 'function',
positionalParams: [EffectEnum.Read],
restParam: null,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKindEnum.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [{kind: 'Assign', from: '@value', into: '@return'}],
},
},
},
};
} else if (moduleName === 'ReactCompilerTest') {

View File

@@ -396,8 +396,4 @@ export function typedLog(...values: Array<any>): void {
console.log(...values);
}
export function typedIdentity<T>(value: T): T {
return value;
}
export default typedLog;