Compare commits

...

3 Commits

Author SHA1 Message Date
Joe Savona
a427c3c1d9 [compiler] Cleanup debugging code
Removes unnecessary debugging code in the new inference passes now that they've stabilized more.
2025-06-18 15:59:35 -07:00
Joe Savona
fc2c4f2f15 [compiler] Preserve Create effects, guarantee effects initialize once
Ensures that effects are well-formed with respect to the rules:
* For a given instruction, each place is only initialized once (w one of Create, CreateFrom, Assign)
* Ensures that Alias targets are already initialized within the same instruction (should have a Create before them)
* Preserves Create and similar instructions
* Avoids duplicate instructions when inferring effects of function expressions
2025-06-18 15:59:35 -07:00
Joe Savona
963970b7a2 [compiler] Fix <ValidateMemoization>
By accident we were only ever checking the compiled output, but the intention was in general to be able to compare memoization with/without forget.
2025-06-18 15:59:35 -07:00
24 changed files with 250 additions and 264 deletions

View File

@@ -20,10 +20,11 @@ import {inferReactiveScopeVariables} from '../ReactiveScopes';
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA'; import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
import {inferMutableRanges} from './InferMutableRanges'; import {inferMutableRanges} from './InferMutableRanges';
import inferReferenceEffects from './InferReferenceEffects'; import inferReferenceEffects from './InferReferenceEffects';
import {assertExhaustive} from '../Utils/utils'; import {assertExhaustive, retainWhere} from '../Utils/utils';
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects'; import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
import {inferFunctionExpressionAliasingEffectsSignature} from './InferFunctionExpressionAliasingEffectsSignature'; import {inferFunctionExpressionAliasingEffectsSignature} from './InferFunctionExpressionAliasingEffectsSignature';
import {inferMutationAliasingRanges} from './InferMutationAliasingRanges'; import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
import {hashEffect} from './AliasingEffects';
export default function analyseFunctions(func: HIRFunction): void { export default function analyseFunctions(func: HIRFunction): void {
for (const [_, block] of func.body.blocks) { for (const [_, block] of func.body.blocks) {
@@ -81,6 +82,17 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
fn.aliasingEffects ??= []; fn.aliasingEffects ??= [];
fn.aliasingEffects?.push(...effects); fn.aliasingEffects?.push(...effects);
} }
if (fn.aliasingEffects != null) {
const seen = new Set<string>();
retainWhere(fn.aliasingEffects, effect => {
const hash = hashEffect(effect);
if (seen.has(hash)) {
return false;
}
seen.add(hash);
return true;
});
}
/** /**
* Phase 2: populate the Effect of each context variable to use in inferring * Phase 2: populate the Effect of each context variable to use in inferring

View File

@@ -57,7 +57,6 @@ import {
import { import {
printAliasingEffect, printAliasingEffect,
printAliasingSignature, printAliasingSignature,
printFunction,
printIdentifier, printIdentifier,
printInstruction, printInstruction,
printInstructionValue, printInstructionValue,
@@ -194,19 +193,15 @@ export function inferMutationAliasingEffects(
hoistedContextDeclarations, hoistedContextDeclarations,
); );
let count = 0; let iterationCount = 0;
while (queuedStates.size !== 0) { while (queuedStates.size !== 0) {
count++; iterationCount++;
if (count > 100) { if (iterationCount > 100) {
console.log( CompilerError.invariant(false, {
'oops infinite loop', reason: `[InferMutationAliasingEffects] Potential infinite loop`,
fn.id, description: `A value, temporary place, or effect was not cached properly`,
typeof fn.loc !== 'symbol' ? fn.loc?.filename : null, loc: fn.loc,
); });
if (DEBUG) {
console.log(printFunction(fn));
}
throw new Error('infinite loop');
} }
for (const [blockId, block] of fn.body.blocks) { for (const [blockId, block] of fn.body.blocks) {
const incomingState = queuedStates.get(blockId); const incomingState = queuedStates.get(blockId);
@@ -217,11 +212,6 @@ export function inferMutationAliasingEffects(
statesByBlock.set(blockId, incomingState); statesByBlock.set(blockId, incomingState);
const state = incomingState.clone(); const state = incomingState.clone();
if (DEBUG) {
console.log('*************');
console.log(`bb${block.id}`);
console.log('*************');
}
inferBlock(context, state, block); inferBlock(context, state, block);
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) { for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
@@ -362,6 +352,11 @@ function inferBlock(
} else if (terminal.kind === 'maybe-throw') { } else if (terminal.kind === 'maybe-throw') {
const handlerParam = context.catchHandlers.get(terminal.handler); const handlerParam = context.catchHandlers.get(terminal.handler);
if (handlerParam != null) { if (handlerParam != null) {
CompilerError.invariant(state.kind(handlerParam) != null, {
reason:
'Expected catch binding to be intialized with a DeclareLocal Catch instruction',
loc: terminal.loc,
});
const effects: Array<AliasingEffect> = []; const effects: Array<AliasingEffect> = [];
for (const instr of block.instructions) { for (const instr of block.instructions) {
if ( if (
@@ -476,14 +471,14 @@ function applySignature(
* Track which values we've already aliased once, so that we can switch to * Track which values we've already aliased once, so that we can switch to
* appendAlias() for subsequent aliases into the same value * appendAlias() for subsequent aliases into the same value
*/ */
const aliased = new Set<IdentifierId>(); const initialized = new Set<IdentifierId>();
if (DEBUG) { if (DEBUG) {
console.log(printInstruction(instruction)); console.log(printInstruction(instruction));
} }
for (const effect of signature.effects) { for (const effect of signature.effects) {
applyEffect(context, state, effect, aliased, effects); applyEffect(context, state, effect, initialized, effects);
} }
if (DEBUG) { if (DEBUG) {
console.log( console.log(
@@ -508,7 +503,7 @@ function applyEffect(
context: Context, context: Context,
state: InferenceState, state: InferenceState,
_effect: AliasingEffect, _effect: AliasingEffect,
aliased: Set<IdentifierId>, initialized: Set<IdentifierId>,
effects: Array<AliasingEffect>, effects: Array<AliasingEffect>,
): void { ): void {
const effect = context.internEffect(_effect); const effect = context.internEffect(_effect);
@@ -524,6 +519,13 @@ function applyEffect(
break; break;
} }
case 'Create': { case 'Create': {
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
});
initialized.add(effect.into.identifier.id);
let value = context.effectInstructionValueCache.get(effect); let value = context.effectInstructionValueCache.get(effect);
if (value == null) { if (value == null) {
value = { value = {
@@ -538,6 +540,7 @@ function applyEffect(
reason: new Set([effect.reason]), reason: new Set([effect.reason]),
}); });
state.define(effect.into, value); state.define(effect.into, value);
effects.push(effect);
break; break;
} }
case 'ImmutableCapture': { case 'ImmutableCapture': {
@@ -555,6 +558,13 @@ function applyEffect(
break; break;
} }
case 'CreateFrom': { case 'CreateFrom': {
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
});
initialized.add(effect.into.identifier.id);
const fromValue = state.kind(effect.from); const fromValue = state.kind(effect.from);
let value = context.effectInstructionValueCache.get(effect); let value = context.effectInstructionValueCache.get(effect);
if (value == null) { if (value == null) {
@@ -573,10 +583,21 @@ function applyEffect(
switch (fromValue.kind) { switch (fromValue.kind) {
case ValueKind.Primitive: case ValueKind.Primitive:
case ValueKind.Global: { case ValueKind.Global: {
// no need to track this data flow effects.push({
kind: 'Create',
value: fromValue.kind,
into: effect.into,
reason: [...fromValue.reason][0] ?? ValueReason.Other,
});
break; break;
} }
case ValueKind.Frozen: { case ValueKind.Frozen: {
effects.push({
kind: 'Create',
value: fromValue.kind,
into: effect.into,
reason: [...fromValue.reason][0] ?? ValueReason.Other,
});
applyEffect( applyEffect(
context, context,
state, state,
@@ -585,7 +606,7 @@ function applyEffect(
from: effect.from, from: effect.from,
into: effect.into, into: effect.into,
}, },
aliased, initialized,
effects, effects,
); );
break; break;
@@ -597,6 +618,13 @@ function applyEffect(
break; break;
} }
case 'CreateFunction': { case 'CreateFunction': {
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
});
initialized.add(effect.into.identifier.id);
effects.push(effect); effects.push(effect);
/** /**
* We consider the function mutable if it has any mutable context variables or * We consider the function mutable if it has any mutable context variables or
@@ -653,7 +681,7 @@ function applyEffect(
from: capture, from: capture,
into: effect.into, into: effect.into,
}, },
aliased, initialized,
effects, effects,
); );
} }
@@ -661,6 +689,14 @@ function applyEffect(
} }
case 'Alias': case 'Alias':
case 'Capture': { case 'Capture': {
CompilerError.invariant(
effect.kind === 'Capture' || initialized.has(effect.into.identifier.id),
{
reason: `Expected destination value to already be initialized within this instruction for Alias effect`,
description: `Destination ${printPlace(effect.into)} is not initialized in this instruction`,
loc: effect.into.loc,
},
);
/* /*
* Capture describes potential information flow: storing a pointer to one value * Capture describes potential information flow: storing a pointer to one value
* within another. If the destination is not mutable, or the source value has * within another. If the destination is not mutable, or the source value has
@@ -698,7 +734,7 @@ function applyEffect(
from: effect.from, from: effect.from,
into: effect.into, into: effect.into,
}, },
aliased, initialized,
effects, effects,
); );
break; break;
@@ -714,6 +750,13 @@ function applyEffect(
break; break;
} }
case 'Assign': { case 'Assign': {
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
reason: `Cannot re-initialize variable within an instruction`,
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
loc: effect.into.loc,
});
initialized.add(effect.into.identifier.id);
/* /*
* Alias represents potential pointer aliasing. If the type is a global, * Alias represents potential pointer aliasing. If the type is a global,
* a primitive (copy-on-write semantics) then we can prune the effect * a primitive (copy-on-write semantics) then we can prune the effect
@@ -730,7 +773,7 @@ function applyEffect(
from: effect.from, from: effect.from,
into: effect.into, into: effect.into,
}, },
aliased, initialized,
effects, effects,
); );
let value = context.effectInstructionValueCache.get(effect); let value = context.effectInstructionValueCache.get(effect);
@@ -768,12 +811,7 @@ function applyEffect(
break; break;
} }
default: { default: {
if (aliased.has(effect.into.identifier.id)) { state.assign(effect.into, effect.from);
state.appendAlias(effect.into, effect.from);
} else {
aliased.add(effect.into.identifier.id);
state.alias(effect.into, effect.from);
}
effects.push(effect); effects.push(effect);
break; break;
} }
@@ -819,18 +857,15 @@ function applyEffect(
), ),
); );
if (signatureEffects != null) { if (signatureEffects != null) {
if (DEBUG) {
console.log('apply function expression effects');
}
applyEffect( applyEffect(
context, context,
state, state,
{kind: 'MutateTransitiveConditionally', value: effect.function}, {kind: 'MutateTransitiveConditionally', value: effect.function},
aliased, initialized,
effects, effects,
); );
for (const signatureEffect of signatureEffects) { for (const signatureEffect of signatureEffects) {
applyEffect(context, state, signatureEffect, aliased, effects); applyEffect(context, state, signatureEffect, initialized, effects);
} }
break; break;
} }
@@ -854,16 +889,10 @@ function applyEffect(
); );
} }
if (signatureEffects != null) { if (signatureEffects != null) {
if (DEBUG) {
console.log('apply aliasing signature effects');
}
for (const signatureEffect of signatureEffects) { for (const signatureEffect of signatureEffects) {
applyEffect(context, state, signatureEffect, aliased, effects); applyEffect(context, state, signatureEffect, initialized, effects);
} }
} else if (effect.signature != null) { } else if (effect.signature != null) {
if (DEBUG) {
console.log('apply legacy signature effects');
}
const legacyEffects = computeEffectsForLegacySignature( const legacyEffects = computeEffectsForLegacySignature(
state, state,
effect.signature, effect.signature,
@@ -873,12 +902,9 @@ function applyEffect(
effect.loc, effect.loc,
); );
for (const legacyEffect of legacyEffects) { for (const legacyEffect of legacyEffects) {
applyEffect(context, state, legacyEffect, aliased, effects); applyEffect(context, state, legacyEffect, initialized, effects);
} }
} else { } else {
if (DEBUG) {
console.log('default effects');
}
applyEffect( applyEffect(
context, context,
state, state,
@@ -888,7 +914,7 @@ function applyEffect(
value: ValueKind.Mutable, value: ValueKind.Mutable,
reason: ValueReason.Other, reason: ValueReason.Other,
}, },
aliased, initialized,
effects, effects,
); );
/* /*
@@ -911,21 +937,21 @@ function applyEffect(
kind: 'MutateTransitiveConditionally', kind: 'MutateTransitiveConditionally',
value: operand, value: operand,
}, },
aliased, initialized,
effects, effects,
); );
} }
const mutateIterator = const mutateIterator =
arg.kind === 'Spread' ? conditionallyMutateIterator(operand) : null; arg.kind === 'Spread' ? conditionallyMutateIterator(operand) : null;
if (mutateIterator) { if (mutateIterator) {
applyEffect(context, state, mutateIterator, aliased, effects); applyEffect(context, state, mutateIterator, initialized, effects);
} }
applyEffect( applyEffect(
context, context,
state, state,
// OK: recording information flow // OK: recording information flow
{kind: 'Alias', from: operand, into: effect.into}, {kind: 'Alias', from: operand, into: effect.into},
aliased, initialized,
effects, effects,
); );
for (const otherArg of [ for (const otherArg of [
@@ -953,7 +979,7 @@ function applyEffect(
from: operand, from: operand,
into: other, into: other,
}, },
aliased, initialized,
effects, effects,
); );
} }
@@ -1009,7 +1035,7 @@ function applyEffect(
suggestions: null, suggestions: null,
}, },
}, },
aliased, initialized,
effects, effects,
); );
} }
@@ -1028,7 +1054,7 @@ function applyEffect(
suggestions: null, suggestions: null,
}, },
}, },
aliased, initialized,
effects, effects,
); );
} else { } else {
@@ -1059,7 +1085,7 @@ function applyEffect(
suggestions: null, suggestions: null,
}, },
}, },
aliased, initialized,
effects, effects,
); );
} }
@@ -1166,7 +1192,7 @@ class InferenceState {
} }
// Updates the value at @param place to point to the same value as @param value. // Updates the value at @param place to point to the same value as @param value.
alias(place: Place, value: Place): void { assign(place: Place, value: Place): void {
const values = this.#variables.get(value.identifier.id); const values = this.#variables.get(value.identifier.id);
CompilerError.invariant(values != null, { CompilerError.invariant(values != null, {
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`, reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
@@ -1244,9 +1270,6 @@ class InferenceState {
kind: ValueKind.Frozen, kind: ValueKind.Frozen,
reason: new Set([reason]), reason: new Set([reason]),
}); });
if (DEBUG) {
console.log(`freeze value: ${printInstructionValue(value)} ${reason}`);
}
if ( if (
value.kind === 'FunctionExpression' && value.kind === 'FunctionExpression' &&
(this.env.config.enablePreserveExistingMemoizationGuarantees || (this.env.config.enablePreserveExistingMemoizationGuarantees ||
@@ -2286,17 +2309,6 @@ function computeEffectsForSignature(
// Too many args and there is no rest param to hold them // Too many args and there is no rest param to hold them
(args.length > signature.params.length && signature.rest == null) (args.length > signature.params.length && signature.rest == null)
) { ) {
if (DEBUG) {
if (signature.params.length > args.length) {
console.log(
`not enough args: ${args.length} args for ${signature.params.length} params`,
);
} else {
console.log(
`too many args: ${args.length} args for ${signature.params.length} params, with no rest param`,
);
}
}
return null; return null;
} }
// Build substitutions // Build substitutions
@@ -2311,9 +2323,6 @@ function computeEffectsForSignature(
continue; continue;
} else if (params == null || i >= params.length || arg.kind === 'Spread') { } else if (params == null || i >= params.length || arg.kind === 'Spread') {
if (signature.rest == null) { if (signature.rest == null) {
if (DEBUG) {
console.log(`no rest value to hold param`);
}
return null; return null;
} }
const place = arg.kind === 'Identifier' ? arg : arg.place; const place = arg.kind === 'Identifier' ? arg : arg.place;
@@ -2421,23 +2430,14 @@ function computeEffectsForSignature(
case 'Apply': { case 'Apply': {
const applyReceiver = substitutions.get(effect.receiver.identifier.id); const applyReceiver = substitutions.get(effect.receiver.identifier.id);
if (applyReceiver == null || applyReceiver.length !== 1) { if (applyReceiver == null || applyReceiver.length !== 1) {
if (DEBUG) {
console.log(`too many substitutions for receiver`);
}
return null; return null;
} }
const applyFunction = substitutions.get(effect.function.identifier.id); const applyFunction = substitutions.get(effect.function.identifier.id);
if (applyFunction == null || applyFunction.length !== 1) { if (applyFunction == null || applyFunction.length !== 1) {
if (DEBUG) {
console.log(`too many substitutions for function`);
}
return null; return null;
} }
const applyInto = substitutions.get(effect.into.identifier.id); const applyInto = substitutions.get(effect.into.identifier.id);
if (applyInto == null || applyInto.length !== 1) { if (applyInto == null || applyInto.length !== 1) {
if (DEBUG) {
console.log(`too many substitutions for into`);
}
return null; return null;
} }
const applyArgs: Array<Place | SpreadPattern | Hole> = []; const applyArgs: Array<Place | SpreadPattern | Hole> = [];
@@ -2447,18 +2447,12 @@ function computeEffectsForSignature(
} else if (arg.kind === 'Identifier') { } else if (arg.kind === 'Identifier') {
const applyArg = substitutions.get(arg.identifier.id); const applyArg = substitutions.get(arg.identifier.id);
if (applyArg == null || applyArg.length !== 1) { if (applyArg == null || applyArg.length !== 1) {
if (DEBUG) {
console.log(`too many substitutions for arg`);
}
return null; return null;
} }
applyArgs.push(applyArg[0]); applyArgs.push(applyArg[0]);
} else { } else {
const applyArg = substitutions.get(arg.place.identifier.id); const applyArg = substitutions.get(arg.place.identifier.id);
if (applyArg == null || applyArg.length !== 1) { if (applyArg == null || applyArg.length !== 1) {
if (DEBUG) {
console.log(`too many substitutions for arg`);
}
return null; return null;
} }
applyArgs.push({kind: 'Spread', place: applyArg[0]}); applyArgs.push({kind: 'Spread', place: applyArg[0]});

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import prettyFormat from 'pretty-format';
import {CompilerError, SourceLocation} from '..'; import {CompilerError, SourceLocation} from '..';
import { import {
BlockId, BlockId,
@@ -23,14 +22,9 @@ import {
eachTerminalOperand, eachTerminalOperand,
} from '../HIR/visitors'; } from '../HIR/visitors';
import {assertExhaustive, getOrInsertWith} from '../Utils/utils'; import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
import {printFunction} from '../HIR';
import {printIdentifier, printPlace} from '../HIR/PrintHIR';
import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature'; import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature';
import {Result} from '../Utils/Result'; import {Result} from '../Utils/Result';
const DEBUG = false;
const VERBOSE = false;
/** /**
* Infers mutable ranges for all values in the program, using previously inferred * Infers mutable ranges for all values in the program, using previously inferred
* mutation/aliasing effects. This pass builds a data flow graph using the effects, * mutation/aliasing effects. This pass builds a data flow graph using the effects,
@@ -56,10 +50,6 @@ export function inferMutationAliasingRanges(
fn: HIRFunction, fn: HIRFunction,
{isFunctionExpression}: {isFunctionExpression: boolean}, {isFunctionExpression}: {isFunctionExpression: boolean},
): Result<void, CompilerError> { ): Result<void, CompilerError> {
if (VERBOSE) {
console.log();
console.log(printFunction(fn));
}
/** /**
* Part 1: Infer mutable ranges for values. We build an abstract model of * Part 1: Infer mutable ranges for values. We build an abstract model of
* values, the alias/capture edges between them, and the set of mutations. * values, the alias/capture edges between them, and the set of mutations.
@@ -115,20 +105,6 @@ export function inferMutationAliasingRanges(
seenBlocks.add(block.id); seenBlocks.add(block.id);
for (const instr of block.instructions) { for (const instr of block.instructions) {
if (
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
state.create(instr.lvalue, {
kind: 'Function',
function: instr.value.loweredFunc.func,
});
} else {
for (const lvalue of eachInstructionLValue(instr)) {
state.create(lvalue, {kind: 'Object'});
}
}
if (instr.effects == null) continue; if (instr.effects == null) continue;
for (const effect of instr.effects) { for (const effect of instr.effects) {
if (effect.kind === 'Create') { if (effect.kind === 'Create') {
@@ -141,6 +117,15 @@ export function inferMutationAliasingRanges(
} else if (effect.kind === 'CreateFrom') { } else if (effect.kind === 'CreateFrom') {
state.createFrom(index++, effect.from, effect.into); state.createFrom(index++, effect.from, effect.into);
} else if (effect.kind === 'Assign') { } else if (effect.kind === 'Assign') {
/**
* TODO: Invariant that the node is not initialized yet
*
* InferFunctionExpressionAliasingEffectSignatures currently infers
* Assign effects in some places that should be Alias, leading to
* Assign effects that reinitialize a value. The end result appears to
* be fine, but we should fix that inference pass so that we add the
* invariant here.
*/
if (!state.nodes.has(effect.into.identifier)) { if (!state.nodes.has(effect.into.identifier)) {
state.create(effect.into, {kind: 'Object'}); state.create(effect.into, {kind: 'Object'});
} }
@@ -216,10 +201,6 @@ export function inferMutationAliasingRanges(
} }
} }
if (VERBOSE) {
console.log(state.debug());
console.log(pretty(mutations));
}
for (const mutation of mutations) { for (const mutation of mutations) {
state.mutate( state.mutate(
mutation.index, mutation.index,
@@ -234,9 +215,6 @@ export function inferMutationAliasingRanges(
for (const render of renders) { for (const render of renders) {
state.render(render.index, render.place.identifier, errors); state.render(render.index, render.place.identifier, errors);
} }
if (DEBUG) {
console.log(pretty([...state.nodes.keys()]));
}
fn.aliasingEffects ??= []; fn.aliasingEffects ??= [];
for (const param of [...fn.context, ...fn.params]) { for (const param of [...fn.context, ...fn.params]) {
const place = param.kind === 'Identifier' ? param : param.place; const place = param.kind === 'Identifier' ? param : param.place;
@@ -458,9 +436,6 @@ export function inferMutationAliasingRanges(
} }
} }
if (VERBOSE) {
console.log(printFunction(fn));
}
return errors.asResult(); return errors.asResult();
} }
@@ -511,11 +486,6 @@ class AliasingState {
const fromNode = this.nodes.get(from.identifier); const fromNode = this.nodes.get(from.identifier);
const toNode = this.nodes.get(into.identifier); const toNode = this.nodes.get(into.identifier);
if (fromNode == null || toNode == null) { if (fromNode == null || toNode == null) {
if (VERBOSE) {
console.log(
`skip: createFrom ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
);
}
return; return;
} }
fromNode.edges.push({index, node: into.identifier, kind: 'alias'}); fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
@@ -528,11 +498,6 @@ class AliasingState {
const fromNode = this.nodes.get(from.identifier); const fromNode = this.nodes.get(from.identifier);
const toNode = this.nodes.get(into.identifier); const toNode = this.nodes.get(into.identifier);
if (fromNode == null || toNode == null) { if (fromNode == null || toNode == null) {
if (VERBOSE) {
console.log(
`skip: capture ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
);
}
return; return;
} }
fromNode.edges.push({index, node: into.identifier, kind: 'capture'}); fromNode.edges.push({index, node: into.identifier, kind: 'capture'});
@@ -545,11 +510,6 @@ class AliasingState {
const fromNode = this.nodes.get(from.identifier); const fromNode = this.nodes.get(from.identifier);
const toNode = this.nodes.get(into.identifier); const toNode = this.nodes.get(into.identifier);
if (fromNode == null || toNode == null) { if (fromNode == null || toNode == null) {
if (VERBOSE) {
console.log(
`skip: assign ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
);
}
return; return;
} }
fromNode.edges.push({index, node: into.identifier, kind: 'alias'}); fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
@@ -604,11 +564,6 @@ class AliasingState {
loc: SourceLocation, loc: SourceLocation,
errors: CompilerError, errors: CompilerError,
): void { ): void {
if (DEBUG) {
console.log(
`mutate ix=${index} start=$${start.id} end=[${end}]${transitive ? ' transitive' : ''} kind=${kind}`,
);
}
const seen = new Set<Identifier>(); const seen = new Set<Identifier>();
const queue: Array<{ const queue: Array<{
place: Identifier; place: Identifier;
@@ -623,18 +578,8 @@ class AliasingState {
seen.add(current); seen.add(current);
const node = this.nodes.get(current); const node = this.nodes.get(current);
if (node == null) { if (node == null) {
if (DEBUG) {
console.log(
`no node! ${printIdentifier(start)} for identifier ${printIdentifier(current)}`,
);
}
continue; continue;
} }
if (DEBUG) {
console.log(
` mutate $${node.id.id} transitive=${transitive} direction=${direction}`,
);
}
node.id.mutableRange.end = makeInstructionId( node.id.mutableRange.end = makeInstructionId(
Math.max(node.id.mutableRange.end, end), Math.max(node.id.mutableRange.end, end),
); );
@@ -701,37 +646,5 @@ class AliasingState {
} }
} }
} }
if (DEBUG) {
const nodes = new Map();
for (const id of seen) {
const node = this.nodes.get(id);
nodes.set(id.id, node);
}
console.log(pretty(nodes));
}
}
debug(): string {
return pretty(this.nodes);
} }
} }
export function pretty(v: any): string {
return prettyFormat(v, {
plugins: [
{
test: v =>
v !== null && typeof v === 'object' && v.kind === 'Identifier',
serialize: v => printPlace(v),
},
{
test: v =>
v !== null &&
typeof v === 'object' &&
typeof v.declarationId === 'number',
serialize: v =>
`${printIdentifier(v)}:${v.mutableRange.start}:${v.mutableRange.end}`,
},
],
});
}

View File

@@ -23,14 +23,9 @@ function Component({a, b, c}: {a: number; b: number; c: number}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />; <ValidateMemoization inputs={[a, b, c]} output={x} />;
{/* TODO: should only depend on c */} {/* TODO: should only depend on c */}
<ValidateMemoization <ValidateMemoization inputs={[a, b, c]} output={x[0]} />;
inputs={[a, b, c]}
output={x[0]}
alwaysCheck={true}
/>
;
</> </>
); );
} }
@@ -98,7 +93,7 @@ function Component(t0) {
} }
let t3; let t3;
if ($[9] !== t2 || $[10] !== x) { if ($[9] !== t2 || $[10] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />; t3 = <ValidateMemoization inputs={t2} output={x} />;
$[9] = t2; $[9] = t2;
$[10] = x; $[10] = x;
$[11] = t3; $[11] = t3;
@@ -117,7 +112,7 @@ function Component(t0) {
} }
let t5; let t5;
if ($[16] !== t4 || $[17] !== x[0]) { if ($[16] !== t4 || $[17] !== x[0]) {
t5 = <ValidateMemoization inputs={t4} output={x[0]} alwaysCheck={true} />; t5 = <ValidateMemoization inputs={t4} output={x[0]} />;
$[16] = t4; $[16] = t4;
$[17] = x[0]; $[17] = x[0];
$[18] = t5; $[18] = t5;

View File

@@ -19,14 +19,9 @@ function Component({a, b, c}: {a: number; b: number; c: number}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />; <ValidateMemoization inputs={[a, b, c]} output={x} />;
{/* TODO: should only depend on c */} {/* TODO: should only depend on c */}
<ValidateMemoization <ValidateMemoization inputs={[a, b, c]} output={x[0]} />;
inputs={[a, b, c]}
output={x[0]}
alwaysCheck={true}
/>
;
</> </>
); );
} }

View File

@@ -22,7 +22,7 @@ function Component({a, b}) {
typedMutate(z, b); typedMutate(z, b);
// TODO: this *should* only depend on `a` // TODO: this *should* only depend on `a`
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {
@@ -86,7 +86,7 @@ function Component(t0) {
} }
let t3; let t3;
if ($[7] !== t2 || $[8] !== x) { if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />; t3 = <ValidateMemoization inputs={t2} output={x} />;
$[7] = t2; $[7] = t2;
$[8] = x; $[8] = x;
$[9] = t3; $[9] = t3;

View File

@@ -18,7 +18,7 @@ function Component({a, b}) {
typedMutate(z, b); typedMutate(z, b);
// TODO: this *should* only depend on `a` // TODO: this *should* only depend on `a`
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {

View File

@@ -20,8 +20,8 @@ function Component({a, b}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />; <ValidateMemoization inputs={[a]} output={o} />;
<ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; <ValidateMemoization inputs={[a, b]} output={x} />;
</> </>
); );
} }
@@ -92,7 +92,7 @@ function Component(t0) {
} }
let t5; let t5;
if ($[8] !== o || $[9] !== t4) { if ($[8] !== o || $[9] !== t4) {
t5 = <ValidateMemoization inputs={t4} output={o} alwaysCheck={true} />; t5 = <ValidateMemoization inputs={t4} output={o} />;
$[8] = o; $[8] = o;
$[9] = t4; $[9] = t4;
$[10] = t5; $[10] = t5;
@@ -110,7 +110,7 @@ function Component(t0) {
} }
let t7; let t7;
if ($[14] !== t6 || $[15] !== x) { if ($[14] !== t6 || $[15] !== x) {
t7 = <ValidateMemoization inputs={t6} output={x} alwaysCheck={true} />; t7 = <ValidateMemoization inputs={t6} output={x} />;
$[14] = t6; $[14] = t6;
$[15] = x; $[15] = x;
$[16] = t7; $[16] = t7;

View File

@@ -16,8 +16,8 @@ function Component({a, b}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />; <ValidateMemoization inputs={[a]} output={o} />;
<ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; <ValidateMemoization inputs={[a, b]} output={x} />;
</> </>
); );
} }

View File

@@ -21,7 +21,7 @@ function Component({a, b}: {a: number; b: number}) {
// mutates x // mutates x
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {
@@ -85,7 +85,7 @@ function Component(t0) {
} }
let t3; let t3;
if ($[7] !== t2 || $[8] !== x) { if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />; t3 = <ValidateMemoization inputs={t2} output={x} />;
$[7] = t2; $[7] = t2;
$[8] = x; $[8] = x;
$[9] = t3; $[9] = t3;

View File

@@ -17,7 +17,7 @@ function Component({a, b}: {a: number; b: number}) {
// mutates x // mutates x
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {

View File

@@ -17,7 +17,7 @@ function Component({a, b}: {a: number; b: number}) {
// mutates x // mutates x
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {
@@ -76,7 +76,7 @@ function Component(t0) {
} }
let t3; let t3;
if ($[7] !== t2 || $[8] !== x) { if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />; t3 = <ValidateMemoization inputs={t2} output={x} />;
$[7] = t2; $[7] = t2;
$[8] = x; $[8] = x;
$[9] = t3; $[9] = t3;

View File

@@ -13,7 +13,7 @@ function Component({a, b}: {a: number; b: number}) {
// mutates x // mutates x
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {

View File

@@ -17,7 +17,7 @@ function Component({a, b}) {
// does not mutate x, so x should not depend on b // does not mutate x, so x should not depend on b
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {
@@ -73,7 +73,7 @@ function Component(t0) {
} }
let t4; let t4;
if ($[4] !== t3 || $[5] !== x) { if ($[4] !== t3 || $[5] !== x) {
t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />; t4 = <ValidateMemoization inputs={t3} output={x} />;
$[4] = t3; $[4] = t3;
$[5] = x; $[5] = x;
$[6] = t4; $[6] = t4;

View File

@@ -13,7 +13,7 @@ function Component({a, b}) {
// does not mutate x, so x should not depend on b // does not mutate x, so x should not depend on b
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {

View File

@@ -21,7 +21,7 @@ function Component({a, b}) {
// could mutate x // could mutate x
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {
@@ -92,7 +92,7 @@ function Component(t0) {
} }
let t4; let t4;
if ($[9] !== t3 || $[10] !== x) { if ($[9] !== t3 || $[10] !== x) {
t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />; t4 = <ValidateMemoization inputs={t3} output={x} />;
$[9] = t3; $[9] = t3;
$[10] = x; $[10] = x;
$[11] = t4; $[11] = t4;

View File

@@ -17,7 +17,7 @@ function Component({a, b}) {
// could mutate x // could mutate x
typedMutate(z, b); typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />; return <ValidateMemoization inputs={[a, b]} output={x} />;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {

View File

@@ -4,6 +4,7 @@
```javascript ```javascript
// @enableNewMutationAliasingModel // @enableNewMutationAliasingModel
import {useMemo} from 'react';
import { import {
identity, identity,
makeObject_Primitives, makeObject_Primitives,
@@ -14,7 +15,7 @@ import {
function Component({a, b}) { function Component({a, b}) {
// create a mutable value with input `a` // create a mutable value with input `a`
const x = makeObject_Primitives(a); const x = useMemo(() => makeObject_Primitives(a), [a]);
// freeze the value // freeze the value
useIdentity(x); useIdentity(x);
@@ -49,6 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript ```javascript
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
import { useMemo } from "react";
import { import {
identity, identity,
makeObject_Primitives, makeObject_Primitives,
@@ -61,13 +63,15 @@ function Component(t0) {
const $ = _c(7); const $ = _c(7);
const { a, b } = t0; const { a, b } = t0;
let t1; let t1;
let t2;
if ($[0] !== a) { if ($[0] !== a) {
t1 = makeObject_Primitives(a); t2 = makeObject_Primitives(a);
$[0] = a; $[0] = a;
$[1] = t1; $[1] = t2;
} else { } else {
t1 = $[1]; t2 = $[1];
} }
t1 = t2;
const x = t1; const x = t1;
useIdentity(x); useIdentity(x);
@@ -75,24 +79,24 @@ function Component(t0) {
const x2 = typedIdentity(x); const x2 = typedIdentity(x);
identity(x2, b); identity(x2, b);
let t2;
if ($[2] !== a) {
t2 = [a];
$[2] = a;
$[3] = t2;
} else {
t2 = $[3];
}
let t3; let t3;
if ($[4] !== t2 || $[5] !== x) { if ($[2] !== a) {
t3 = <ValidateMemoization inputs={t2} output={x} />; t3 = [a];
$[4] = t2; $[2] = a;
$[5] = x; $[3] = t3;
$[6] = t3;
} else { } else {
t3 = $[6]; t3 = $[3];
} }
return t3; let t4;
if ($[4] !== t3 || $[5] !== x) {
t4 = <ValidateMemoization inputs={t3} output={x} />;
$[4] = t3;
$[5] = x;
$[6] = t4;
} else {
t4 = $[6];
}
return t4;
} }
export const FIXTURE_ENTRYPOINT = { export const FIXTURE_ENTRYPOINT = {

View File

@@ -1,5 +1,6 @@
// @enableNewMutationAliasingModel // @enableNewMutationAliasingModel
import {useMemo} from 'react';
import { import {
identity, identity,
makeObject_Primitives, makeObject_Primitives,
@@ -10,7 +11,7 @@ import {
function Component({a, b}) { function Component({a, b}) {
// create a mutable value with input `a` // create a mutable value with input `a`
const x = makeObject_Primitives(a); const x = useMemo(() => makeObject_Primitives(a), [a]);
// freeze the value // freeze the value
useIdentity(x); useIdentity(x);

View File

@@ -2,6 +2,7 @@
## Input ## Input
```javascript ```javascript
import {useMemo} from 'react';
import {ValidateMemoization} from 'shared-runtime'; import {ValidateMemoization} from 'shared-runtime';
function Component({a, b, c}) { function Component({a, b, c}) {
@@ -13,9 +14,21 @@ function Component({a, b, c}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a, c]} output={map} /> <ValidateMemoization
<ValidateMemoization inputs={[a, c]} output={mapAlias} /> inputs={[a, c]}
<ValidateMemoization inputs={[b]} output={[hasB]} /> output={map}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[a, c]}
output={mapAlias}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[b]}
output={[hasB]}
onlyCheckCompiled={true}
/>
</> </>
); );
} }
@@ -44,6 +57,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript ```javascript
import { c as _c } from "react/compiler-runtime"; import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import { ValidateMemoization } from "shared-runtime"; import { ValidateMemoization } from "shared-runtime";
function Component(t0) { function Component(t0) {
@@ -76,7 +90,9 @@ function Component(t0) {
} }
let t2; let t2;
if ($[7] !== map || $[8] !== t1) { if ($[7] !== map || $[8] !== t1) {
t2 = <ValidateMemoization inputs={t1} output={map} />; t2 = (
<ValidateMemoization inputs={t1} output={map} onlyCheckCompiled={true} />
);
$[7] = map; $[7] = map;
$[8] = t1; $[8] = t1;
$[9] = t2; $[9] = t2;
@@ -94,7 +110,13 @@ function Component(t0) {
} }
let t4; let t4;
if ($[13] !== mapAlias || $[14] !== t3) { if ($[13] !== mapAlias || $[14] !== t3) {
t4 = <ValidateMemoization inputs={t3} output={mapAlias} />; t4 = (
<ValidateMemoization
inputs={t3}
output={mapAlias}
onlyCheckCompiled={true}
/>
);
$[13] = mapAlias; $[13] = mapAlias;
$[14] = t3; $[14] = t3;
$[15] = t4; $[15] = t4;
@@ -119,7 +141,9 @@ function Component(t0) {
} }
let t7; let t7;
if ($[20] !== t5 || $[21] !== t6) { if ($[20] !== t5 || $[21] !== t6) {
t7 = <ValidateMemoization inputs={t5} output={t6} />; t7 = (
<ValidateMemoization inputs={t5} output={t6} onlyCheckCompiled={true} />
);
$[20] = t5; $[20] = t5;
$[21] = t6; $[21] = t6;
$[22] = t7; $[22] = t7;

View File

@@ -1,3 +1,4 @@
import {useMemo} from 'react';
import {ValidateMemoization} from 'shared-runtime'; import {ValidateMemoization} from 'shared-runtime';
function Component({a, b, c}) { function Component({a, b, c}) {
@@ -9,9 +10,21 @@ function Component({a, b, c}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a, c]} output={map} /> <ValidateMemoization
<ValidateMemoization inputs={[a, c]} output={mapAlias} /> inputs={[a, c]}
<ValidateMemoization inputs={[b]} output={[hasB]} /> output={map}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[a, c]}
output={mapAlias}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[b]}
output={[hasB]}
onlyCheckCompiled={true}
/>
</> </>
); );
} }

View File

@@ -2,6 +2,7 @@
## Input ## Input
```javascript ```javascript
import {useMemo} from 'react';
import {ValidateMemoization} from 'shared-runtime'; import {ValidateMemoization} from 'shared-runtime';
function Component({a, b, c}) { function Component({a, b, c}) {
@@ -13,9 +14,21 @@ function Component({a, b, c}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a, c]} output={set} /> <ValidateMemoization
<ValidateMemoization inputs={[a, c]} output={setAlias} /> inputs={[a, c]}
<ValidateMemoization inputs={[b]} output={[hasB]} /> output={set}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[a, c]}
output={setAlias}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[b]}
output={[hasB]}
onlyCheckCompiled={true}
/>
</> </>
); );
} }
@@ -44,6 +57,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript ```javascript
import { c as _c } from "react/compiler-runtime"; import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import { ValidateMemoization } from "shared-runtime"; import { ValidateMemoization } from "shared-runtime";
function Component(t0) { function Component(t0) {
@@ -76,7 +90,9 @@ function Component(t0) {
} }
let t2; let t2;
if ($[7] !== set || $[8] !== t1) { if ($[7] !== set || $[8] !== t1) {
t2 = <ValidateMemoization inputs={t1} output={set} />; t2 = (
<ValidateMemoization inputs={t1} output={set} onlyCheckCompiled={true} />
);
$[7] = set; $[7] = set;
$[8] = t1; $[8] = t1;
$[9] = t2; $[9] = t2;
@@ -94,7 +110,13 @@ function Component(t0) {
} }
let t4; let t4;
if ($[13] !== setAlias || $[14] !== t3) { if ($[13] !== setAlias || $[14] !== t3) {
t4 = <ValidateMemoization inputs={t3} output={setAlias} />; t4 = (
<ValidateMemoization
inputs={t3}
output={setAlias}
onlyCheckCompiled={true}
/>
);
$[13] = setAlias; $[13] = setAlias;
$[14] = t3; $[14] = t3;
$[15] = t4; $[15] = t4;
@@ -119,7 +141,9 @@ function Component(t0) {
} }
let t7; let t7;
if ($[20] !== t5 || $[21] !== t6) { if ($[20] !== t5 || $[21] !== t6) {
t7 = <ValidateMemoization inputs={t5} output={t6} />; t7 = (
<ValidateMemoization inputs={t5} output={t6} onlyCheckCompiled={true} />
);
$[20] = t5; $[20] = t5;
$[21] = t6; $[21] = t6;
$[22] = t7; $[22] = t7;

View File

@@ -1,3 +1,4 @@
import {useMemo} from 'react';
import {ValidateMemoization} from 'shared-runtime'; import {ValidateMemoization} from 'shared-runtime';
function Component({a, b, c}) { function Component({a, b, c}) {
@@ -9,9 +10,21 @@ function Component({a, b, c}) {
return ( return (
<> <>
<ValidateMemoization inputs={[a, c]} output={set} /> <ValidateMemoization
<ValidateMemoization inputs={[a, c]} output={setAlias} /> inputs={[a, c]}
<ValidateMemoization inputs={[b]} output={[hasB]} /> output={set}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[a, c]}
output={setAlias}
onlyCheckCompiled={true}
/>
<ValidateMemoization
inputs={[b]}
output={[hasB]}
onlyCheckCompiled={true}
/>
</> </>
); );
} }

View File

@@ -269,12 +269,10 @@ export function ValidateMemoization({
inputs, inputs,
output: rawOutput, output: rawOutput,
onlyCheckCompiled = false, onlyCheckCompiled = false,
alwaysCheck = false,
}: { }: {
inputs: Array<any>; inputs: Array<any>;
output: any; output: any;
onlyCheckCompiled?: boolean; onlyCheckCompiled?: boolean;
alwaysCheck?: boolean;
}): React.ReactElement { }): React.ReactElement {
'use no forget'; 'use no forget';
// Wrap rawOutput as it might be a function, which useState would invoke. // Wrap rawOutput as it might be a function, which useState would invoke.
@@ -282,7 +280,7 @@ export function ValidateMemoization({
const [previousInputs, setPreviousInputs] = React.useState(inputs); const [previousInputs, setPreviousInputs] = React.useState(inputs);
const [previousOutput, setPreviousOutput] = React.useState(output); const [previousOutput, setPreviousOutput] = React.useState(output);
if ( if (
alwaysCheck || !onlyCheckCompiled ||
(onlyCheckCompiled && (onlyCheckCompiled &&
(globalThis as any).__SNAP_EVALUATOR_MODE === 'forget') (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
) { ) {