Compare commits

...

9 Commits

Author SHA1 Message Date
Joe Savona
6dc956a396 [compiler] Fix infinite loop due to uncached applied signatures
When we apply new aliasing signatures we can generate new temporaries, which causes the abstract memory model to not converge. The fix is to make sure we cache the applications of these signatures.
2025-06-18 15:30:52 -07:00
Joseph Savona
8f4ce72f0b [commit] Improve error for hoisting violations (#33514)
The previous error for hoisting violations pointed only to the variable
declaration, but didn't show where the value was accessed before that
declaration. We now track where each hoisted variable is first accessed
and report two errors, one for the reference and one for the
declaration. When we improve our diagnostic infra to support reporting
errors at multiple locations we can merge these into a single conceptual
error.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33514).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* __->__ #33514
* #33573
2025-06-18 15:24:41 -07:00
Joseph Savona
7ce2a63acc [compiler] update fixtures (#33573)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33573).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* __->__ #33573
2025-06-18 15:24:30 -07:00
Joseph Savona
b067c6fe79 [compiler] Improve error message for mutating hook args/return (#33513)
The previous error message was generic, because the old style function
signature didn't support a way to specify a reason alongside a freeze
effect. This meant we could only say why a value was frozen for
instructions, but not hooks which use function signatures. By defining a
new aliasing signature for custom hooks we can specify a reason and
provide a better error message.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33513).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* __->__ #33513
2025-06-18 13:04:53 -07:00
Joseph Savona
e081cb3446 [compiler] FunctionExpression context locations point to first reference (#33512)
This has always been awkward: `FunctionExpression.context` places have
locations set to the declaration of the identifier, whereas other
references have locations pointing to the reference itself. Here, we
update context operands to have their location point to the first
reference of that variable within the function.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33512).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* __->__ #33512
* #33504
* #33500
* #33497
* #33496
2025-06-18 13:02:43 -07:00
Joseph Savona
7b67dc92b0 [commit] Better error message for invalid hoisting (#33504)
We're already tracking which variables are hoisted context variables, so
if we see a mutation of a frozen value we can emit a custom error
message to help users identify the problem.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33504).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* __->__ #33504
* #33500
* #33497
* #33496
2025-06-18 13:02:32 -07:00
Joseph Savona
7c28c15465 [compiler] Fix AnalyzeFunctions to fully reset context identifiers (#33500)
AnalyzeFunctions had logic to reset the mutable ranges of context
variables after visiting inner function expressions. However, there was
a bug in that logic: InferReactiveScopeVariables makes all the
identifiers in a scope point to the same mutable range instance. That
meant that it was possible for a later function expression to indirectly
cause an earlier function expressions' context variables to get a
non-zero mutable range.

The fix is to not just reset start/end of context var ranges, but assign
a new range instance. Thanks for the help on debugging, @mofeiz!

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33500).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* __->__ #33500
* #33497
* #33496
2025-06-18 13:02:23 -07:00
Joseph Savona
90ccbd71c1 [compiler] Enable new inference by default (#33497)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33497).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* __->__ #33497
* #33496
2025-06-18 13:02:12 -07:00
Joseph Savona
0cf6d0c929 [compiler] Update fixtures for new inference (#33496)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33496).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* __->__ #33496
2025-06-18 13:01:56 -07:00
90 changed files with 1093 additions and 482 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: Array<t.Identifier> = [],
capturedRefs: Map<t.Identifier, SourceLocation> = new Map(),
): Result<HIRFunction, CompilerError> {
const builder = new HIRBuilder(env, {
bindings,
@@ -80,13 +80,13 @@ export function lower(
});
const context: HIRFunction['context'] = [];
for (const ref of capturedRefs ?? []) {
for (const [ref, loc] of capturedRefs ?? []) {
context.push({
kind: 'Identifier',
identifier: builder.resolveBinding(ref),
effect: Effect.Unknown,
reactive: false,
loc: ref.loc ?? GeneratedSource,
loc,
});
}
@@ -3439,10 +3439,12 @@ 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, [
...builder.context,
...capturedContext,
]);
const lowering = lower(
expr,
builder.environment,
builder.bindings,
new Map([...builder.context, ...capturedContext]),
);
let loweredFunc: HIRFunction;
if (lowering.isErr()) {
lowering
@@ -4160,6 +4162,11 @@ 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
@@ -4168,8 +4175,8 @@ function gatherCapturedContext(
| t.ObjectMethod
>,
componentScope: Scope,
): Array<t.Identifier> {
const capturedIds = new Set<t.Identifier>();
): Map<t.Identifier, SourceLocation> {
const capturedIds = new Map<t.Identifier, SourceLocation>();
/*
* Capture all the scopes from the parent of this function up to and including
@@ -4212,8 +4219,15 @@ 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.add(binding.identifier);
if (
binding !== undefined &&
pureScopes.has(binding.scope) &&
!capturedIds.has(binding.identifier)
) {
capturedIds.set(
binding.identifier,
path.node.loc ?? binding.identifier.loc ?? GeneratedSource,
);
}
}
@@ -4250,7 +4264,7 @@ function gatherCapturedContext(
},
});
return [...capturedIds.keys()];
return capturedIds;
}
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(false),
enableNewMutationAliasingModel: z.boolean().default(true),
/**
* Enables inference of optional dependency chains. Without this flag

View File

@@ -1388,6 +1388,16 @@ 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
*/

View File

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

View File

@@ -1302,6 +1302,34 @@ export const DefaultNonmutatingHook = addHook(
calleeEffect: Effect.Read,
hookKind: 'Custom',
returnValueKind: ValueKind.Frozen,
aliasing: {
receiver: makeIdentifierId(0),
params: [],
rest: makeIdentifierId(1),
returns: makeIdentifierId(2),
temporaries: [],
effects: [
// Freeze the arguments
{
kind: 'Freeze',
value: signatureArgument(1),
reason: ValueReason.HookCaptured,
},
// Returns a frozen value
{
kind: 'Create',
into: signatureArgument(2),
value: ValueKind.Frozen,
reason: ValueReason.HookReturn,
},
// May alias any arguments into the return
{
kind: 'Alias',
from: signatureArgument(1),
into: signatureArgument(2),
},
],
},
},
'DefaultNonmutatingHook',
);

View File

@@ -8,6 +8,7 @@
import {CompilerErrorDetailOptions} from '../CompilerError';
import {
FunctionExpression,
GeneratedSource,
Hole,
IdentifierId,
ObjectMethod,
@@ -18,6 +19,7 @@ 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
@@ -200,10 +202,19 @@ export function hashEffect(effect: AliasingEffect): string {
return [effect.kind, effect.value.identifier.id, effect.reason].join(':');
}
case 'Impure':
case 'Render':
case 'Render': {
return [effect.kind, effect.place.identifier.id].join(':');
}
case 'MutateFrozen':
case 'MutateGlobal': {
return [effect.kind, effect.place.identifier.id].join(':');
return [
effect.kind,
effect.place.identifier.id,
effect.error.severity,
effect.error.reason,
effect.error.description,
printSourceLocation(effect.error.loc ?? GeneratedSource),
].join(':');
}
case 'Mutate':
case 'MutateConditionally':

View File

@@ -42,8 +42,16 @@ export default function analyseFunctions(func: HIRFunction): void {
* Reset mutable range for outer inferReferenceEffects
*/
for (const operand of instr.value.loweredFunc.func.context) {
operand.identifier.mutableRange.start = makeInstructionId(0);
operand.identifier.mutableRange.end = makeInstructionId(0);
/**
* 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.scope = null;
}
break;

View File

@@ -341,6 +341,10 @@ 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,6 +38,7 @@ import {
import {
eachInstructionValueLValue,
eachInstructionValueOperand,
eachTerminalOperand,
eachTerminalSuccessor,
} from '../HIR/visitors';
import {Ok, Result} from '../Utils/Result';
@@ -49,12 +50,14 @@ import {
} from './InferReferenceEffects';
import {
assertExhaustive,
getOrInsertDefault,
getOrInsertWith,
Set_isSuperset,
} from '../Utils/utils';
import {
printAliasingEffect,
printAliasingSignature,
printFunction,
printIdentifier,
printInstruction,
printInstructionValue,
@@ -194,12 +197,15 @@ export function inferMutationAliasingEffects(
let count = 0;
while (queuedStates.size !== 0) {
count++;
if (count > 1000) {
if (count > 100) {
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) {
@@ -211,6 +217,11 @@ 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)) {
@@ -221,8 +232,19 @@ export function inferMutationAliasingEffects(
return Ok(undefined);
}
function findHoistedContextDeclarations(fn: HIRFunction): Set<DeclarationId> {
const hoisted = new Set<DeclarationId>();
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);
}
}
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
if (instr.value.kind === 'DeclareContext') {
@@ -232,10 +254,17 @@ function findHoistedContextDeclarations(fn: HIRFunction): Set<DeclarationId> {
kind == InstructionKind.HoistedFunction ||
kind == InstructionKind.HoistedLet
) {
hoisted.add(instr.value.lvalue.place.identifier.declarationId);
hoisted.set(instr.value.lvalue.place.identifier.declarationId, null);
}
} else {
for (const operand of eachInstructionValueOperand(instr.value)) {
visit(operand);
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
visit(operand);
}
}
return hoisted;
}
@@ -245,21 +274,40 @@ 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: Set<DeclarationId>;
hoistedContextDeclarations: Map<DeclarationId, Place | null>;
constructor(
isFunctionExpression: boolean,
fn: HIRFunction,
hoistedContextDeclarations: Set<DeclarationId>,
hoistedContextDeclarations: Map<DeclarationId, Place | null>,
) {
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);
@@ -333,11 +381,13 @@ function inferBlock(
state.appendAlias(handlerParam, instr.lvalue);
const kind = state.kind(instr.lvalue).kind;
if (kind === ValueKind.Mutable || kind == ValueKind.Context) {
effects.push({
kind: 'Alias',
from: instr.lvalue,
into: handlerParam,
});
effects.push(
context.internEffect({
kind: 'Alias',
from: instr.lvalue,
into: handlerParam,
}),
);
}
}
}
@@ -346,11 +396,11 @@ function inferBlock(
} else if (terminal.kind === 'return') {
if (!context.isFuctionExpression) {
terminal.effects = [
{
context.internEffect({
kind: 'Freeze',
value: terminal.value,
reason: ValueReason.JsxCaptured,
},
}),
];
}
}
@@ -527,20 +577,21 @@ function applyEffect(
break;
}
case ValueKind.Frozen: {
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
break;
}
default: {
effects.push({
// OK: recording information flow
kind: 'CreateFrom', // prev Alias
from: effect.from,
into: effect.into,
});
effects.push(effect);
}
}
break;
@@ -639,11 +690,17 @@ function applyEffect(
}
case ValueKind.Frozen: {
isMutableReferenceType = false;
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
break;
}
default: {
@@ -665,11 +722,17 @@ function applyEffect(
const fromKind = fromValue.kind;
switch (fromKind) {
case ValueKind.Frozen: {
effects.push({
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
});
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
aliased,
effects,
);
let value = context.effectInstructionValueCache.get(effect);
if (value == null) {
value = {
@@ -727,23 +790,33 @@ 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 signature = buildSignatureFromFunctionExpression(
state.env,
functionValues[0],
);
const functionExpr = functionValues[0];
let signature = context.functionSignatureCache.get(functionExpr);
if (signature == null) {
signature = buildSignatureFromFunctionExpression(
state.env,
functionExpr,
);
context.functionSignatureCache.set(functionExpr, signature);
}
if (DEBUG) {
console.log(
`constructed alias signature:\n${printAliasingSignature(signature)}`,
);
}
const signatureEffects = computeEffectsForSignature(
state.env,
const signatureEffects = context.cacheApplySignature(
signature,
effect.into,
effect.receiver,
effect.args,
functionValues[0].loweredFunc.func.context,
effect.loc,
effect,
() =>
computeEffectsForSignature(
state.env,
signature,
effect.into,
effect.receiver,
effect.args,
functionExpr.loweredFunc.func.context,
effect.loc,
),
);
if (signatureEffects != null) {
if (DEBUG) {
@@ -762,18 +835,24 @@ function applyEffect(
break;
}
}
const signatureEffects =
effect.signature?.aliasing != null
? computeEffectsForSignature(
let signatureEffects = null;
if (effect.signature?.aliasing != null) {
const signature = effect.signature.aliasing;
signatureEffects = context.cacheApplySignature(
effect.signature.aliasing,
effect,
() =>
computeEffectsForSignature(
state.env,
effect.signature.aliasing,
signature,
effect.into,
effect.receiver,
effect.args,
[],
effect.loc,
)
: null;
),
);
}
if (signatureEffects != null) {
if (DEBUG) {
console.log('apply aliasing signature effects');
@@ -901,27 +980,89 @@ function applyEffect(
console.log(prettyFormat(state.debugAbstractValue(value)));
}
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,
},
});
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,
);
}
}
break;
}
@@ -1959,28 +2100,17 @@ function computeEffectsForLegacySignature(
break;
}
case Effect.ConditionallyMutateIterator: {
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,
});
const mutateIterator = conditionallyMutateIterator(place);
if (mutateIterator != null) {
effects.push(mutateIterator);
// TODO: should we always push to captures?
captures.push(place);
effects.push({
kind: 'MutateTransitiveConditionally',
value: place,
});
}
effects.push({
kind: 'Capture',
from: place,
into: lvalue,
});
break;
}
case Effect.Freeze: {
@@ -2170,6 +2300,7 @@ 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]);
@@ -2187,6 +2318,13 @@ 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]);
@@ -2258,6 +2396,12 @@ 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,21 +175,14 @@ import {
* and mutability.
*/
function Component(t0) {
const $ = _c(4);
const $ = _c(2);
const { prop } = t0;
let t1;
if ($[0] !== prop) {
const obj = shallowCopy(prop);
const aliasedObj = identity(obj);
let t2;
if ($[2] !== obj) {
t2 = [obj.id];
$[2] = obj;
$[3] = t2;
} else {
t2 = $[3];
}
const id = t2;
const id = [obj.id];
mutate(aliasedObj);
setPropertyByKey(aliasedObj, "id", prop.id + 1);

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
11 | });
12 |
> 13 | x.value += count;
| ^ InvalidReact: This mutates a variable that React considers immutable (13:13)
| ^ 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)
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: This mutates a variable that React considers immutable (13:13)
| ^ 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)
14 | return <Stringify x={x} cb={cb} />;
15 | }
16 |

View File

@@ -38,13 +38,15 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
19 | useEffect(() => setState(2), []);
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)
20 |
> 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)
21 | const [state, setState] = useState(0);
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: Mutating a value returned from a function whose return value should not be mutated. Found mutation of `sharedVal` (11:11)
| ^^^^^^^^^ 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)
12 | title="Randomize"
13 | />
14 | );

View File

@@ -16,6 +16,8 @@ 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,6 +21,8 @@ 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,6 +21,8 @@ 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,6 +18,8 @@ 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: [hoisting] Expected value for identifier to be initialized. hasErrors_0$15 (9:9)
| ^^^^^^^^^ Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized. <unknown> hasErrors_0$15:TFunction (9:9)
10 | }
11 |
```

View File

@@ -34,13 +34,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
13 | return bar();
11 |
12 | function foo() {
> 13 | return bar();
| ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (13:13)
14 | }
> 15 | function bar() {
| ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (15:15)
15 | function bar() {
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":"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":"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":"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,"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":"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":"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":"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":"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":"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":"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":"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":"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,22 +34,28 @@ 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(2);
const $ = _c(4);
const [, setState1] = useRef("initial value");
const [, setState2] = useRef("initial value");
let setState;
if (props.foo) {
setState = setState1;
if ($[0] !== props.foo) {
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
$[0] = props.foo;
$[1] = setState;
} else {
setState = setState2;
setState = $[1];
}
let t0;
if ($[0] !== setState) {
if ($[2] !== setState) {
t0 = () => print(setState);
$[0] = setState;
$[1] = t0;
$[2] = setState;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
useEffect(t0, [setState]);
}

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {
Stringify,
mutate,
@@ -101,7 +102,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import {
Stringify,
mutate,
@@ -175,21 +176,14 @@ import {
* and mutability.
*/
function Component(t0) {
const $ = _c(4);
const $ = _c(2);
const { prop } = t0;
let t1;
if ($[0] !== prop) {
const obj = shallowCopy(prop);
const aliasedObj = identity(obj);
let t2;
if ($[2] !== obj) {
t2 = [obj.id];
$[2] = obj;
$[3] = t2;
} else {
t2 = $[3];
}
const id = t2;
const id = [obj.id];
mutate(aliasedObj);
setPropertyByKey(aliasedObj, "id", prop.id + 1);

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {
Stringify,
mutate,

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {Stringify} from 'shared-runtime';
/**
@@ -43,7 +44,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { Stringify } from "shared-runtime";
/**

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {Stringify} from 'shared-runtime';
/**

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
@@ -23,19 +24,27 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a) {
const $ = _c(2);
let y;
const $ = _c(4);
let t0;
if ($[0] !== a) {
const x = [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];
$[0] = a;
$[1] = y;
$[2] = x[0][1];
$[3] = y;
} else {
y = $[1];
y = $[3];
}
return y;
}

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a, b) {
let x = [a, b];
let y = {};
@@ -27,22 +28,31 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a, b) {
const $ = _c(3);
let y;
const $ = _c(6);
let t0;
if ($[0] !== a || $[1] !== b) {
const x = [a, 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];
$[0] = a;
$[1] = b;
$[2] = y;
$[3] = x[0][1];
$[4] = x[1][0];
$[5] = y;
} else {
y = $[2];
y = $[5];
}
return y;
}

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
function bar(a, b) {
let x = [a, b];
let y = {};

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
@@ -23,19 +24,27 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a) {
const $ = _c(2);
let y;
const $ = _c(4);
let t0;
if ($[0] !== a) {
const x = [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];
$[0] = a;
$[1] = y;
$[2] = x[0].a[1];
$[3] = y;
} else {
y = $[1];
y = $[3];
}
return y;
}

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};
@@ -22,19 +23,27 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
function bar(a) {
const $ = _c(2);
let y;
const $ = _c(4);
let t0;
if ($[0] !== a) {
const x = [a];
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0]) {
y = {};
y = x[0];
$[0] = a;
$[1] = y;
$[2] = x[0];
$[3] = y;
} else {
y = $[1];
y = $[3];
}
return y;
}

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
function bar(a) {
let x = [a];
let y = {};

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validateNoImpureFunctionsInRender
// @validateNoImpureFunctionsInRender @enableNewMutationAliasingModel
function Component() {
const date = Date.now();
@@ -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

@@ -1,4 +1,4 @@
// @validateNoImpureFunctionsInRender
// @validateNoImpureFunctionsInRender @enableNewMutationAliasingModel
function Component() {
const date = Date.now();

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component() {
let local;
@@ -41,13 +42,13 @@ function Component() {
## Error
```
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 => {
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 => {
```

View File

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

View File

@@ -0,0 +1,45 @@
## 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

@@ -0,0 +1,22 @@
//@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: This mutates a variable that React considers immutable (5:5)
| ^ 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)
6 | return <div>error</div>;
7 | }
8 |

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function useHook(a, b) {
b.test = 1;
a.test = 2;
@@ -13,12 +14,15 @@ function useHook(a, b) {
## Error
```
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)
3 | a.test = 2;
4 | }
5 |
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,3 +1,4 @@
// @enableNewMutationAliasingModel
function useHook(a, b) {
b.test = 1;
a.test = 2;

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
let x = {a: 42};
function Component(props) {
@@ -17,13 +18,15 @@ function Component(props) {
## Error
```
3 | 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)
6 | x.a = 20;
7 | });
8 | }
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,3 +1,4 @@
// @enableNewMutationAliasingModel
let x = {a: 42};
function Component(props) {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component() {
const foo = () => {
// Cannot assign to globals
@@ -17,13 +18,15 @@ function Component() {
## Error
```
2 | const foo = () => {
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)
5 | moduleLocal = true;
6 | };
7 | foo();
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,3 +1,4 @@
// @enableNewMutationAliasingModel
function Component() {
const foo = () => {
// Cannot assign to globals

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component() {
// Cannot assign to globals
someUnknownGlobal = true;
@@ -14,13 +15,15 @@ function Component() {
## Error
```
1 | 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)
4 | moduleLocal = true;
5 | }
6 |
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,3 +1,4 @@
// @enableNewMutationAliasingModel
function Component() {
// Cannot assign to globals
someUnknownGlobal = true;

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
function Component(props) {
function hasErrors() {
let hasErrors = false;
@@ -19,12 +20,12 @@ function Component(props) {
## Error
```
7 | return hasErrors;
8 | }
> 9 | return hasErrors();
| ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$15 (9:9)
10 | }
11 |
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,3 +1,4 @@
// @enableNewMutationAliasingModel
function Component(props) {
function hasErrors() {
let hasErrors = false;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
import {print} from 'shared-runtime';
@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect } from "react";
import { print } from "shared-runtime";
@@ -48,9 +48,9 @@ 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":"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}
{"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

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
import {print} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect, useRef } from "react";
import { print } from "shared-runtime";
@@ -47,9 +47,9 @@ 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,"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}
{"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

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
function Component({foo}) {
@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect } from "react";
function Component(t0) {
@@ -47,9 +47,9 @@ 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":"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}
{"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

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {useEffect} from 'react';
function Component({foo}) {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {identity, mutate} from 'shared-runtime';
function Component(props) {
@@ -23,38 +24,22 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { identity, mutate } from "shared-runtime";
function Component(props) {
const $ = _c(5);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
$[0] = t0;
} else {
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;
const $ = _c(2);
let context;
if ($[0] !== props.value) {
const key = {};
context = { [key]: identity([props.value]) };
mutate(key);
mutate(key);
$[0] = props.value;
$[1] = context;
} else {
context = $[1];
}
return context;
}

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {identity, mutate} from 'shared-runtime';
function Component(props) {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {identity, mutate, mutateAndReturn} from 'shared-runtime';
function Component(props) {
@@ -23,15 +24,26 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { identity, mutate, mutateAndReturn } from "shared-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(4);
let context;
if ($[0] !== props.value) {
const key = { a: "key" };
context = { [key.a]: identity([props.value]) };
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;

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {identity, mutate, mutateAndReturn} from 'shared-runtime';
function Component(props) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies
// @inferEffectDependencies @enableNewMutationAliasingModel
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';
@@ -26,7 +26,7 @@ function ReactiveRefInEffect(props) {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @enableNewMutationAliasingModel
import { useEffect, useState } from "react";
import { print } from "shared-runtime";
@@ -34,22 +34,28 @@ 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(2);
const $ = _c(4);
const [, setState1] = useRef("initial value");
const [, setState2] = useRef("initial value");
let setState;
if (props.foo) {
setState = setState1;
if ($[0] !== props.foo) {
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
$[0] = props.foo;
$[1] = setState;
} else {
setState = setState2;
setState = $[1];
}
let t0;
if ($[0] !== setState) {
if ($[2] !== setState) {
t0 = () => print(setState);
$[0] = setState;
$[1] = t0;
$[2] = setState;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
useEffect(t0, [setState]);
}

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies
// @inferEffectDependencies @enableNewMutationAliasingModel
import {useEffect, useState} from 'react';
import {print} from 'shared-runtime';

View File

@@ -0,0 +1,54 @@
## 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

@@ -0,0 +1,17 @@
// @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

@@ -0,0 +1,80 @@
## 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

@@ -0,0 +1,25 @@
//@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

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
@@ -52,10 +52,10 @@ 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":"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}
{"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

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableFire
// @enableFire @enableNewMutationAliasingModel
import {fire} from 'react';
function Component({bar, baz}) {
@@ -26,7 +26,7 @@ function Component({bar, baz}) {
## Code
```javascript
import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @enableNewMutationAliasingModel
import { fire } from "react";
function Component(t0) {

View File

@@ -1,4 +1,4 @@
// @enableFire
// @enableFire @enableNewMutationAliasingModel
import {fire} from 'react';
function Component({bar, baz}) {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
@@ -35,44 +36,51 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(t0) {
const $ = _c(8);
const $ = _c(10);
const { arr1, arr2, foo } = t0;
let getVal1;
let t1;
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
const x = [arr1];
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;
t1 = () => [y];
t2 = () => [y];
foo ? (y = x.concat(arr2)) : y;
$[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} />;
$[2] = arr2;
$[3] = foo;
$[4] = x;
$[5] = getVal1;
$[6] = getVal2;
$[7] = t2;
$[6] = t2;
} else {
t2 = $[7];
getVal1 = $[5];
t2 = $[6];
}
return t2;
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 };

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
@@ -30,37 +31,44 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
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(5);
const $ = _c(7);
let t0;
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let t1;
if ($[2] !== arr2 || $[3] !== x) {
let y;
t0 = () => ({ y });
t1 = () => ({ y });
(y = x.concat(arr2)), y;
$[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;
$[2] = arr2;
$[3] = x;
$[4] = t1;
} else {
t1 = $[4];
}
return t1;
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 = {

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @enableNewMutationAliasingModel
import {useMemo} from 'react';
function useFoo(arr1, arr2) {
@@ -26,33 +27,40 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { useMemo } from "react";
function useFoo(arr1, arr2) {
const $ = _c(5);
let y;
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
(y = x.concat(arr2)), y;
$[0] = arr1;
$[1] = arr2;
$[2] = y;
} else {
y = $[2];
}
const $ = _c(7);
let t0;
let t1;
if ($[3] !== y) {
t1 = { y };
$[3] = y;
$[4] = t1;
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t1 = $[4];
t0 = $[1];
}
t0 = t1;
return t0;
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 = {

View File

@@ -1,3 +1,4 @@
// @enableNewMutationAliasingModel
import {useMemo} from 'react';
function useFoo(arr1, arr2) {

View File

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

View File

@@ -27,11 +27,22 @@ import { c as _c } from "react/compiler-runtime";
import { identity, mutate, mutateAndReturn } from "shared-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(4);
let context;
if ($[0] !== props.value) {
const key = { a: "key" };
context = { [key.a]: identity([props.value]) };
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;

View File

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

View File

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

View File

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