Compare commits
62 Commits
pr35044
...
gh/josephs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5747d786db | ||
|
|
3ab075a886 | ||
|
|
7e417bc0e7 | ||
|
|
4d81498a12 | ||
|
|
02f71eaa8a | ||
|
|
6dea0de38f | ||
|
|
6d9f8c240c | ||
|
|
3099d0af9f | ||
|
|
d12cde103a | ||
|
|
6b1f47a581 | ||
|
|
64cb9592a3 | ||
|
|
9497f48035 | ||
|
|
eefde44bb3 | ||
|
|
dd068ddd40 | ||
|
|
e14d35d95c | ||
|
|
39b38f1bb6 | ||
|
|
8a1de1e463 | ||
|
|
e7e669bade | ||
|
|
90428fc050 | ||
|
|
0c53d95a28 | ||
|
|
8e8caacc9e | ||
|
|
fb4d568f78 | ||
|
|
d5db0fe72d | ||
|
|
61bd21cd18 | ||
|
|
3e67c7e39f | ||
|
|
6cc26518f3 | ||
|
|
79a13a29c5 | ||
|
|
80ae4e96ae | ||
|
|
f5cd556ffa | ||
|
|
0c7e645dd3 | ||
|
|
b0b09a74a8 | ||
|
|
f40a865bc9 | ||
|
|
bfff07b450 | ||
|
|
5fc610efb3 | ||
|
|
68ab155cfe | ||
|
|
4afa39de4e | ||
|
|
275866f3fc | ||
|
|
45a889cb87 | ||
|
|
23222b4b36 | ||
|
|
98b02b93f9 | ||
|
|
52a34e4fa0 | ||
|
|
5bf3eb6085 | ||
|
|
b9367e0e3b | ||
|
|
90ed7e8228 | ||
|
|
a25a7e1873 | ||
|
|
206e7e93d8 | ||
|
|
63e29f78e3 | ||
|
|
ec552a5011 | ||
|
|
390d516127 | ||
|
|
3b83903ca1 | ||
|
|
1fec32eeeb | ||
|
|
fd0df94b1a | ||
|
|
97442d79cb | ||
|
|
7936a99a2d | ||
|
|
c3a733fe2d | ||
|
|
05e75d8055 | ||
|
|
cd2537c6a6 | ||
|
|
3d646a8993 | ||
|
|
15307de4e2 | ||
|
|
e9b19a6732 | ||
|
|
0ee6891337 | ||
|
|
18e4165a94 |
3
compiler/.gitignore
vendored
3
compiler/.gitignore
vendored
@@ -6,6 +6,9 @@
|
||||
debug/
|
||||
target/
|
||||
|
||||
nocommit*.js
|
||||
nocommit*.expect.md
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureF
|
||||
import {CompilerError} from '..';
|
||||
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
|
||||
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
|
||||
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
|
||||
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -226,6 +228,16 @@ function runWithEnvironment(
|
||||
analyseFunctions(hir);
|
||||
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
|
||||
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (mutabilityAliasingErrors.isErr()) {
|
||||
throw mutabilityAliasingErrors.unwrapErr();
|
||||
}
|
||||
}
|
||||
inferMutationAliasingRanges(hir);
|
||||
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
|
||||
|
||||
const fnEffectErrors = inferReferenceEffects(hir);
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (fnEffectErrors.length > 0) {
|
||||
|
||||
@@ -1235,6 +1235,7 @@ function lowerStatement(
|
||||
kind: 'Debugger',
|
||||
loc,
|
||||
},
|
||||
effects: null,
|
||||
loc,
|
||||
});
|
||||
return;
|
||||
@@ -1892,6 +1893,7 @@ function lowerExpression(
|
||||
place: leftValue,
|
||||
loc: exprLoc,
|
||||
},
|
||||
effects: null,
|
||||
loc: exprLoc,
|
||||
});
|
||||
builder.terminateWithContinuation(
|
||||
@@ -2827,6 +2829,7 @@ function lowerOptionalCallExpression(
|
||||
args,
|
||||
loc,
|
||||
},
|
||||
effects: null,
|
||||
loc,
|
||||
});
|
||||
} else {
|
||||
@@ -2840,6 +2843,7 @@ function lowerOptionalCallExpression(
|
||||
args,
|
||||
loc,
|
||||
},
|
||||
effects: null,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
@@ -3466,9 +3470,10 @@ function lowerValueToTemporary(
|
||||
const place: Place = buildTemporaryPlace(builder, value.loc);
|
||||
builder.push({
|
||||
id: makeInstructionId(0),
|
||||
value: value,
|
||||
loc: value.loc,
|
||||
lvalue: {...place},
|
||||
value: value,
|
||||
effects: null,
|
||||
loc: value.loc,
|
||||
});
|
||||
return place;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {Environment, ReactFunctionType} from './Environment';
|
||||
import type {HookKind} from './ObjectShape';
|
||||
import {Type, makeType} from './Types';
|
||||
import {z} from 'zod';
|
||||
import {AliasingEffect} from '../Inference/InferMutationAliasingEffects';
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -100,6 +101,7 @@ export type ReactiveInstruction = {
|
||||
id: InstructionId;
|
||||
lvalue: Place | null;
|
||||
value: ReactiveValue;
|
||||
effects?: Array<AliasingEffect> | null; // TODO make non-optional
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
@@ -300,6 +302,10 @@ export type FunctionEffect =
|
||||
places: ReadonlySet<Place>;
|
||||
effect: Effect;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| {
|
||||
kind: 'CaptureEffect';
|
||||
places: ReadonlySet<Place>;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -645,12 +651,18 @@ export type Instruction = {
|
||||
lvalue: Place;
|
||||
value: InstructionValue;
|
||||
loc: SourceLocation;
|
||||
effects: Array<AliasingEffect> | null;
|
||||
};
|
||||
|
||||
export function todoPopulateAliasingEffects(): Array<AliasingEffect> | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export type TInstruction<T extends InstructionValue> = {
|
||||
id: InstructionId;
|
||||
lvalue: Place;
|
||||
value: T;
|
||||
effects: Array<AliasingEffect> | null;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Instruction,
|
||||
Place,
|
||||
} from './HIR';
|
||||
import {markPredecessors} from './HIRBuilder';
|
||||
import {terminalFallthrough, terminalHasFallthrough} from './visitors';
|
||||
@@ -80,20 +81,22 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
suggestions: null,
|
||||
});
|
||||
const operand = Array.from(phi.operands.values())[0]!;
|
||||
const lvalue: Place = {
|
||||
kind: 'Identifier',
|
||||
identifier: phi.place.identifier,
|
||||
effect: Effect.ConditionallyMutate,
|
||||
reactive: false,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
const instr: Instruction = {
|
||||
id: predecessor.terminal.id,
|
||||
lvalue: {
|
||||
kind: 'Identifier',
|
||||
identifier: phi.place.identifier,
|
||||
effect: Effect.ConditionallyMutate,
|
||||
reactive: false,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
lvalue: {...lvalue},
|
||||
value: {
|
||||
kind: 'LoadLocal',
|
||||
place: {...operand},
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
effects: [{kind: 'Alias', from: {...operand}, into: {...lvalue}}],
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
predecessor.instructions.push(instr);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {AliasingSignature} from '../Inference/InferMutationAliasingEffects';
|
||||
import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BuiltInType,
|
||||
@@ -179,6 +180,8 @@ export type FunctionSignature = {
|
||||
impure?: boolean;
|
||||
|
||||
canonicalName?: string;
|
||||
|
||||
aliasing?: AliasingSignature | null;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -332,6 +335,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
mutableOnlyIfOperandsAreMutable: true,
|
||||
aliasing: null,
|
||||
}),
|
||||
],
|
||||
[
|
||||
|
||||
@@ -35,6 +35,10 @@ import type {
|
||||
Type,
|
||||
} from './HIR';
|
||||
import {GotoVariant, InstructionKind} from './HIR';
|
||||
import {
|
||||
AliasedPlace,
|
||||
AliasingEffect,
|
||||
} from '../Inference/InferMutationAliasingEffects';
|
||||
|
||||
export type Options = {
|
||||
indent: number;
|
||||
@@ -151,7 +155,10 @@ export function printMixedHIR(
|
||||
|
||||
export function printInstruction(instr: ReactiveInstruction): string {
|
||||
const id = `[${instr.id}]`;
|
||||
const value = printInstructionValue(instr.value);
|
||||
let value = printInstructionValue(instr.value);
|
||||
if (instr.effects != null) {
|
||||
value += `\n ${instr.effects.map(printAliasingEffect).join('\n ')}`;
|
||||
}
|
||||
|
||||
if (instr.lvalue !== null) {
|
||||
return `${id} ${printPlace(instr.lvalue)} = ${value}`;
|
||||
@@ -546,12 +553,23 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
const effects =
|
||||
instrValue.loweredFunc.func.effects
|
||||
?.map(effect => {
|
||||
if (effect.kind === 'ContextMutation') {
|
||||
return `ContextMutation places=[${[...effect.places]
|
||||
.map(place => printPlace(place))
|
||||
.join(', ')}] effect=${effect.effect}`;
|
||||
} else {
|
||||
return `GlobalMutation`;
|
||||
switch (effect.kind) {
|
||||
case 'ContextMutation': {
|
||||
return `ContextMutation places=[${[...effect.places]
|
||||
.map(place => printPlace(place))
|
||||
.join(', ')}] effect=${effect.effect}`;
|
||||
}
|
||||
case 'GlobalMutation': {
|
||||
return 'GlobalMutation';
|
||||
}
|
||||
case 'ReactMutation': {
|
||||
return 'ReactMutation';
|
||||
}
|
||||
case 'CaptureEffect': {
|
||||
return `CaptureEffect places=[${[...effect.places]
|
||||
.map(place => printPlace(place))
|
||||
.join(', ')}]`;
|
||||
}
|
||||
}
|
||||
})
|
||||
.join(', ') ?? '';
|
||||
@@ -720,7 +738,7 @@ function isMutable(range: MutableRange): boolean {
|
||||
}
|
||||
|
||||
const DEBUG_MUTABLE_RANGES = false;
|
||||
function printMutableRange(identifier: Identifier): string {
|
||||
export function printMutableRange(identifier: Identifier): string {
|
||||
if (DEBUG_MUTABLE_RANGES) {
|
||||
// if debugging, print both the identifier and scope range if they differ
|
||||
const range = identifier.mutableRange;
|
||||
@@ -922,3 +940,46 @@ function getFunctionName(
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
export function printAliasingEffect(effect: AliasingEffect): string {
|
||||
switch (effect.kind) {
|
||||
case 'Alias': {
|
||||
return `Alias ${printPlaceForAliasEffect(effect.from)} -> ${printPlaceForAliasEffect(effect.into)}`;
|
||||
}
|
||||
case 'Capture': {
|
||||
return `Capture ${printPlaceForAliasEffect(effect.from)} -> ${printPlaceForAliasEffect(effect.into)}`;
|
||||
}
|
||||
case 'Create': {
|
||||
return `Create ${printPlaceForAliasEffect(effect.into)} = ${effect.value}`;
|
||||
}
|
||||
case 'CreateFrom': {
|
||||
return `Create ${printPlaceForAliasEffect(effect.into)} = kindOf(${printPlaceForAliasEffect(effect.from)})`;
|
||||
}
|
||||
case 'Apply': {
|
||||
const params = effect.params.map(printAliasedPlace).join(', ');
|
||||
const rest = effect.rest != null ? printAliasedPlace(effect.rest) : '';
|
||||
const returns = printAliasedPlace(effect.returns);
|
||||
return `Apply ${returns} = ${printAliasedPlace(effect.function)} as ${effect.receiver} ( ${params} ${params.length !== 0 && rest !== '' ? ', ...' : ''}${rest})`;
|
||||
}
|
||||
case 'Freeze': {
|
||||
return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`;
|
||||
}
|
||||
case 'Mutate':
|
||||
case 'MutateConditionally':
|
||||
case 'MutateTransitive':
|
||||
case 'MutateTransitiveConditionally': {
|
||||
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(effect, `Unexpected kind '${(effect as any).kind}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printPlaceForAliasEffect(place: Place): string {
|
||||
return printIdentifier(place.identifier);
|
||||
}
|
||||
|
||||
function printAliasedPlace(place: AliasedPlace): string {
|
||||
return place.kind + ' ' + printPlaceForAliasEffect(place.place);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
LoweredFunction,
|
||||
Place,
|
||||
isRefOrRefValue,
|
||||
makeInstructionId,
|
||||
} from '../HIR';
|
||||
@@ -19,6 +20,12 @@ import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
||||
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
||||
import {inferMutableRanges} from './InferMutableRanges';
|
||||
import inferReferenceEffects from './InferReferenceEffects';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {Iterable_some} from '../Utils/utils';
|
||||
|
||||
export default function analyseFunctions(func: HIRFunction): void {
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
@@ -26,8 +33,8 @@ export default function analyseFunctions(func: HIRFunction): void {
|
||||
switch (instr.value.kind) {
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression': {
|
||||
lower(instr.value.loweredFunc.func);
|
||||
infer(instr.value.loweredFunc);
|
||||
const aliases = lower(instr.value.loweredFunc.func);
|
||||
infer(instr.value.loweredFunc, aliases);
|
||||
|
||||
/**
|
||||
* Reset mutable range for outer inferReferenceEffects
|
||||
@@ -44,11 +51,11 @@ export default function analyseFunctions(func: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
function lower(func: HIRFunction): void {
|
||||
function lower(func: HIRFunction): DisjointSet<Identifier> {
|
||||
analyseFunctions(func);
|
||||
inferReferenceEffects(func, {isFunctionExpression: true});
|
||||
deadCodeElimination(func);
|
||||
inferMutableRanges(func);
|
||||
const aliases = inferMutableRanges(func);
|
||||
rewriteInstructionKindsBasedOnReassignment(func);
|
||||
inferReactiveScopeVariables(func);
|
||||
func.env.logger?.debugLogIRs?.({
|
||||
@@ -56,9 +63,61 @@ function lower(func: HIRFunction): void {
|
||||
name: 'AnalyseFunction (inner)',
|
||||
value: func,
|
||||
});
|
||||
inferAliasesForCapturing(func, aliases);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function infer(loweredFunc: LoweredFunction): void {
|
||||
/**
|
||||
* The alias sets returned by InferMutableRanges() accounts only for aliases that
|
||||
* are known to mutate together. Notably this skips cases where a value is captured
|
||||
* into some other value, but neither is subsequently mutated. An example is pushing
|
||||
* a mutable value onto an array, where neither the array or value are subsequently
|
||||
* mutated.
|
||||
*
|
||||
* This function extends the aliases sets to account for such capturing, so that we
|
||||
* can detect cases where one of the values in a set is mutated later (in an outer function)
|
||||
* we can correctly infer them as mutating together.
|
||||
*/
|
||||
function inferAliasesForCapturing(
|
||||
fn: HIRFunction,
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {lvalue, value} = instr;
|
||||
const hasCapture =
|
||||
lvalue.effect === Effect.Store ||
|
||||
Iterable_some(
|
||||
eachInstructionValueOperand(value),
|
||||
operand => operand.effect === Effect.Capture,
|
||||
);
|
||||
if (!hasCapture) {
|
||||
continue;
|
||||
}
|
||||
const operands: Array<Identifier> = [];
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
operands.push(lvalue.identifier);
|
||||
}
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
if (
|
||||
operand.effect === Effect.Store ||
|
||||
operand.effect === Effect.Mutate ||
|
||||
operand.effect === Effect.Capture
|
||||
) {
|
||||
operands.push(operand.identifier);
|
||||
}
|
||||
}
|
||||
if (operands.length > 1) {
|
||||
aliases.union(operands);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function infer(
|
||||
loweredFunc: LoweredFunction,
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
for (const operand of loweredFunc.func.context) {
|
||||
const identifier = operand.identifier;
|
||||
CompilerError.invariant(operand.effect === Effect.Unknown, {
|
||||
@@ -85,6 +144,23 @@ function infer(loweredFunc: LoweredFunction): void {
|
||||
operand.effect = Effect.Read;
|
||||
}
|
||||
}
|
||||
const contextIdentifiers = new Map(
|
||||
loweredFunc.func.context.map(place => [place.identifier, place]),
|
||||
);
|
||||
for (const set of aliases.buildSets()) {
|
||||
const contextOperands: Set<Place> = new Set(
|
||||
[...set]
|
||||
.map(identifier => contextIdentifiers.get(identifier))
|
||||
.filter(place => place != null) as Array<Place>,
|
||||
);
|
||||
if (contextOperands.size !== 0) {
|
||||
loweredFunc.func.effects ??= [];
|
||||
loweredFunc.func.effects?.push({
|
||||
kind: 'CaptureEffect',
|
||||
places: contextOperands,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMutatedOrReassigned(id: Identifier): boolean {
|
||||
|
||||
@@ -197,6 +197,7 @@ function makeManualMemoizationMarkers(
|
||||
deps: depsList,
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
effects: null,
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
{
|
||||
@@ -208,6 +209,7 @@ function makeManualMemoizationMarkers(
|
||||
decl: {...memoDecl},
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
effects: null,
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -60,6 +60,10 @@ function inferInstr(
|
||||
alias = instrValue.value;
|
||||
break;
|
||||
}
|
||||
case 'IteratorNext': {
|
||||
alias = instrValue.collection;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {HIRFunction, Identifier} from '../HIR/HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
export function inferAliasForFunctionCaptureEffects(
|
||||
func: HIRFunction,
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
const {value} = instr;
|
||||
if (
|
||||
value.kind !== 'FunctionExpression' &&
|
||||
value.kind !== 'ObjectMethod'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const loweredFunction = value.loweredFunc.func;
|
||||
for (const effect of loweredFunction.effects ?? []) {
|
||||
if (effect.kind !== 'CaptureEffect') {
|
||||
continue;
|
||||
}
|
||||
aliases.union([...effect.places].map(place => place.identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
isSetStateType,
|
||||
isFireFunctionType,
|
||||
makeScopeId,
|
||||
todoPopulateAliasingEffects,
|
||||
} from '../HIR';
|
||||
import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
|
||||
import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
|
||||
@@ -236,9 +237,10 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
|
||||
newInstructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...depsPlace, effect: Effect.Mutate},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
value: deps,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
|
||||
// Step 2: push the inferred deps array as an argument of the useEffect
|
||||
@@ -249,9 +251,10 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
// Global functions have no reactive dependencies, so we can insert an empty array
|
||||
newInstructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...depsPlace, effect: Effect.Mutate},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
value: deps,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
value.args.push({...depsPlace, effect: Effect.Freeze});
|
||||
rewriteInstrs.set(instr.id, newInstructions);
|
||||
@@ -316,21 +319,25 @@ function writeDependencyToInstructions(
|
||||
const instructions: Array<Instruction> = [];
|
||||
let currValue = createTemporaryPlace(env, GeneratedSource);
|
||||
currValue.reactive = reactive;
|
||||
const dependencyPlace: Place = {
|
||||
kind: 'Identifier',
|
||||
identifier: dep.identifier,
|
||||
effect: Effect.Capture,
|
||||
reactive,
|
||||
loc: loc,
|
||||
};
|
||||
instructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: {...currValue, effect: Effect.Mutate},
|
||||
value: {
|
||||
kind: 'LoadLocal',
|
||||
place: {
|
||||
kind: 'Identifier',
|
||||
identifier: dep.identifier,
|
||||
effect: Effect.Capture,
|
||||
reactive,
|
||||
loc: loc,
|
||||
},
|
||||
place: {...dependencyPlace},
|
||||
loc: loc,
|
||||
},
|
||||
effects: [
|
||||
{kind: 'Alias', from: {...dependencyPlace}, into: {...currValue}},
|
||||
],
|
||||
});
|
||||
for (const path of dep.path) {
|
||||
if (path.optional) {
|
||||
@@ -359,6 +366,7 @@ function writeDependencyToInstructions(
|
||||
property: path.property,
|
||||
loc: loc,
|
||||
},
|
||||
effects: [{kind: 'Capture', from: {...currValue}, into: {...nextValue}}],
|
||||
});
|
||||
currValue = nextValue;
|
||||
}
|
||||
|
||||
@@ -95,45 +95,58 @@ function inheritFunctionEffects(
|
||||
|
||||
return effects
|
||||
.flatMap(effect => {
|
||||
if (effect.kind === 'GlobalMutation' || effect.kind === 'ReactMutation') {
|
||||
return [effect];
|
||||
} else {
|
||||
const effects: Array<FunctionEffect | null> = [];
|
||||
CompilerError.invariant(effect.kind === 'ContextMutation', {
|
||||
reason: 'Expected ContextMutation',
|
||||
loc: null,
|
||||
});
|
||||
/**
|
||||
* Contextual effects need to be replayed against the current inference
|
||||
* state, which may know more about the value to which the effect applied.
|
||||
* The main cases are:
|
||||
* 1. The mutated context value is _still_ a context value in the current scope,
|
||||
* so we have to continue propagating the original context mutation.
|
||||
* 2. The mutated context value is a mutable value in the current scope,
|
||||
* so the context mutation was fine and we can skip propagating the effect.
|
||||
* 3. The mutated context value is an immutable value in the current scope,
|
||||
* resulting in a non-ContextMutation FunctionEffect. We propagate that new,
|
||||
* more detailed effect to the current function context.
|
||||
*/
|
||||
for (const place of effect.places) {
|
||||
if (state.isDefined(place)) {
|
||||
const replayedEffect = inferOperandEffect(state, {
|
||||
...place,
|
||||
loc: effect.loc,
|
||||
effect: effect.effect,
|
||||
});
|
||||
if (replayedEffect != null) {
|
||||
if (replayedEffect.kind === 'ContextMutation') {
|
||||
// Case 1, still a context value so propagate the original effect
|
||||
effects.push(effect);
|
||||
} else {
|
||||
// Case 3, immutable value so propagate the more precise effect
|
||||
effects.push(replayedEffect);
|
||||
}
|
||||
} // else case 2, local mutable value so this effect was fine
|
||||
}
|
||||
switch (effect.kind) {
|
||||
case 'GlobalMutation':
|
||||
case 'ReactMutation': {
|
||||
return [effect];
|
||||
}
|
||||
case 'ContextMutation': {
|
||||
const effects: Array<FunctionEffect | null> = [];
|
||||
CompilerError.invariant(effect.kind === 'ContextMutation', {
|
||||
reason: 'Expected ContextMutation',
|
||||
loc: null,
|
||||
});
|
||||
/**
|
||||
* Contextual effects need to be replayed against the current inference
|
||||
* state, which may know more about the value to which the effect applied.
|
||||
* The main cases are:
|
||||
* 1. The mutated context value is _still_ a context value in the current scope,
|
||||
* so we have to continue propagating the original context mutation.
|
||||
* 2. The mutated context value is a mutable value in the current scope,
|
||||
* so the context mutation was fine and we can skip propagating the effect.
|
||||
* 3. The mutated context value is an immutable value in the current scope,
|
||||
* resulting in a non-ContextMutation FunctionEffect. We propagate that new,
|
||||
* more detailed effect to the current function context.
|
||||
*/
|
||||
for (const place of effect.places) {
|
||||
if (state.isDefined(place)) {
|
||||
const replayedEffect = inferOperandEffect(state, {
|
||||
...place,
|
||||
loc: effect.loc,
|
||||
effect: effect.effect,
|
||||
});
|
||||
if (replayedEffect != null) {
|
||||
if (replayedEffect.kind === 'ContextMutation') {
|
||||
// Case 1, still a context value so propagate the original effect
|
||||
effects.push(effect);
|
||||
} else {
|
||||
// Case 3, immutable value so propagate the more precise effect
|
||||
effects.push(replayedEffect);
|
||||
}
|
||||
} // else case 2, local mutable value so this effect was fine
|
||||
}
|
||||
}
|
||||
return effects;
|
||||
}
|
||||
case 'CaptureEffect': {
|
||||
return [];
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
effect,
|
||||
`Unexpected effect kind '${(effect as any).kind}'`,
|
||||
);
|
||||
}
|
||||
return effects;
|
||||
}
|
||||
})
|
||||
.filter((effect): effect is FunctionEffect => effect != null);
|
||||
@@ -298,26 +311,31 @@ export function inferTerminalFunctionEffects(
|
||||
export function transformFunctionEffectErrors(
|
||||
functionEffects: Array<FunctionEffect>,
|
||||
): Array<CompilerErrorDetailOptions> {
|
||||
return functionEffects.map(eff => {
|
||||
switch (eff.kind) {
|
||||
case 'ReactMutation':
|
||||
case 'GlobalMutation': {
|
||||
return eff.error;
|
||||
return functionEffects
|
||||
.map(eff => {
|
||||
switch (eff.kind) {
|
||||
case 'ReactMutation':
|
||||
case 'GlobalMutation': {
|
||||
return eff.error;
|
||||
}
|
||||
case 'ContextMutation': {
|
||||
return {
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: `Unexpected ContextMutation in top-level function effects`,
|
||||
loc: eff.loc,
|
||||
};
|
||||
}
|
||||
case 'CaptureEffect': {
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
assertExhaustive(
|
||||
eff,
|
||||
`Unexpected function effect kind \`${(eff as any).kind}\``,
|
||||
);
|
||||
}
|
||||
case 'ContextMutation': {
|
||||
return {
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: `Unexpected ContextMutation in top-level function effects`,
|
||||
loc: eff.loc,
|
||||
};
|
||||
}
|
||||
default:
|
||||
assertExhaustive(
|
||||
eff,
|
||||
`Unexpected function effect kind \`${(eff as any).kind}\``,
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
.filter(eff => eff != null) as Array<CompilerErrorDetailOptions>;
|
||||
}
|
||||
|
||||
function isEffectSafeOutsideRender(effect: FunctionEffect): boolean {
|
||||
|
||||
@@ -5,16 +5,21 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {HIRFunction, Identifier} from '../HIR/HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {inferAliasForUncalledFunctions} from './InerAliasForUncalledFunctions';
|
||||
import {inferAliases} from './InferAlias';
|
||||
import {inferAliasForFunctionCaptureEffects} from './InferAliasesForFunctionCaptureEffects';
|
||||
import {inferAliasForPhis} from './InferAliasForPhis';
|
||||
import {inferAliasForStores} from './InferAliasForStores';
|
||||
import {inferMutableLifetimes} from './InferMutableLifetimes';
|
||||
import {inferMutableRangesForAlias} from './InferMutableRangesForAlias';
|
||||
import {inferMutableRangesForComutation} from './InferMutableRangesForComutation';
|
||||
import {inferTryCatchAliases} from './InferTryCatchAliases';
|
||||
import {printIdentifier, printMutableRange} from '../HIR/PrintHIR';
|
||||
|
||||
export function inferMutableRanges(ir: HIRFunction): void {
|
||||
export function inferMutableRanges(ir: HIRFunction): DisjointSet<Identifier> {
|
||||
// Infer mutable ranges for non fields
|
||||
inferMutableLifetimes(ir, false);
|
||||
|
||||
@@ -30,18 +35,22 @@ export function inferMutableRanges(ir: HIRFunction): void {
|
||||
* Eagerly canonicalize so that if nothing changes we can bail out
|
||||
* after a single iteration
|
||||
*/
|
||||
let prevAliases: Map<Identifier, Identifier> = aliases.canonicalize();
|
||||
let prevAliases: Map<Identifier, string> = canonicalize(aliases);
|
||||
while (true) {
|
||||
// Infer mutable ranges for aliases that are not fields
|
||||
inferMutableRangesForAlias(ir, aliases);
|
||||
|
||||
inferMutableRangesForComutation(ir);
|
||||
|
||||
// Update aliasing information of fields
|
||||
inferAliasForStores(ir, aliases);
|
||||
|
||||
inferAliasForFunctionCaptureEffects(ir, aliases);
|
||||
|
||||
// Update aliasing information of phis
|
||||
inferAliasForPhis(ir, aliases);
|
||||
|
||||
const nextAliases = aliases.canonicalize();
|
||||
const nextAliases = canonicalize(aliases);
|
||||
if (areEqualMaps(prevAliases, nextAliases)) {
|
||||
break;
|
||||
}
|
||||
@@ -73,20 +82,57 @@ export function inferMutableRanges(ir: HIRFunction): void {
|
||||
* but does not modify values that `y` "contains" such as the
|
||||
* object literal or `z`.
|
||||
*/
|
||||
prevAliases = aliases.canonicalize();
|
||||
prevAliases = canonicalize(aliases);
|
||||
while (true) {
|
||||
inferMutableRangesForAlias(ir, aliases);
|
||||
inferMutableRangesForComutation(ir);
|
||||
inferAliasForPhis(ir, aliases);
|
||||
inferAliasForUncalledFunctions(ir, aliases);
|
||||
const nextAliases = aliases.canonicalize();
|
||||
const nextAliases = canonicalize(aliases);
|
||||
if (areEqualMaps(prevAliases, nextAliases)) {
|
||||
break;
|
||||
}
|
||||
prevAliases = nextAliases;
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function areEqualMaps<T>(a: Map<T, T>, b: Map<T, T>): boolean {
|
||||
export function debugAliases(aliases: DisjointSet<Identifier>): void {
|
||||
console.log(
|
||||
prettyFormat(
|
||||
aliases
|
||||
.buildSets()
|
||||
.map(set =>
|
||||
[...set].map(
|
||||
ident => printIdentifier(ident) + printMutableRange(ident),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonicalizes the alias set and mutable range information calculated at the current time.
|
||||
* The returned value maps each identifier in the program to the root identifier of its alias
|
||||
* set and the the mutable range of that set.
|
||||
*
|
||||
* This ensures that we fixpoint the mutable ranges themselves and not just the alias sets.
|
||||
*/
|
||||
export function canonicalize(
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): Map<Identifier, string> {
|
||||
const entries = new Map<Identifier, string>();
|
||||
aliases.forEach((item, root) => {
|
||||
entries.set(
|
||||
item,
|
||||
`${root.id}:${root.mutableRange.start}:${root.mutableRange.end}`,
|
||||
);
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function areEqualMaps<T, U>(a: Map<T, U>, b: Map<T, U>): boolean {
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
isRefOrRefValue,
|
||||
makeInstructionId,
|
||||
} from '../HIR';
|
||||
import {eachInstructionOperand} from '../HIR/visitors';
|
||||
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
|
||||
/**
|
||||
* Finds instructions with operands that co-mutate and extends all their mutable ranges
|
||||
* to end at the same point (the highest `end` value of the group). Note that the
|
||||
* alias sets used in InferMutableRanges are meant for values that strictly alias:
|
||||
* a mutation of one value in the set would directly modify the same object as some
|
||||
* other value in the set.
|
||||
*
|
||||
* However, co-mutation can cause an alias to one object to be stored within another object,
|
||||
* for example:
|
||||
*
|
||||
* ```
|
||||
* const a = {};
|
||||
* const b = {};
|
||||
* const f = () => b.c; //
|
||||
* setProperty(a, 'b', b); // equiv to a.b = b
|
||||
*
|
||||
* a.b.c = 'c'; // this mutates b!
|
||||
* ```
|
||||
*
|
||||
* Here, the co-mutation in `setProperty(a, 'b', b)` means that a reference to b may be stored
|
||||
* in a, vice-versa, or both. We need to extend the mutable range of both a and b to reflect
|
||||
* the fact the values may mutate together.
|
||||
*
|
||||
* Previously this was implemented in InferReactiveScopeVariables, but that is too late:
|
||||
* we need this to be part of the InferMutableRanges fixpoint iteration to account for functions
|
||||
* like `f` in the example, which capture a reference to a value that may change later. `f`
|
||||
* cannot be independently memoized from the `setProperty()` call due to the co-mutation.
|
||||
*
|
||||
* See aliased-capture-mutate and aliased-capture-aliased-mutate fixtures for examples.
|
||||
*/
|
||||
export function inferMutableRangesForComutation(fn: HIRFunction): void {
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
let operands: Array<Identifier> | null = null;
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
if (
|
||||
isMutable(instr, operand) &&
|
||||
operand.identifier.mutableRange.start > 0
|
||||
) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
if (operand.identifier.type.kind === 'Primitive') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
operands ??= [];
|
||||
operands.push(operand.identifier);
|
||||
}
|
||||
}
|
||||
if (operands != null) {
|
||||
// Find the last instruction which mutates any of the mutable operands
|
||||
let lastMutatingInstructionId = makeInstructionId(0);
|
||||
for (const id of operands) {
|
||||
if (id.mutableRange.end > lastMutatingInstructionId) {
|
||||
lastMutatingInstructionId = id.mutableRange.end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all mutable operands's mutable ranges to end at the same point
|
||||
*/
|
||||
for (const id of operands) {
|
||||
if (
|
||||
id.mutableRange.end < lastMutatingInstructionId &&
|
||||
!isRefOrRefValue(id)
|
||||
) {
|
||||
id.mutableRange.end = lastMutatingInstructionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {HIRFunction, Identifier, makeInstructionId} from '../HIR/HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {inferMutableRangesForAlias} from './InferMutableRangesForAlias';
|
||||
|
||||
export function inferMutationAliasingRanges(fn: HIRFunction): void {
|
||||
/**
|
||||
* Part 1
|
||||
* Infer ranges for transitive mutations, which includes mutations that affect
|
||||
* captured references and not just direct aliases. We build a distjoing set
|
||||
* that tracks capturing and direct aliasing, and look at transitive mutations
|
||||
* only.
|
||||
*/
|
||||
const captures = new DisjointSet<Identifier>();
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const phi of block.phis) {
|
||||
captures.union([
|
||||
phi.place.identifier,
|
||||
...[...phi.operands.values()].map(place => place.identifier),
|
||||
]);
|
||||
}
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
if (instr.effects == null) continue;
|
||||
for (const effect of instr.effects) {
|
||||
if (
|
||||
effect.kind === 'Alias' ||
|
||||
effect.kind === 'CreateFrom' ||
|
||||
effect.kind === 'Capture'
|
||||
) {
|
||||
captures.union([effect.from.identifier, effect.into.identifier]);
|
||||
} else if (effect.kind === 'MutateTransitive') {
|
||||
const value = effect.value;
|
||||
value.identifier.mutableRange.end = makeInstructionId(instr.id + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inferMutableRangesForAlias(fn, captures);
|
||||
|
||||
/**
|
||||
* Part 2
|
||||
* Infer ranges for local (non-transitive) mutations. We build a disjoint set
|
||||
* that only tracks direct value aliasing, and look only at local mutations
|
||||
* to extend ranges
|
||||
*/
|
||||
const aliases = new DisjointSet<Identifier>();
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const phi of block.phis) {
|
||||
aliases.union([
|
||||
phi.place.identifier,
|
||||
...[...phi.operands.values()].map(place => place.identifier),
|
||||
]);
|
||||
}
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
if (instr.effects == null) continue;
|
||||
for (const effect of instr.effects) {
|
||||
if (effect.kind === 'Alias' || effect.kind === 'CreateFrom') {
|
||||
aliases.union([effect.from.identifier, effect.into.identifier]);
|
||||
} else if (effect.kind === 'Mutate') {
|
||||
const value = effect.value;
|
||||
value.identifier.mutableRange.end = makeInstructionId(instr.id + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inferMutableRangesForAlias(fn, aliases);
|
||||
}
|
||||
@@ -31,13 +31,13 @@ import {
|
||||
isArrayType,
|
||||
isMapType,
|
||||
isMutableEffect,
|
||||
isObjectType,
|
||||
isSetType,
|
||||
isObjectType,
|
||||
} from '../HIR/HIR';
|
||||
import {FunctionSignature} from '../HIR/ObjectShape';
|
||||
import {
|
||||
printIdentifier,
|
||||
printMixedHIR,
|
||||
printInstructionValue,
|
||||
printPlace,
|
||||
printSourceLocation,
|
||||
} from '../HIR/PrintHIR';
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
eachTerminalOperand,
|
||||
eachTerminalSuccessor,
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {assertExhaustive, retainWhere, Set_isSuperset} from '../Utils/utils';
|
||||
import {
|
||||
inferTerminalFunctionEffects,
|
||||
inferInstructionFunctionEffects,
|
||||
@@ -521,7 +521,7 @@ class InferenceState {
|
||||
* `expected valueKind to be 'Mutable' but found to be \`${valueKind}\``
|
||||
* );
|
||||
*/
|
||||
effect = isObjectType(place.identifier) ? Effect.Store : Effect.Mutate;
|
||||
effect = Effect.Store;
|
||||
break;
|
||||
}
|
||||
case Effect.Capture: {
|
||||
@@ -669,7 +669,10 @@ class InferenceState {
|
||||
}
|
||||
for (const [value, kind] of this.#values) {
|
||||
const id = identify(value);
|
||||
result.values[id] = {kind, value: printMixedHIR(value)};
|
||||
result.values[id] = {
|
||||
abstract: this.debugAbstractValue(kind),
|
||||
value: printInstructionValue(value),
|
||||
};
|
||||
}
|
||||
for (const [variable, values] of this.#variables) {
|
||||
result.variables[`$${variable}`] = [...values].map(identify);
|
||||
@@ -677,6 +680,14 @@ class InferenceState {
|
||||
return result;
|
||||
}
|
||||
|
||||
debugAbstractValue(value: AbstractValue): any {
|
||||
return {
|
||||
kind: value.kind,
|
||||
context: [...value.context].map(printPlace),
|
||||
reason: [...value.reason],
|
||||
};
|
||||
}
|
||||
|
||||
inferPhi(phi: Phi): void {
|
||||
const values: Set<InstructionValue> = new Set();
|
||||
for (const [_, operand] of phi.operands) {
|
||||
@@ -779,7 +790,7 @@ function inferParam(
|
||||
* │ Mutable │───┘
|
||||
* └──────────────────────────┘
|
||||
*/
|
||||
function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
|
||||
export function mergeValueKinds(a: ValueKind, b: ValueKind): ValueKind {
|
||||
if (a === b) {
|
||||
return a;
|
||||
} else if (a === ValueKind.MaybeFrozen || b === ValueKind.MaybeFrozen) {
|
||||
@@ -821,28 +832,16 @@ function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if `a` is a superset of `b`.
|
||||
*/
|
||||
function isSuperset<T>(a: ReadonlySet<T>, b: ReadonlySet<T>): boolean {
|
||||
for (const v of b) {
|
||||
if (!a.has(v)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function mergeAbstractValues(
|
||||
a: AbstractValue,
|
||||
b: AbstractValue,
|
||||
): AbstractValue {
|
||||
const kind = mergeValues(a.kind, b.kind);
|
||||
const kind = mergeValueKinds(a.kind, b.kind);
|
||||
if (
|
||||
kind === a.kind &&
|
||||
kind === b.kind &&
|
||||
isSuperset(a.reason, b.reason) &&
|
||||
isSuperset(a.context, b.context)
|
||||
Set_isSuperset(a.reason, b.reason) &&
|
||||
Set_isSuperset(a.context, b.context)
|
||||
) {
|
||||
return a;
|
||||
}
|
||||
@@ -902,19 +901,11 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'ArrayExpression': {
|
||||
const contextRefOperands = getContextRefOperand(state, instrValue);
|
||||
const valueKind: AbstractValue =
|
||||
contextRefOperands.length > 0
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(contextRefOperands),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
|
||||
for (const element of instrValue.elements) {
|
||||
if (element.kind === 'Spread') {
|
||||
@@ -935,6 +926,7 @@ function inferBlock(
|
||||
let _: 'Hole' = element.kind;
|
||||
}
|
||||
}
|
||||
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
@@ -954,19 +946,11 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'ObjectExpression': {
|
||||
const contextRefOperands = getContextRefOperand(state, instrValue);
|
||||
const valueKind: AbstractValue =
|
||||
contextRefOperands.length > 0
|
||||
? {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(contextRefOperands),
|
||||
}
|
||||
: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
};
|
||||
|
||||
for (const property of instrValue.properties) {
|
||||
switch (property.kind) {
|
||||
@@ -1190,6 +1174,35 @@ function inferBlock(
|
||||
);
|
||||
hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Filter CaptureEffects to remove values that are immutable and don't
|
||||
* need to be tracked for aliasing
|
||||
*/
|
||||
const effects = instrValue.loweredFunc.func.effects;
|
||||
if (effects != null && effects.length !== 0) {
|
||||
retainWhere(effects, effect => {
|
||||
if (effect.kind !== 'CaptureEffect') {
|
||||
return true;
|
||||
}
|
||||
const places: Set<Place> = new Set();
|
||||
for (const place of effect.places) {
|
||||
const kind = state.kind(place);
|
||||
if (
|
||||
kind.kind === ValueKind.Context ||
|
||||
kind.kind === ValueKind.MaybeFrozen ||
|
||||
kind.kind === ValueKind.Mutable
|
||||
) {
|
||||
places.add(place);
|
||||
}
|
||||
}
|
||||
if (places.size === 0) {
|
||||
return false;
|
||||
}
|
||||
effect.places = places;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
/*
|
||||
* If a closure did not capture any mutable values, then we can consider it to be
|
||||
* frozen, which allows it to be independently memoized.
|
||||
@@ -1280,20 +1293,18 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'PropertyStore': {
|
||||
const effect =
|
||||
state.kind(instrValue.object).kind === ValueKind.Context
|
||||
? Effect.ConditionallyMutate
|
||||
: Effect.Capture;
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.value,
|
||||
effect,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.object,
|
||||
Effect.Store,
|
||||
isObjectType(instrValue.object.identifier)
|
||||
? Effect.Store
|
||||
: Effect.Mutate,
|
||||
ValueReason.Other,
|
||||
);
|
||||
|
||||
@@ -1320,25 +1331,21 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.object,
|
||||
Effect.Read,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
lvalue.effect = Effect.Store;
|
||||
state.initialize(instrValue, state.kind(instrValue.object));
|
||||
state.define(lvalue, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
break;
|
||||
}
|
||||
case 'ComputedStore': {
|
||||
const effect =
|
||||
state.kind(instrValue.object).kind === ValueKind.Context
|
||||
? Effect.ConditionallyMutate
|
||||
: Effect.Capture;
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.value,
|
||||
effect,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.referenceAndRecordEffects(
|
||||
@@ -1350,7 +1357,9 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.object,
|
||||
Effect.Store,
|
||||
isObjectType(instrValue.object.identifier)
|
||||
? Effect.Store
|
||||
: Effect.Mutate,
|
||||
ValueReason.Other,
|
||||
);
|
||||
|
||||
@@ -1387,7 +1396,7 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.object,
|
||||
Effect.Read,
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.referenceAndRecordEffects(
|
||||
@@ -1397,7 +1406,7 @@ function inferBlock(
|
||||
ValueReason.Other,
|
||||
);
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
lvalue.effect = Effect.Store;
|
||||
state.initialize(instrValue, state.kind(instrValue.object));
|
||||
state.define(lvalue, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
@@ -1811,7 +1820,9 @@ function inferBlock(
|
||||
state.isDefined(operand) &&
|
||||
((operand.identifier.type.kind === 'Function' &&
|
||||
state.isFunctionExpression) ||
|
||||
state.kind(operand).kind === ValueKind.Context)
|
||||
state.kind(operand).kind === ValueKind.Context ||
|
||||
(state.kind(operand).kind === ValueKind.Mutable &&
|
||||
state.isFunctionExpression))
|
||||
) {
|
||||
/**
|
||||
* Returned values should only be typed as 'frozen' if they are both (1)
|
||||
@@ -1838,22 +1849,6 @@ function inferBlock(
|
||||
);
|
||||
}
|
||||
|
||||
function getContextRefOperand(
|
||||
state: InferenceState,
|
||||
instrValue: InstructionValue,
|
||||
): Array<Place> {
|
||||
const result = [];
|
||||
for (const place of eachInstructionValueOperand(instrValue)) {
|
||||
if (
|
||||
state.isDefined(place) &&
|
||||
state.kind(place).kind === ValueKind.Context
|
||||
) {
|
||||
result.push(place);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getFunctionCallSignature(
|
||||
env: Environment,
|
||||
type: Type,
|
||||
|
||||
@@ -235,6 +235,7 @@ function rewriteBlock(
|
||||
type: null,
|
||||
loc: terminal.loc,
|
||||
},
|
||||
effects: null,
|
||||
});
|
||||
block.terminal = {
|
||||
kind: 'goto',
|
||||
@@ -263,5 +264,6 @@ function declareTemporary(
|
||||
type: null,
|
||||
loc: result.loc,
|
||||
},
|
||||
effects: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Place,
|
||||
promoteTemporary,
|
||||
SpreadPattern,
|
||||
todoPopulateAliasingEffects,
|
||||
} from '../HIR';
|
||||
import {
|
||||
createTemporaryPlace,
|
||||
@@ -151,6 +152,7 @@ export function inlineJsxTransform(
|
||||
type: null,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
currentBlockInstructions.push(varInstruction);
|
||||
@@ -167,6 +169,7 @@ export function inlineJsxTransform(
|
||||
},
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
currentBlockInstructions.push(devGlobalInstruction);
|
||||
@@ -220,6 +223,7 @@ export function inlineJsxTransform(
|
||||
type: null,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
thenBlockInstructions.push(reassignElseInstruction);
|
||||
@@ -292,6 +296,7 @@ export function inlineJsxTransform(
|
||||
],
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
elseBlockInstructions.push(reactElementInstruction);
|
||||
@@ -309,6 +314,7 @@ export function inlineJsxTransform(
|
||||
type: null,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
elseBlockInstructions.push(reassignConditionalInstruction);
|
||||
@@ -436,6 +442,7 @@ function createSymbolProperty(
|
||||
binding: {kind: 'Global', name: 'Symbol'},
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
nextInstructions.push(symbolInstruction);
|
||||
@@ -450,6 +457,7 @@ function createSymbolProperty(
|
||||
property: makePropertyLiteral('for'),
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
nextInstructions.push(symbolForInstruction);
|
||||
@@ -463,6 +471,7 @@ function createSymbolProperty(
|
||||
value: symbolName,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
nextInstructions.push(symbolValueInstruction);
|
||||
@@ -478,6 +487,7 @@ function createSymbolProperty(
|
||||
args: [symbolValueInstruction.lvalue],
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
const $$typeofProperty: ObjectProperty = {
|
||||
@@ -508,6 +518,7 @@ function createTagProperty(
|
||||
value: componentTag.name,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
tagProperty = {
|
||||
@@ -634,6 +645,7 @@ function createPropsProperties(
|
||||
elements: [...children],
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
nextInstructions.push(childrenPropInstruction);
|
||||
@@ -657,6 +669,7 @@ function createPropsProperties(
|
||||
value: null,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
refProperty = {
|
||||
@@ -678,6 +691,7 @@ function createPropsProperties(
|
||||
value: null,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
keyProperty = {
|
||||
@@ -711,6 +725,7 @@ function createPropsProperties(
|
||||
properties: props,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: instr.loc,
|
||||
};
|
||||
propsProperty = {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
markInstructionIds,
|
||||
promoteTemporary,
|
||||
reversePostorderBlocks,
|
||||
todoPopulateAliasingEffects,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace} from '../HIR/HIRBuilder';
|
||||
import {enterSSA} from '../SSA';
|
||||
@@ -146,6 +147,7 @@ function emitLoadLoweredContextCallee(
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
lvalue: createTemporaryPlace(env, GeneratedSource),
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
value: loadGlobal,
|
||||
};
|
||||
}
|
||||
@@ -192,6 +194,7 @@ function emitPropertyLoad(
|
||||
lvalue: object,
|
||||
value: loadObj,
|
||||
id: makeInstructionId(0),
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
|
||||
@@ -206,6 +209,7 @@ function emitPropertyLoad(
|
||||
lvalue: element,
|
||||
value: loadProp,
|
||||
id: makeInstructionId(0),
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return {
|
||||
@@ -278,6 +282,7 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
lvalue: createTemporaryPlace(env, GeneratedSource),
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return fnInstr;
|
||||
@@ -294,6 +299,7 @@ function emitArrayInstr(elements: Array<Place>, env: Environment): Instruction {
|
||||
id: makeInstructionId(0),
|
||||
value: array,
|
||||
lvalue: arrayLvalue,
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return arrayInstr;
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Place,
|
||||
promoteTemporary,
|
||||
promoteTemporaryJsxTag,
|
||||
todoPopulateAliasingEffects,
|
||||
} from '../HIR/HIR';
|
||||
import {createTemporaryPlace} from '../HIR/HIRBuilder';
|
||||
import {printIdentifier} from '../HIR/PrintHIR';
|
||||
@@ -297,6 +298,7 @@ function emitOutlinedJsx(
|
||||
},
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
effects: null,
|
||||
};
|
||||
promoteTemporaryJsxTag(loadJsx.lvalue.identifier);
|
||||
const jsxExpr: Instruction = {
|
||||
@@ -312,6 +314,7 @@ function emitOutlinedJsx(
|
||||
openingLoc: GeneratedSource,
|
||||
closingLoc: GeneratedSource,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
};
|
||||
|
||||
return [loadJsx, jsxExpr];
|
||||
@@ -517,6 +520,7 @@ function emitDestructureProps(
|
||||
loc: GeneratedSource,
|
||||
value: propsObj,
|
||||
},
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
};
|
||||
return destructurePropsInstr;
|
||||
}
|
||||
|
||||
@@ -1183,7 +1183,7 @@ function codegenTerminal(
|
||||
? codegenPlaceToExpression(cx, case_.test)
|
||||
: null;
|
||||
const block = codegenBlock(cx, case_.block!);
|
||||
return t.switchCase(test, [block]);
|
||||
return t.switchCase(test, block.body.length === 0 ? [] : [block]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
NonLocalImportSpecifier,
|
||||
Place,
|
||||
promoteTemporary,
|
||||
todoPopulateAliasingEffects,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
@@ -436,6 +437,7 @@ function makeLoadUseFireInstruction(
|
||||
value: instrValue,
|
||||
lvalue: {...useFirePlace},
|
||||
loc: GeneratedSource,
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -460,6 +462,7 @@ function makeLoadFireCalleeInstruction(
|
||||
},
|
||||
lvalue: {...loadedFireCallee},
|
||||
loc: GeneratedSource,
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -483,6 +486,7 @@ function makeCallUseFireInstruction(
|
||||
value: useFireCall,
|
||||
lvalue: {...useFireCallResultPlace},
|
||||
loc: GeneratedSource,
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -511,6 +515,7 @@ function makeStoreUseFireInstruction(
|
||||
},
|
||||
lvalue: fireFunctionBindingLValuePlace,
|
||||
loc: GeneratedSource,
|
||||
effects: todoPopulateAliasingEffects(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,21 @@ export function Set_intersect<T>(sets: Array<ReadonlySet<T>>): Set<T> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if `a` is a superset of `b`.
|
||||
*/
|
||||
export function Set_isSuperset<T>(
|
||||
a: ReadonlySet<T>,
|
||||
b: ReadonlySet<T>,
|
||||
): boolean {
|
||||
for (const v of b) {
|
||||
if (!a.has(v)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function Iterable_some<T>(
|
||||
iter: Iterable<T>,
|
||||
pred: (item: T) => boolean,
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @enableTransitivelyFreezeFunctionExpressions:false
|
||||
import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a, b}: {a: number, b: number}) {
|
||||
const x = [];
|
||||
const y = {value: a};
|
||||
|
||||
arrayPush(x, y); // x and y co-mutate
|
||||
const y_alias = y;
|
||||
const cb = () => y_alias.value;
|
||||
setPropertyByKey(x[0], 'value', b); // might overwrite y.value
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: 2, b: 10}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 10},
|
||||
{a: 2, b: 11},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush, setPropertyByKey, Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(3);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const x = [];
|
||||
const y = { value: a };
|
||||
|
||||
arrayPush(x, y);
|
||||
const y_alias = y;
|
||||
const cb = () => y_alias.value;
|
||||
setPropertyByKey(x[0], "value", b);
|
||||
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: 2, b: 10 }],
|
||||
sequentialRenders: [
|
||||
{ a: 2, b: 10 },
|
||||
{ a: 2, b: 11 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
<div>{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
// @flow @enableTransitivelyFreezeFunctionExpressions:false
|
||||
import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a, b}: {a: number, b: number}) {
|
||||
const x = [];
|
||||
const y = {value: a};
|
||||
|
||||
arrayPush(x, y); // x and y co-mutate
|
||||
const y_alias = y;
|
||||
const cb = () => y_alias.value;
|
||||
setPropertyByKey(x[0], 'value', b); // might overwrite y.value
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: 2, b: 10}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 10},
|
||||
{a: 2, b: 11},
|
||||
],
|
||||
};
|
||||
@@ -5,19 +5,6 @@
|
||||
// @flow @enableTransitivelyFreezeFunctionExpressions:false
|
||||
import {setPropertyByKey, Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Variation of bug in `bug-aliased-capture-aliased-mutate`
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
|
||||
function useFoo({a}: {a: number, b: number}) {
|
||||
const arr = [];
|
||||
const obj = {value: a};
|
||||
@@ -46,7 +33,7 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { setPropertyByKey, Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(2);
|
||||
const { a } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
@@ -55,15 +42,7 @@ function useFoo(t0) {
|
||||
|
||||
setPropertyByKey(obj, "arr", arr);
|
||||
const obj_alias = obj;
|
||||
let t2;
|
||||
if ($[2] !== obj_alias.arr.length) {
|
||||
t2 = () => obj_alias.arr.length;
|
||||
$[2] = obj_alias.arr.length;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
const cb = t2;
|
||||
const cb = () => obj_alias.arr.length;
|
||||
for (let i = 0; i < a; i++) {
|
||||
arr.push(i);
|
||||
}
|
||||
@@ -84,4 +63,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
<div>{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}</div>
|
||||
@@ -1,19 +1,6 @@
|
||||
// @flow @enableTransitivelyFreezeFunctionExpressions:false
|
||||
import {setPropertyByKey, Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Variation of bug in `bug-aliased-capture-aliased-mutate`
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
|
||||
function useFoo({a}: {a: number, b: number}) {
|
||||
const arr = [];
|
||||
const obj = {value: a};
|
||||
@@ -23,34 +23,18 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(6);
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.a) {
|
||||
t0 = { a: props.a };
|
||||
const item = { a: props.a };
|
||||
const items = [item];
|
||||
t0 = items.map(_temp);
|
||||
$[0] = props.a;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const item = t0;
|
||||
let t1;
|
||||
if ($[2] !== item) {
|
||||
t1 = [item];
|
||||
$[2] = item;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const items = t1;
|
||||
let t2;
|
||||
if ($[4] !== items) {
|
||||
t2 = items.map(_temp);
|
||||
$[4] = items;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const mapped = t2;
|
||||
const mapped = t0;
|
||||
return mapped;
|
||||
}
|
||||
function _temp(item_0) {
|
||||
|
||||
@@ -50,8 +50,7 @@ function Component(props) {
|
||||
console.log(handlers.value);
|
||||
break bb0;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
t0 = handlers;
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @enableTransitivelyFreezeFunctionExpressions:false
|
||||
import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* 1. `InferMutableRanges` derives the mutable range of identifiers and their
|
||||
* aliases from `LoadLocal`, `PropertyLoad`, etc
|
||||
* - After this pass, y's mutable range only extends to `arrayPush(x, y)`
|
||||
* - We avoid assigning mutable ranges to loads after y's mutable range, as
|
||||
* these are working with an immutable value. As a result, `LoadLocal y` and
|
||||
* `PropertyLoad y` do not get mutable ranges
|
||||
* 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes,
|
||||
* as according to the 'co-mutation' of different values
|
||||
* - Here, we infer that
|
||||
* - `arrayPush(y, x)` might alias `x` and `y` to each other
|
||||
* - `setPropertyKey(x, ...)` may mutate both `x` and `y`
|
||||
* - This pass correctly extends the mutable range of `y`
|
||||
* - Since we didn't run `InferMutableRange` logic again, the LoadLocal /
|
||||
* PropertyLoads still don't have a mutable range
|
||||
*
|
||||
* Note that the this bug is an edge case. Compiler output is only invalid for:
|
||||
* - function expressions with
|
||||
* `enableTransitivelyFreezeFunctionExpressions:false`
|
||||
* - functions that throw and get retried without clearing the memocache
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
function useFoo({a, b}: {a: number, b: number}) {
|
||||
const x = [];
|
||||
const y = {value: a};
|
||||
|
||||
arrayPush(x, y); // x and y co-mutate
|
||||
const y_alias = y;
|
||||
const cb = () => y_alias.value;
|
||||
setPropertyByKey(x[0], 'value', b); // might overwrite y.value
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: 2, b: 10}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 10},
|
||||
{a: 2, b: 11},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush, setPropertyByKey, Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(5);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const x = [];
|
||||
const y = { value: a };
|
||||
|
||||
arrayPush(x, y);
|
||||
const y_alias = y;
|
||||
let t2;
|
||||
if ($[3] !== y_alias.value) {
|
||||
t2 = () => y_alias.value;
|
||||
$[3] = y_alias.value;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
const cb = t2;
|
||||
setPropertyByKey(x[0], "value", b);
|
||||
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: 2, b: 10 }],
|
||||
sequentialRenders: [
|
||||
{ a: 2, b: 10 },
|
||||
{ a: 2, b: 11 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// @flow @enableTransitivelyFreezeFunctionExpressions:false
|
||||
import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* 1. `InferMutableRanges` derives the mutable range of identifiers and their
|
||||
* aliases from `LoadLocal`, `PropertyLoad`, etc
|
||||
* - After this pass, y's mutable range only extends to `arrayPush(x, y)`
|
||||
* - We avoid assigning mutable ranges to loads after y's mutable range, as
|
||||
* these are working with an immutable value. As a result, `LoadLocal y` and
|
||||
* `PropertyLoad y` do not get mutable ranges
|
||||
* 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes,
|
||||
* as according to the 'co-mutation' of different values
|
||||
* - Here, we infer that
|
||||
* - `arrayPush(y, x)` might alias `x` and `y` to each other
|
||||
* - `setPropertyKey(x, ...)` may mutate both `x` and `y`
|
||||
* - This pass correctly extends the mutable range of `y`
|
||||
* - Since we didn't run `InferMutableRange` logic again, the LoadLocal /
|
||||
* PropertyLoads still don't have a mutable range
|
||||
*
|
||||
* Note that the this bug is an edge case. Compiler output is only invalid for:
|
||||
* - function expressions with
|
||||
* `enableTransitivelyFreezeFunctionExpressions:false`
|
||||
* - functions that throw and get retried without clearing the memocache
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
|
||||
*/
|
||||
function useFoo({a, b}: {a: number, b: number}) {
|
||||
const x = [];
|
||||
const y = {value: a};
|
||||
|
||||
arrayPush(x, y); // x and y co-mutate
|
||||
const y_alias = y;
|
||||
const cb = () => y_alias.value;
|
||||
setPropertyByKey(x[0], 'value', b); // might overwrite y.value
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: 2, b: 10}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 10},
|
||||
{a: 2, b: 11},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
const Codes = {
|
||||
en: {name: 'English'},
|
||||
ja: {name: 'Japanese'},
|
||||
ko: {name: 'Korean'},
|
||||
zh: {name: 'Chinese'},
|
||||
};
|
||||
|
||||
function Component(a) {
|
||||
let keys;
|
||||
if (a) {
|
||||
keys = Object.keys(Codes);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const options = keys.map(code => {
|
||||
const country = Codes[code];
|
||||
return {
|
||||
name: country.name,
|
||||
code,
|
||||
};
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<ValidateMemoization inputs={[]} output={keys} onlyCheckCompiled={true} />
|
||||
<ValidateMemoization
|
||||
inputs={[]}
|
||||
output={options}
|
||||
onlyCheckCompiled={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: false}],
|
||||
sequentialRenders: [
|
||||
{a: false},
|
||||
{a: true},
|
||||
{a: true},
|
||||
{a: false},
|
||||
{a: true},
|
||||
{a: false},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { ValidateMemoization } from "shared-runtime";
|
||||
|
||||
const Codes = {
|
||||
en: { name: "English" },
|
||||
ja: { name: "Japanese" },
|
||||
ko: { name: "Korean" },
|
||||
zh: { name: "Chinese" },
|
||||
};
|
||||
|
||||
function Component(a) {
|
||||
const $ = _c(13);
|
||||
let keys;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
t1 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
if (a) {
|
||||
keys = Object.keys(Codes);
|
||||
} else {
|
||||
t1 = null;
|
||||
break bb0;
|
||||
}
|
||||
|
||||
t0 = keys.map(_temp);
|
||||
}
|
||||
$[0] = a;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
$[3] = keys;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
keys = $[3];
|
||||
}
|
||||
if (t1 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t1;
|
||||
}
|
||||
const options = t0;
|
||||
let t2;
|
||||
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = [];
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
let t3;
|
||||
if ($[5] !== keys) {
|
||||
t3 = (
|
||||
<ValidateMemoization inputs={t2} output={keys} onlyCheckCompiled={true} />
|
||||
);
|
||||
$[5] = keys;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
let t4;
|
||||
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = [];
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
let t5;
|
||||
if ($[8] !== options) {
|
||||
t5 = (
|
||||
<ValidateMemoization
|
||||
inputs={t4}
|
||||
output={options}
|
||||
onlyCheckCompiled={true}
|
||||
/>
|
||||
);
|
||||
$[8] = options;
|
||||
$[9] = t5;
|
||||
} else {
|
||||
t5 = $[9];
|
||||
}
|
||||
let t6;
|
||||
if ($[10] !== t3 || $[11] !== t5) {
|
||||
t6 = (
|
||||
<>
|
||||
{t3}
|
||||
{t5}
|
||||
</>
|
||||
);
|
||||
$[10] = t3;
|
||||
$[11] = t5;
|
||||
$[12] = t6;
|
||||
} else {
|
||||
t6 = $[12];
|
||||
}
|
||||
return t6;
|
||||
}
|
||||
function _temp(code) {
|
||||
const country = Codes[code];
|
||||
return { name: country.name, code };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: false }],
|
||||
sequentialRenders: [
|
||||
{ a: false },
|
||||
{ a: true },
|
||||
{ a: true },
|
||||
{ a: false },
|
||||
{ a: true },
|
||||
{ a: false },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
const Codes = {
|
||||
en: {name: 'English'},
|
||||
ja: {name: 'Japanese'},
|
||||
ko: {name: 'Korean'},
|
||||
zh: {name: 'Chinese'},
|
||||
};
|
||||
|
||||
function Component(a) {
|
||||
let keys;
|
||||
if (a) {
|
||||
keys = Object.keys(Codes);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const options = keys.map(code => {
|
||||
const country = Codes[code];
|
||||
return {
|
||||
name: country.name,
|
||||
code,
|
||||
};
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<ValidateMemoization inputs={[]} output={keys} onlyCheckCompiled={true} />
|
||||
<ValidateMemoization
|
||||
inputs={[]}
|
||||
output={options}
|
||||
onlyCheckCompiled={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: false}],
|
||||
sequentialRenders: [
|
||||
{a: false},
|
||||
{a: true},
|
||||
{a: true},
|
||||
{a: false},
|
||||
{a: true},
|
||||
{a: false},
|
||||
],
|
||||
};
|
||||
@@ -67,8 +67,7 @@ function Component(props) {
|
||||
case "b": {
|
||||
break bb1;
|
||||
}
|
||||
case "c": {
|
||||
}
|
||||
case "c":
|
||||
default: {
|
||||
x = 6;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component({a, b, c}) {
|
||||
// This is an object version of array-access-assignment.js
|
||||
// Meant to confirm that object expressions and PropertyStore/PropertyLoad with strings
|
||||
// works equivalently to array expressions and property accesses with numeric indices
|
||||
const x = {zero: a};
|
||||
const y = {zero: null, one: b};
|
||||
const z = {zero: {}, one: {}, two: {zero: c}};
|
||||
x.zero = y.one;
|
||||
z.zero.zero = x.zero;
|
||||
return {zero: x, one: z};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 1, b: 20, c: 300}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 20, c: 300},
|
||||
{a: 3, b: 20, c: 300},
|
||||
{a: 3, b: 21, c: 300},
|
||||
{a: 3, b: 22, c: 300},
|
||||
{a: 3, b: 22, c: 301},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { a, b, c } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a || $[1] !== b || $[2] !== c) {
|
||||
const x = { zero: a };
|
||||
let t2;
|
||||
if ($[4] !== b) {
|
||||
t2 = { zero: null, one: b };
|
||||
$[4] = b;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const y = t2;
|
||||
const z = { zero: {}, one: {}, two: { zero: c } };
|
||||
x.zero = y.one;
|
||||
z.zero.zero = x.zero;
|
||||
t1 = { zero: x, one: z };
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = c;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: 1, b: 20, c: 300 }],
|
||||
sequentialRenders: [
|
||||
{ a: 2, b: 20, c: 300 },
|
||||
{ a: 3, b: 20, c: 300 },
|
||||
{ a: 3, b: 21, c: 300 },
|
||||
{ a: 3, b: 22, c: 300 },
|
||||
{ a: 3, b: 22, c: 301 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"zero":{"zero":20},"one":{"zero":{"zero":20},"one":{},"two":{"zero":300}}}
|
||||
{"zero":{"zero":20},"one":{"zero":{"zero":20},"one":{},"two":{"zero":300}}}
|
||||
{"zero":{"zero":21},"one":{"zero":{"zero":21},"one":{},"two":{"zero":300}}}
|
||||
{"zero":{"zero":22},"one":{"zero":{"zero":22},"one":{},"two":{"zero":300}}}
|
||||
{"zero":{"zero":22},"one":{"zero":{"zero":22},"one":{},"two":{"zero":301}}}
|
||||
@@ -0,0 +1,23 @@
|
||||
function Component({a, b, c}) {
|
||||
// This is an object version of array-access-assignment.js
|
||||
// Meant to confirm that object expressions and PropertyStore/PropertyLoad with strings
|
||||
// works equivalently to array expressions and property accesses with numeric indices
|
||||
const x = {zero: a};
|
||||
const y = {zero: null, one: b};
|
||||
const z = {zero: {}, one: {}, two: {zero: c}};
|
||||
x.zero = y.one;
|
||||
z.zero.zero = x.zero;
|
||||
return {zero: x, one: z};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 1, b: 20, c: 300}],
|
||||
sequentialRenders: [
|
||||
{a: 2, b: 20, c: 300},
|
||||
{a: 3, b: 20, c: 300},
|
||||
{a: 3, b: 21, c: 300},
|
||||
{a: 3, b: 22, c: 300},
|
||||
{a: 3, b: 22, c: 301},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoFreezingKnownMutableFunctions
|
||||
|
||||
import {useCallback, useEffect, useRef} from 'react';
|
||||
import {useHook} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const params = useHook();
|
||||
const update = useCallback(
|
||||
partialParams => {
|
||||
const nextParams = {
|
||||
...params,
|
||||
...partialParams,
|
||||
};
|
||||
// Due to how we previously represented ObjectExpressions in InferReferenceEffects,
|
||||
// this was recorded as a mutation of a context value (`params`) which then made
|
||||
// the function appear ineligible for freezing when passing to useEffect below.
|
||||
nextParams.param = 'value';
|
||||
console.log(nextParams);
|
||||
},
|
||||
[params]
|
||||
);
|
||||
const ref = useRef(null);
|
||||
useEffect(() => {
|
||||
if (ref.current === null) {
|
||||
update();
|
||||
}
|
||||
}, [update]);
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoFreezingKnownMutableFunctions
|
||||
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useHook } from "shared-runtime";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(5);
|
||||
const params = useHook();
|
||||
let t0;
|
||||
if ($[0] !== params) {
|
||||
t0 = (partialParams) => {
|
||||
const nextParams = { ...params, ...partialParams };
|
||||
|
||||
nextParams.param = "value";
|
||||
console.log(nextParams);
|
||||
};
|
||||
$[0] = params;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const update = t0;
|
||||
|
||||
const ref = useRef(null);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[2] !== update) {
|
||||
t1 = () => {
|
||||
if (ref.current === null) {
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
t2 = [update];
|
||||
$[2] = update;
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,30 @@
|
||||
// @validateNoFreezingKnownMutableFunctions
|
||||
|
||||
import {useCallback, useEffect, useRef} from 'react';
|
||||
import {useHook} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const params = useHook();
|
||||
const update = useCallback(
|
||||
partialParams => {
|
||||
const nextParams = {
|
||||
...params,
|
||||
...partialParams,
|
||||
};
|
||||
// Due to how we previously represented ObjectExpressions in InferReferenceEffects,
|
||||
// this was recorded as a mutation of a context value (`params`) which then made
|
||||
// the function appear ineligible for freezing when passing to useEffect below.
|
||||
nextParams.param = 'value';
|
||||
console.log(nextParams);
|
||||
},
|
||||
[params]
|
||||
);
|
||||
const ref = useRef(null);
|
||||
useEffect(() => {
|
||||
if (ref.current === null) {
|
||||
update();
|
||||
}
|
||||
}, [update]);
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
@@ -50,10 +50,8 @@ function Component(props) {
|
||||
case 1: {
|
||||
break bb0;
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
default: {
|
||||
}
|
||||
case 2:
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
if (props.cond2) {
|
||||
|
||||
@@ -41,8 +41,7 @@ function foo() {
|
||||
case 2: {
|
||||
break bb0;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,22 +43,17 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
function foo(x) {
|
||||
bb0: switch (x) {
|
||||
case 0: {
|
||||
}
|
||||
case 1: {
|
||||
}
|
||||
case 0:
|
||||
case 1:
|
||||
case 2: {
|
||||
break bb0;
|
||||
}
|
||||
case 3: {
|
||||
break bb0;
|
||||
}
|
||||
case 4: {
|
||||
}
|
||||
case 5: {
|
||||
}
|
||||
default: {
|
||||
}
|
||||
case 4:
|
||||
case 5:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -453,8 +453,6 @@ const skipFilter = new Set([
|
||||
'inner-function/nullable-objects/bug-invalid-array-map-manual',
|
||||
'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr',
|
||||
`bug-capturing-func-maybealias-captured-mutate`,
|
||||
'bug-aliased-capture-aliased-mutate',
|
||||
'bug-aliased-capture-mutate',
|
||||
'bug-functiondecl-hoisting',
|
||||
'bug-type-inference-control-flow',
|
||||
'fbt/bug-fbt-plural-multiple-function-calls',
|
||||
@@ -485,6 +483,7 @@ const skipFilter = new Set([
|
||||
'todo.lower-context-access-array-destructuring',
|
||||
'lower-context-selector-simple',
|
||||
'lower-context-acess-multiple',
|
||||
'bug-separate-memoization-due-to-callback-capturing',
|
||||
]);
|
||||
|
||||
export default skipFilter;
|
||||
|
||||
Reference in New Issue
Block a user