mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
validatePreserveExistingMemoizationGuarantees ensures compiler preserves subset
of dependencies from source
---
`validatePreserveExistingMemoizationGuarantees` previously checked
- manual memoization dependencies and declarations (the returned value) do not
"lose" memoization due to inferred mutations
```
function useFoo() {
const y = {};
// bail out because we infer that y cannot be a dependency of x as its
mutableRange
// extends beyond
const x = useMemo(() => maybeMutate(y), [y]);
// similarly, bail out if we find that x or y are mutated here
return x;
}
```
- manual memoization deps and decls do not get deopted due to hook calls
```
function useBar() {
const x = getArray();
useHook();
mutate(x);
return useCallback(() => [x], [x]);
}
```
This PR updates `validatePreserveExistingMemoizationGuarantees` with the
following correctness conditions:
*major change* All inferred dependencies of reactive scopes between
`StartMemoize` and `StopMemoize` instructions (e.g. scopes containing manual
memoization code) must either:
1. be produced from earlier within the same manual memoization block
2. exactly match an element of depslist from source
This assumes that the source codebase mostly follows the `exhaustive-deps` lint
rule, which ensures that deps lists are (1) simple expressions composing of
reads from named identifiers + property loads and (2) exactly match deps usages
in the useMemo/useCallback itself.
---
Validated that this does not change source by running internally on ~50k files
(no validation on `main`, no validation on this PR, and validation on this PR).
This commit is contained in:
@@ -632,14 +632,41 @@ export type Phi = {
|
||||
type: Type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid ManualMemoDependencies are always of the form
|
||||
* `sourceDeclaredVariable.a.b?.c`, since this is documented
|
||||
* and enforced by the `react-hooks/exhaustive-deps` rule.
|
||||
*
|
||||
* `root` must either reference a ValidatedIdentifier or a global
|
||||
* variable.
|
||||
*/
|
||||
export type ManualMemoDependency = {
|
||||
root:
|
||||
| {
|
||||
kind: "NamedLocal";
|
||||
value: Place;
|
||||
}
|
||||
| { kind: "Global"; identifierName: string };
|
||||
path: Array<string>;
|
||||
};
|
||||
|
||||
export type StartMemoize = {
|
||||
kind: "StartMemoize";
|
||||
deps: Array<Place>;
|
||||
// Start/FinishMemoize markers should have matching ids
|
||||
manualMemoId: number;
|
||||
/**
|
||||
* deps-list from source code, or null if one was not provided
|
||||
* (e.g. useMemo without a second arg)
|
||||
*/
|
||||
deps: Array<ManualMemoDependency> | null;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
export type FinishMemoize = {
|
||||
kind: "FinishMemoize";
|
||||
// Start/FinishMemoize markers should have matching ids
|
||||
manualMemoId: number;
|
||||
decl: Place;
|
||||
pruned?: true;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
Instruction,
|
||||
InstructionValue,
|
||||
LValue,
|
||||
ManualMemoDependency,
|
||||
MutableRange,
|
||||
ObjectMethod,
|
||||
ObjectPropertyKey,
|
||||
@@ -601,9 +602,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
break;
|
||||
}
|
||||
case "StartMemoize": {
|
||||
value = `StartMemoize deps=${instrValue.deps.map((dep) =>
|
||||
printPlace(dep)
|
||||
)}`;
|
||||
value = `StartMemoize deps=${
|
||||
instrValue.deps?.map((dep) => printManualMemoDependency(dep, false)) ??
|
||||
"(none)"
|
||||
}`;
|
||||
break;
|
||||
}
|
||||
case "FinishMemoize": {
|
||||
@@ -744,6 +746,25 @@ function printScope(scope: ReactiveScope | null): string {
|
||||
return `${scope !== null ? `_@${scope.id}` : ""}`;
|
||||
}
|
||||
|
||||
export function printManualMemoDependency(
|
||||
val: ManualMemoDependency,
|
||||
nameOnly: boolean
|
||||
): string {
|
||||
let rootStr;
|
||||
if (val.root.kind === "Global") {
|
||||
rootStr = val.root.identifierName;
|
||||
} else {
|
||||
CompilerError.invariant(val.root.value.identifier.name?.kind === "named", {
|
||||
reason: "DepsValidation: expected named local variable in depslist",
|
||||
suggestions: null,
|
||||
loc: val.root.value.loc,
|
||||
});
|
||||
rootStr = nameOnly
|
||||
? val.root.value.identifier.name.value
|
||||
: printIdentifier(val.root.value.identifier);
|
||||
}
|
||||
return `${rootStr}${val.path.length > 0 ? "." : ""}${val.path.join(".")}`;
|
||||
}
|
||||
export function printType(type: Type): string {
|
||||
if (type.kind === "Type") return "";
|
||||
// TODO(mofeiZ): add debugName for generated ids
|
||||
|
||||
@@ -217,8 +217,12 @@ export function* eachInstructionValueOperand(
|
||||
break;
|
||||
}
|
||||
case "StartMemoize": {
|
||||
for (const dep of instrValue.deps) {
|
||||
yield dep;
|
||||
if (instrValue.deps != null) {
|
||||
for (const dep of instrValue.deps) {
|
||||
if (dep.root.kind === "NamedLocal") {
|
||||
yield dep.root.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -528,8 +532,12 @@ export function mapInstructionValueOperands(
|
||||
break;
|
||||
}
|
||||
case "StartMemoize": {
|
||||
for (let i = 0; i < instrValue.deps.length; i++) {
|
||||
instrValue.deps[i] = fn(instrValue.deps[i]);
|
||||
if (instrValue.deps != null) {
|
||||
for (const dep of instrValue.deps) {
|
||||
if (dep.root.kind === "NamedLocal") {
|
||||
dep.root.value = fn(dep.root.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+163
-39
@@ -16,8 +16,10 @@ import {
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionId,
|
||||
InstructionValue,
|
||||
LoadGlobal,
|
||||
LoadLocal,
|
||||
ManualMemoDependency,
|
||||
MethodCall,
|
||||
Place,
|
||||
PropertyLoad,
|
||||
@@ -28,7 +30,6 @@ import {
|
||||
makeInstructionId,
|
||||
} from "../HIR";
|
||||
import { createTemporaryPlace, markInstructionIds } from "../HIR/HIRBuilder";
|
||||
import { eachInstructionValueOperand } from "../HIR/visitors";
|
||||
|
||||
type ManualMemoCallee = {
|
||||
kind: "useMemo" | "useCallback";
|
||||
@@ -39,14 +40,84 @@ type IdentifierSidemap = {
|
||||
functions: Map<IdentifierId, TInstruction<FunctionExpression>>;
|
||||
manualMemos: Map<IdentifierId, ManualMemoCallee>;
|
||||
react: Set<IdentifierId>;
|
||||
maybeDepsLists: Map<IdentifierId, Array<Place>>;
|
||||
maybeDeps: Map<IdentifierId, ManualMemoDependency>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect loads from named variables and property reads from @value
|
||||
* into `maybeDeps`
|
||||
* Returns the variable + property reads represented by @instr
|
||||
*/
|
||||
export function collectMaybeMemoDependencies(
|
||||
value: InstructionValue,
|
||||
maybeDeps: Map<IdentifierId, ManualMemoDependency>
|
||||
): ManualMemoDependency | null {
|
||||
switch (value.kind) {
|
||||
case "LoadGlobal": {
|
||||
return {
|
||||
root: {
|
||||
kind: "Global",
|
||||
identifierName: value.name,
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
const object = maybeDeps.get(value.object.identifier.id);
|
||||
if (object != null) {
|
||||
return {
|
||||
root: object.root,
|
||||
path: [...object.path, value.property],
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "LoadLocal":
|
||||
case "LoadContext": {
|
||||
const source = maybeDeps.get(value.place.identifier.id);
|
||||
if (source != null) {
|
||||
return source;
|
||||
} else if (
|
||||
value.place.identifier.name != null &&
|
||||
value.place.identifier.name.kind === "named"
|
||||
) {
|
||||
return {
|
||||
root: {
|
||||
kind: "NamedLocal",
|
||||
value: { ...value.place },
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "StoreLocal": {
|
||||
/*
|
||||
* Value blocks rely on StoreLocal to populate their return value.
|
||||
* We need to track these as optional property chains are valid in
|
||||
* source depslists
|
||||
*/
|
||||
const lvalue = value.lvalue.place.identifier;
|
||||
const rvalue = value.value.identifier.id;
|
||||
const aliased = maybeDeps.get(rvalue);
|
||||
if (aliased != null && lvalue.name?.kind !== "named") {
|
||||
maybeDeps.set(lvalue.id, aliased);
|
||||
return aliased;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectTemporaries(
|
||||
instr: Instruction,
|
||||
env: Environment,
|
||||
sidemap: IdentifierSidemap
|
||||
): void {
|
||||
const { value } = instr;
|
||||
const { value, lvalue } = instr;
|
||||
switch (value.kind) {
|
||||
case "FunctionExpression": {
|
||||
sidemap.functions.set(
|
||||
@@ -80,14 +151,29 @@ function collectTemporaries(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ArrayExpression": {
|
||||
if (value.elements.every((e) => e.kind === "Identifier")) {
|
||||
sidemap.maybeDepsLists.set(
|
||||
instr.lvalue.identifier.id,
|
||||
value.elements as Array<Place>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
const maybeDep = collectMaybeMemoDependencies(value, sidemap.maybeDeps);
|
||||
// We don't expect named lvalues during this pass (unlike ValidatePreservingManualMemo)
|
||||
if (maybeDep != null) {
|
||||
sidemap.maybeDeps.set(lvalue.identifier.id, maybeDep);
|
||||
}
|
||||
}
|
||||
|
||||
function makeManualMemoizationMarkers(
|
||||
fnExpr: Place,
|
||||
env: Environment,
|
||||
depsList: Array<Place>,
|
||||
memoDecl: Place
|
||||
depsList: Array<ManualMemoDependency> | null,
|
||||
memoDecl: Place,
|
||||
manualMemoId: number
|
||||
): [TInstruction<StartMemoize>, TInstruction<FinishMemoize>] {
|
||||
return [
|
||||
{
|
||||
@@ -95,6 +181,7 @@ function makeManualMemoizationMarkers(
|
||||
lvalue: createTemporaryPlace(env),
|
||||
value: {
|
||||
kind: "StartMemoize",
|
||||
manualMemoId,
|
||||
/*
|
||||
* Use deps list from source instead of inferred deps
|
||||
* as dependencies
|
||||
@@ -109,6 +196,7 @@ function makeManualMemoizationMarkers(
|
||||
lvalue: createTemporaryPlace(env),
|
||||
value: {
|
||||
kind: "FinishMemoize",
|
||||
manualMemoId,
|
||||
decl: { ...memoDecl },
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
@@ -183,11 +271,13 @@ function getManualMemoizationReplacement(
|
||||
|
||||
function extractManualMemoizationArgs(
|
||||
instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
kind: "useCallback" | "useMemo"
|
||||
kind: "useCallback" | "useMemo",
|
||||
sidemap: IdentifierSidemap
|
||||
): {
|
||||
fnPlace: Place;
|
||||
depsList: Array<ManualMemoDependency> | null;
|
||||
} {
|
||||
const [fnPlace] = instr.value.args as Array<
|
||||
const [fnPlace, depsListPlace] = instr.value.args as Array<
|
||||
Place | SpreadPattern | undefined
|
||||
>;
|
||||
if (fnPlace == null) {
|
||||
@@ -197,15 +287,40 @@ function extractManualMemoizationArgs(
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
if (fnPlace?.kind !== "Identifier") {
|
||||
if (fnPlace?.kind !== "Identifier" || depsListPlace?.kind === "Spread") {
|
||||
CompilerError.throwInvalidReact({
|
||||
reason: `Unexpected arguments to ${kind} call`,
|
||||
loc: instr.value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
let depsList: Array<ManualMemoDependency> | null = null;
|
||||
if (depsListPlace != null) {
|
||||
const maybeDepsList = sidemap.maybeDepsLists.get(
|
||||
depsListPlace.identifier.id
|
||||
);
|
||||
if (maybeDepsList == null) {
|
||||
CompilerError.throwInvalidReact({
|
||||
reason: `Expected the dependency list for ${kind} to be an array literal without rest spreads`,
|
||||
suggestions: null,
|
||||
loc: depsListPlace.loc,
|
||||
});
|
||||
}
|
||||
depsList = maybeDepsList.map((dep) => {
|
||||
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
|
||||
if (maybeDep == null) {
|
||||
CompilerError.throwInvalidReact({
|
||||
reason: `Expected the dependency list for ${kind} to be an array of simple expressions`,
|
||||
suggestions: null,
|
||||
loc: dep.loc,
|
||||
});
|
||||
}
|
||||
return maybeDep;
|
||||
});
|
||||
}
|
||||
return {
|
||||
fnPlace,
|
||||
depsList,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,7 +340,10 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
functions: new Map(),
|
||||
manualMemos: new Map(),
|
||||
react: new Set(),
|
||||
maybeDeps: new Map(),
|
||||
maybeDepsLists: new Map(),
|
||||
};
|
||||
let nextManualMemoId = 0;
|
||||
|
||||
/**
|
||||
* Phase 1:
|
||||
@@ -238,10 +356,7 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
*/
|
||||
const queuedInserts: Map<
|
||||
InstructionId,
|
||||
{
|
||||
kind: "before" | "after";
|
||||
value: TInstruction<StartMemoize> | TInstruction<FinishMemoize>;
|
||||
}
|
||||
TInstruction<StartMemoize> | TInstruction<FinishMemoize>
|
||||
> = new Map();
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
for (let i = 0; i < block.instructions.length; i++) {
|
||||
@@ -257,9 +372,10 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
|
||||
const manualMemo = sidemap.manualMemos.get(id);
|
||||
if (manualMemo != null) {
|
||||
const { fnPlace } = extractManualMemoizationArgs(
|
||||
const { fnPlace, depsList } = extractManualMemoizationArgs(
|
||||
instr as TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
manualMemo.kind
|
||||
manualMemo.kind,
|
||||
sidemap
|
||||
);
|
||||
instr.value = getManualMemoizationReplacement(
|
||||
fnPlace,
|
||||
@@ -267,11 +383,22 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
manualMemo.kind
|
||||
);
|
||||
if (isValidationEnabled) {
|
||||
const inlineMemoFn = sidemap.functions.get(fnPlace.identifier.id);
|
||||
if (inlineMemoFn == null) {
|
||||
/**
|
||||
* Explicitly bail out when we encounter manual memoization
|
||||
* without inline instructions, as our current validation
|
||||
* assumes that source depslists closely match inferred deps
|
||||
* due to the `exhaustive-deps` lint rule (which only provides
|
||||
* diagnostics for inline memo functions)
|
||||
* ```js
|
||||
* useMemo(opaqueFn, [dep1, dep2]);
|
||||
* ```
|
||||
* While we could handle this by diffing reactive scope deps
|
||||
* of the opaque arg against the source depslist, this pattern
|
||||
* is rare and likely sketchy.
|
||||
*/
|
||||
if (!sidemap.functions.has(fnPlace.identifier.id)) {
|
||||
CompilerError.throwInvalidReact({
|
||||
reason:
|
||||
"DepsValidation: Expected function literal as manual memoization callback",
|
||||
reason: `Expected the first argument of ${manualMemo.kind} to be an inline function expression`,
|
||||
suggestions: [],
|
||||
loc: fnPlace.loc,
|
||||
});
|
||||
@@ -290,24 +417,26 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
const [startMarker, finishMarker] = makeManualMemoizationMarkers(
|
||||
fnPlace,
|
||||
func.env,
|
||||
// Next PR will replace this with depslist from source
|
||||
[...eachInstructionValueOperand(inlineMemoFn.value)],
|
||||
memoDecl
|
||||
depsList,
|
||||
memoDecl,
|
||||
nextManualMemoId++
|
||||
);
|
||||
|
||||
/*
|
||||
* This PR reorders startMarker to right before the inlineMemoFn
|
||||
* since startMarker references inlineMemoFn.deps.
|
||||
* Next PR will move startMarker earlier, to after the `useMemo`/
|
||||
* `useCallback` load itself (as it also changes startMarker to
|
||||
* not reference lowered deps anymore).
|
||||
/**
|
||||
* Insert StartMarker right after the `useMemo`/`useCallback` load to
|
||||
* ensure all temporaries created when lowering the inline fn expression
|
||||
* are included.
|
||||
* e.g.
|
||||
* ```
|
||||
* 0: LoadGlobal useMemo
|
||||
* 1: StartMarker deps=[var]
|
||||
* 2: t0 = LoadContext [var]
|
||||
* 3: function deps=t0
|
||||
* ...
|
||||
* ```
|
||||
*/
|
||||
queuedInserts.set(inlineMemoFn.id, {
|
||||
kind: "before",
|
||||
value: startMarker,
|
||||
});
|
||||
queuedInserts.set(instr.id, { kind: "after", value: finishMarker });
|
||||
continue;
|
||||
queuedInserts.set(manualMemo.loadInstr.id, startMarker);
|
||||
queuedInserts.set(instr.id, finishMarker);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -328,13 +457,8 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
const insertInstr = queuedInserts.get(instr.id);
|
||||
if (insertInstr != null) {
|
||||
nextInstructions = nextInstructions ?? block.instructions.slice(0, i);
|
||||
if (insertInstr.kind === "before") {
|
||||
nextInstructions.push(insertInstr.value);
|
||||
nextInstructions.push(instr);
|
||||
} else {
|
||||
nextInstructions.push(instr);
|
||||
nextInstructions.push(insertInstr.value);
|
||||
}
|
||||
nextInstructions.push(instr);
|
||||
nextInstructions.push(insertInstr);
|
||||
} else if (nextInstructions != null) {
|
||||
nextInstructions.push(instr);
|
||||
}
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ export function writeReactiveBlock(
|
||||
writer.writeLine("}");
|
||||
}
|
||||
|
||||
function printDependency(dependency: ReactiveScopeDependency): string {
|
||||
export function printDependency(dependency: ReactiveScopeDependency): string {
|
||||
const identifier =
|
||||
printIdentifier(dependency.identifier) +
|
||||
printType(dependency.identifier.type);
|
||||
|
||||
+1
-1
@@ -933,7 +933,7 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
identifier.scope !== null &&
|
||||
this.prunedScopes.has(identifier.scope.id)
|
||||
) {
|
||||
return { kind: "remove" };
|
||||
instruction.value.pruned = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+287
-15
@@ -5,16 +5,25 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError, ErrorSeverity } from "..";
|
||||
import { CompilerError, Effect, ErrorSeverity } from "..";
|
||||
import {
|
||||
GeneratedSource,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionValue,
|
||||
ManualMemoDependency,
|
||||
Place,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveScopeBlock,
|
||||
ReactiveScopeDependency,
|
||||
ReactiveValue,
|
||||
ScopeId,
|
||||
} from "../HIR";
|
||||
import { printManualMemoDependency } from "../HIR/PrintHIR";
|
||||
import { eachInstructionValueOperand } from "../HIR/visitors";
|
||||
import { collectMaybeMemoDependencies } from "../Inference/DropManualMemoization";
|
||||
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
|
||||
import {
|
||||
ReactiveFunctionVisitor,
|
||||
@@ -29,22 +38,250 @@ import {
|
||||
* was pruned.
|
||||
*/
|
||||
export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
|
||||
const errors = new CompilerError();
|
||||
visitReactiveFunction(fn, new Visitor(), errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
const state = {
|
||||
errors: new CompilerError(),
|
||||
manualMemoState: null,
|
||||
};
|
||||
visitReactiveFunction(fn, new Visitor(), state);
|
||||
if (state.errors.hasErrors()) {
|
||||
throw state.errors;
|
||||
}
|
||||
}
|
||||
|
||||
class Visitor extends ReactiveFunctionVisitor<CompilerError> {
|
||||
type ManualMemoBlockState = {
|
||||
/**
|
||||
* Values produced within manual memoization blocks.
|
||||
* We track these to ensure our inferred dependencies are
|
||||
* produced before the manual memo block starts
|
||||
*
|
||||
* As an example:
|
||||
* ```js
|
||||
* // source
|
||||
* const result = useMemo(() => {
|
||||
* return [makeObject(input1), input2],
|
||||
* }, [input1, input2]);
|
||||
* ```
|
||||
* Here, we record inferred dependencies as [input1, input2]
|
||||
* but not t0
|
||||
* ```js
|
||||
* // StartMemoize
|
||||
* let t0;
|
||||
* if ($[0] != input1) {
|
||||
* t0 = makeObject(input1);
|
||||
* // ...
|
||||
* } else { ... }
|
||||
*
|
||||
* let result;
|
||||
* if ($[1] != t0 || $[2] != input2) {
|
||||
* result = [t0, input2];
|
||||
* } else { ... }
|
||||
* ```
|
||||
*/
|
||||
decls: Set<IdentifierId>;
|
||||
|
||||
/*
|
||||
* normalized depslist from useMemo/useCallback
|
||||
* callsite in source
|
||||
*/
|
||||
depsFromSource: Array<ManualMemoDependency> | null;
|
||||
manualMemoId: number;
|
||||
};
|
||||
|
||||
type VisitorState = {
|
||||
errors: CompilerError;
|
||||
manualMemoState: ManualMemoBlockState | null;
|
||||
};
|
||||
|
||||
function prettyPrintScopeDependency(val: ReactiveScopeDependency): string {
|
||||
let rootStr;
|
||||
if (val.identifier.name?.kind === "named") {
|
||||
rootStr = val.identifier.name.value;
|
||||
} else {
|
||||
rootStr = "[unnamed]";
|
||||
}
|
||||
return `${rootStr}${val.path.length > 0 ? "." : ""}${val.path.join(".")}`;
|
||||
}
|
||||
function depsEqual(
|
||||
dep1: ManualMemoDependency,
|
||||
dep2: ManualMemoDependency
|
||||
): boolean {
|
||||
const rootsEqual =
|
||||
(dep1.root.kind === "Global" &&
|
||||
dep2.root.kind === "Global" &&
|
||||
dep1.root.identifierName === dep2.root.identifierName) ||
|
||||
(dep1.root.kind === "NamedLocal" &&
|
||||
dep2.root.kind === "NamedLocal" &&
|
||||
dep1.root.value.identifier.id === dep2.root.value.identifier.id);
|
||||
return (
|
||||
rootsEqual &&
|
||||
dep1.path.length === dep2.path.length &&
|
||||
dep1.path.every((val, idx) => val === dep2.path[idx])
|
||||
);
|
||||
}
|
||||
|
||||
function validateInferredDep(
|
||||
dep: ReactiveScopeDependency,
|
||||
temporaries: Map<IdentifierId, ManualMemoDependency>,
|
||||
declsWithinMemoBlock: Set<IdentifierId>,
|
||||
validDepsInMemoBlock: Array<ManualMemoDependency>,
|
||||
errorState: CompilerError
|
||||
): void {
|
||||
let normalizedDep: ManualMemoDependency;
|
||||
const maybeNormalizedRoot = temporaries.get(dep.identifier.id);
|
||||
if (maybeNormalizedRoot != null) {
|
||||
normalizedDep = {
|
||||
root: maybeNormalizedRoot.root,
|
||||
path: [...maybeNormalizedRoot.path, ...dep.path],
|
||||
};
|
||||
} else {
|
||||
CompilerError.invariant(dep.identifier.name?.kind === "named", {
|
||||
reason:
|
||||
"ValidatePreservedManualMemoization: expected scope dependency to be named",
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
});
|
||||
normalizedDep = {
|
||||
root: {
|
||||
kind: "NamedLocal",
|
||||
value: {
|
||||
kind: "Identifier",
|
||||
identifier: dep.identifier,
|
||||
loc: GeneratedSource,
|
||||
effect: Effect.Read,
|
||||
reactive: false,
|
||||
},
|
||||
},
|
||||
path: [...dep.path],
|
||||
};
|
||||
}
|
||||
for (const originalDep of validDepsInMemoBlock) {
|
||||
if (depsEqual(originalDep, normalizedDep)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const decl of declsWithinMemoBlock) {
|
||||
const normalizedDecl = temporaries.get(decl);
|
||||
if (normalizedDecl != null && depsEqual(normalizedDecl, normalizedDep)) {
|
||||
return;
|
||||
} else if (
|
||||
normalizedDep.root.kind === "NamedLocal" &&
|
||||
decl === normalizedDep.root.value.identifier.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
errorState.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
reason:
|
||||
"Could not preserve manual memoization because an inferred dependency does not match the dependency list in source",
|
||||
description: `The inferred dependency was \`${prettyPrintScopeDependency(
|
||||
dep
|
||||
)}\`, but the source dependencies were [${validDepsInMemoBlock
|
||||
.map((dep) => printManualMemoDependency(dep, true))
|
||||
.join(", ")}]`,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
scopes: Set<ScopeId> = new Set();
|
||||
scopeMapping = new Map();
|
||||
temporaries: Map<IdentifierId, ManualMemoDependency> = new Map();
|
||||
|
||||
collectMaybeMemoDependencies(
|
||||
value: ReactiveValue,
|
||||
state: VisitorState
|
||||
): ManualMemoDependency | null {
|
||||
switch (value.kind) {
|
||||
case "SequenceExpression": {
|
||||
for (const instr of value.instructions) {
|
||||
this.visitInstruction(instr, state);
|
||||
}
|
||||
const result = this.collectMaybeMemoDependencies(value.value, state);
|
||||
|
||||
return result;
|
||||
}
|
||||
case "OptionalExpression": {
|
||||
return this.collectMaybeMemoDependencies(value.value, state);
|
||||
}
|
||||
case "ReactiveFunctionValue":
|
||||
case "ConditionalExpression":
|
||||
case "LogicalExpression": {
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
const dep = collectMaybeMemoDependencies(value, this.temporaries);
|
||||
if (value.kind === "StoreLocal" || value.kind === "StoreContext") {
|
||||
const storeTarget = value.lvalue.place;
|
||||
state.manualMemoState?.decls.add(storeTarget.identifier.id);
|
||||
if (storeTarget.identifier.name?.kind === "named" && dep == null) {
|
||||
const dep: ManualMemoDependency = {
|
||||
root: {
|
||||
kind: "NamedLocal",
|
||||
value: storeTarget,
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
this.temporaries.set(storeTarget.identifier.id, dep);
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recordTemporaries(instr: ReactiveInstruction, state: VisitorState): void {
|
||||
const temporaries = this.temporaries;
|
||||
const { value } = instr;
|
||||
const lvalId = instr.lvalue?.identifier.id;
|
||||
if (lvalId != null && temporaries.has(lvalId)) {
|
||||
return;
|
||||
}
|
||||
const isNamedLocal =
|
||||
lvalId != null && instr.lvalue?.identifier.name?.kind === "named";
|
||||
if (isNamedLocal && state.manualMemoState != null) {
|
||||
state.manualMemoState.decls.add(lvalId);
|
||||
}
|
||||
|
||||
const maybeDep = this.collectMaybeMemoDependencies(value, state);
|
||||
if (lvalId != null) {
|
||||
if (maybeDep != null) {
|
||||
temporaries.set(lvalId, maybeDep);
|
||||
} else if (isNamedLocal) {
|
||||
temporaries.set(lvalId, {
|
||||
root: {
|
||||
kind: "NamedLocal",
|
||||
value: { ...(instr.lvalue as Place) },
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override visitScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
state: CompilerError
|
||||
state: VisitorState
|
||||
): void {
|
||||
this.traverseScope(scopeBlock, state);
|
||||
|
||||
if (
|
||||
state.manualMemoState != null &&
|
||||
state.manualMemoState.depsFromSource != null
|
||||
) {
|
||||
for (const dep of scopeBlock.scope.dependencies) {
|
||||
validateInferredDep(
|
||||
dep,
|
||||
this.temporaries,
|
||||
state.manualMemoState.decls,
|
||||
state.manualMemoState.depsFromSource,
|
||||
state.errors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Record scopes that exist in the AST so we can later check to see if
|
||||
* effect dependencies which should be memoized (have a scope assigned)
|
||||
@@ -69,19 +306,54 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
|
||||
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: CompilerError
|
||||
state: VisitorState
|
||||
): void {
|
||||
this.traverseInstruction(instruction, state);
|
||||
if (
|
||||
instruction.value.kind === "StartMemoize" ||
|
||||
instruction.value.kind === "FinishMemoize"
|
||||
) {
|
||||
for (const value of eachInstructionValueOperand(instruction.value)) {
|
||||
this.recordTemporaries(instruction, state);
|
||||
if (instruction.value.kind === "StartMemoize") {
|
||||
let depsFromSource: Array<ManualMemoDependency> | null = null;
|
||||
if (instruction.value.deps != null) {
|
||||
depsFromSource = instruction.value.deps;
|
||||
}
|
||||
CompilerError.invariant(state.manualMemoState == null, {
|
||||
reason: "Unexpected nested StartMemoize instructions",
|
||||
description: `Bad manual memoization ids: ${state.manualMemoState?.manualMemoId}, ${instruction.value.manualMemoId}`,
|
||||
loc: instruction.value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
state.manualMemoState = {
|
||||
decls: new Set(),
|
||||
depsFromSource,
|
||||
manualMemoId: instruction.value.manualMemoId,
|
||||
};
|
||||
}
|
||||
if (instruction.value.kind === "FinishMemoize") {
|
||||
CompilerError.invariant(
|
||||
state.manualMemoState != null &&
|
||||
state.manualMemoState.manualMemoId === instruction.value.manualMemoId,
|
||||
{
|
||||
reason: "Unexpected mismatch between StartMemoize and FinishMemoize",
|
||||
description: `Encountered StartMemoize id=${state.manualMemoState?.manualMemoId} followed by FinishMemoize id=${instruction.value.manualMemoId}`,
|
||||
loc: instruction.value.loc,
|
||||
suggestions: null,
|
||||
}
|
||||
);
|
||||
state.manualMemoState = null;
|
||||
}
|
||||
|
||||
const isDep = instruction.value.kind === "StartMemoize";
|
||||
const isDecl =
|
||||
instruction.value.kind === "FinishMemoize" && !instruction.value.pruned;
|
||||
if (isDep || isDecl) {
|
||||
for (const value of eachInstructionValueOperand(
|
||||
instruction.value as InstructionValue
|
||||
)) {
|
||||
if (
|
||||
isMutable(instruction as Instruction, value) ||
|
||||
isUnmemoized(value.identifier, this.scopes)
|
||||
(isDecl && isUnmemoized(value.identifier, this.scopes))
|
||||
) {
|
||||
state.push({
|
||||
state.errors.push({
|
||||
reason:
|
||||
"This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized",
|
||||
description: null,
|
||||
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function App({ text, hasDeps }) {
|
||||
const resolvedText = useMemo(
|
||||
() => {
|
||||
return text.toUpperCase();
|
||||
},
|
||||
hasDeps ? null : [text] // should be DCE'd
|
||||
);
|
||||
return resolvedText;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: App,
|
||||
params: ["TodoAdd"],
|
||||
isComponent: "TodoAdd",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function App(t0) {
|
||||
const $ = useMemoCache(2);
|
||||
const { text, hasDeps } = t0;
|
||||
|
||||
hasDeps ? null : [text];
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== text) {
|
||||
t2 = text.toUpperCase();
|
||||
$[0] = text;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
const resolvedText = t1;
|
||||
return resolvedText;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: App,
|
||||
params: ["TodoAdd"],
|
||||
isComponent: "TodoAdd",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
-2
@@ -46,8 +46,6 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 11 | });
|
||||
| ^^^^ [ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (7:11)
|
||||
|
||||
[ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (7:11)
|
||||
12 |
|
||||
13 | // The ref is modified later, extending its range and preventing memoization of onChange
|
||||
14 | const reset = () => {
|
||||
|
||||
-2
@@ -43,8 +43,6 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 11 | });
|
||||
| ^^^^ [ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (7:11)
|
||||
|
||||
[ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (7:11)
|
||||
12 |
|
||||
13 | // The ref is modified later, extending its range and preventing memoization of onChange
|
||||
14 | ref.current.inner = null;
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useMemo } from "react";
|
||||
|
||||
// react-hooks-deps would error on this code (complex expression in depslist),
|
||||
// so Forget could bailout here
|
||||
function App({ text, hasDeps }) {
|
||||
const resolvedText = useMemo(
|
||||
() => {
|
||||
return text.toUpperCase();
|
||||
},
|
||||
hasDeps ? null : [text] // should be DCE'd
|
||||
);
|
||||
return resolvedText;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: App,
|
||||
params: ["TodoAdd"],
|
||||
isComponent: "TodoAdd",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
8 | return text.toUpperCase();
|
||||
9 | },
|
||||
> 10 | hasDeps ? null : [text] // should be DCE'd
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ [ReactForget] InvalidReact: Expected the dependency list for useMemo to be an array literal without rest spreads (10:10)
|
||||
11 | );
|
||||
12 | return resolvedText;
|
||||
13 | }
|
||||
```
|
||||
|
||||
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
// react-hooks-deps would error on this code (complex expression in depslist),
|
||||
// so Forget could bailout here
|
||||
function App({ text, hasDeps }) {
|
||||
const resolvedText = useMemo(
|
||||
() => {
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
// False positive as more specific memoization always results
|
||||
// in fewer memo block executions.
|
||||
// Precisely:
|
||||
// x_new != x_prev does not imply x.y.z_new != x.y.z_prev
|
||||
// x.y.z_new != x.y.z_prev does imply x_new != x_prev
|
||||
// One fix would be to depend on optional chains
|
||||
function useHook(x) {
|
||||
return useCallback(() => [x.y.z], [x]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `x.y.z`, but the source dependencies were [x]
|
||||
```
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
// False positive as more specific memoization always results
|
||||
// in fewer memo block executions.
|
||||
// Precisely:
|
||||
// x_new != x_prev does not imply x.y.z_new != x.y.z_prev
|
||||
// x.y.z_new != x.y.z_prev does imply x_new != x_prev
|
||||
// One fix would be to depend on optional chains
|
||||
function useHook(x) {
|
||||
return useCallback(() => [x.y.z], [x]);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// False positive as more specific memoization always results
|
||||
// in fewer memo block executions.
|
||||
// Precisely:
|
||||
// x_new != x_prev does not imply x.y.z_new != x.y.z_prev
|
||||
// x.y.z_new != x.y.z_prev does imply x_new != x_prev
|
||||
function useHook(x) {
|
||||
return useMemo(() => [x.y.z], [x]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `x.y.z`, but the source dependencies were [x]
|
||||
```
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// False positive as more specific memoization always results
|
||||
// in fewer memo block executions.
|
||||
// Precisely:
|
||||
// x_new != x_prev does not imply x.y.z_new != x.y.z_prev
|
||||
// x.y.z_new != x.y.z_prev does imply x_new != x_prev
|
||||
function useHook(x) {
|
||||
return useMemo(() => [x.y.z], [x]);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
// This is a false positive as Forget's inferred memoization
|
||||
// invalidates strictly less than source. We currently do not
|
||||
// track transitive deps / invalidations of manual memo deps
|
||||
// because of implementation complexity
|
||||
function useFoo() {
|
||||
const val = [1, 2, 3];
|
||||
|
||||
return useMemo(() => {
|
||||
return identity(val);
|
||||
}, [val]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
10 | const val = [1, 2, 3];
|
||||
11 |
|
||||
> 12 | return useMemo(() => {
|
||||
| ^^^^^^^
|
||||
> 13 | return identity(val);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 14 | }, [val]);
|
||||
| ^^^^ [ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (12:14)
|
||||
15 | }
|
||||
16 |
|
||||
17 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
// This is a false positive as Forget's inferred memoization
|
||||
// invalidates strictly less than source. We currently do not
|
||||
// track transitive deps / invalidations of manual memo deps
|
||||
// because of implementation complexity
|
||||
function useFoo() {
|
||||
const val = [1, 2, 3];
|
||||
|
||||
return useMemo(() => {
|
||||
return identity(val);
|
||||
}, [val]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// This case is already unsound in source, so we can safely bailout
|
||||
function Foo(props) {
|
||||
let x = [];
|
||||
x.push(props);
|
||||
|
||||
// makeArray() is captured, but depsList contains [props]
|
||||
const cb = useCallback(() => [x], [x]);
|
||||
|
||||
x = makeArray();
|
||||
|
||||
return cb;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
10 |
|
||||
11 | // makeArray() is captured, but depsList contains [props]
|
||||
> 12 | const cb = useCallback(() => [x], [x]);
|
||||
| ^^^^^^^^^ [ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (12:12)
|
||||
|
||||
[ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (12:12)
|
||||
13 |
|
||||
14 | x = makeArray();
|
||||
15 |
|
||||
```
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// This case is already unsound in source, so we can safely bailout
|
||||
function Foo(props) {
|
||||
let x = [];
|
||||
x.push(props);
|
||||
|
||||
// makeArray() is captured, but depsList contains [props]
|
||||
const cb = useCallback(() => [x], [x]);
|
||||
|
||||
x = makeArray();
|
||||
|
||||
return cb;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function useHook(maybeRef) {
|
||||
return useCallback(() => {
|
||||
return [maybeRef.current];
|
||||
}, [maybeRef]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `maybeRef.current`, but the source dependencies were [maybeRef]
|
||||
```
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function useHook(maybeRef) {
|
||||
return useCallback(() => {
|
||||
return [maybeRef.current];
|
||||
}, [maybeRef]);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function useHook(maybeRef, shouldRead) {
|
||||
return useMemo(() => {
|
||||
return () => [maybeRef.current];
|
||||
}, [shouldRead, maybeRef]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `maybeRef.current`, but the source dependencies were [shouldRead, maybeRef]
|
||||
```
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function useHook(maybeRef, shouldRead) {
|
||||
return useMemo(() => {
|
||||
return () => [maybeRef.current];
|
||||
}, [shouldRead, maybeRef]);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
// False positive:
|
||||
// We currently bail out on this because we don't understand
|
||||
// that `() => [x]` gets pruned because `x` always invalidates.
|
||||
function useFoo(props) {
|
||||
const x = [];
|
||||
useHook();
|
||||
x.push(props);
|
||||
|
||||
return useCallback(() => [x], [x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
11 | x.push(props);
|
||||
12 |
|
||||
> 13 | return useCallback(() => [x], [x]);
|
||||
| ^^^^^^^^^ [ReactForget] InvalidReact: This value was manually memoized, but cannot be memoized under Forget because it may be mutated after it is memoized (13:13)
|
||||
14 | }
|
||||
15 |
|
||||
16 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
// False positive:
|
||||
// We currently bail out on this because we don't understand
|
||||
// that `() => [x]` gets pruned because `x` always invalidates.
|
||||
function useFoo(props) {
|
||||
const x = [];
|
||||
useHook();
|
||||
x.push(props);
|
||||
|
||||
return useCallback(() => [x], [x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
// This is technically a false positive, but source is already breaking
|
||||
// `exhaustive-deps` lint rule (and can be considered invalid).
|
||||
function useHook(x) {
|
||||
const aliasedX = x;
|
||||
const aliasedProp = x.y.z;
|
||||
|
||||
return useCallback(() => [aliasedX, x.y.z], [x, aliasedProp]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `aliasedX`, but the source dependencies were [x, aliasedProp]
|
||||
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `x.y.z`, but the source dependencies were [x, aliasedProp]
|
||||
```
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
// This is technically a false positive, but source is already breaking
|
||||
// `exhaustive-deps` lint rule (and can be considered invalid).
|
||||
function useHook(x) {
|
||||
const aliasedX = x;
|
||||
const aliasedProp = x.y.z;
|
||||
|
||||
return useCallback(() => [aliasedX, x.y.z], [x, aliasedProp]);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useCallback(() => {
|
||||
return {
|
||||
value: propB?.x.y,
|
||||
other: propA,
|
||||
};
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propB`, but the source dependencies were [propA, propB.x.y]
|
||||
```
|
||||
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useCallback(() => {
|
||||
return {
|
||||
value: propB?.x.y,
|
||||
other: propA,
|
||||
};
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useCallback(() => {
|
||||
const x = {};
|
||||
if (propA?.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA?.a, propB.x.y]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]
|
||||
```
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useCallback(() => {
|
||||
const x = {};
|
||||
if (propA?.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA?.a, propB.x.y]);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function Component({ propA }) {
|
||||
return useCallback(() => {
|
||||
return propA.x();
|
||||
}, [propA.x]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propA`, but the source dependencies were [propA.x]
|
||||
```
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function Component({ propA }) {
|
||||
return useCallback(() => {
|
||||
return propA.x();
|
||||
}, [propA.x]);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
// This is technically a false positive, but source is already breaking
|
||||
// `exhaustive-deps` lint rule (and can be considered invalid).
|
||||
function useHook(x) {
|
||||
const aliasedX = x;
|
||||
const aliasedProp = x.y.z;
|
||||
|
||||
return useMemo(() => [x, x.y.z], [aliasedX, aliasedProp]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `x`, but the source dependencies were [aliasedX, aliasedProp]
|
||||
```
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
// This is technically a false positive, but source is already breaking
|
||||
// `exhaustive-deps` lint rule (and can be considered invalid).
|
||||
function useHook(x) {
|
||||
const aliasedX = x;
|
||||
const aliasedProp = x.y.z;
|
||||
|
||||
return useMemo(() => [x, x.y.z], [aliasedX, aliasedProp]);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// We currently only recognize "hoistable" values (e.g. variable reads
|
||||
// and property loads from named variables) in the source depslist.
|
||||
// This makes validation logic simpler and follows the same constraints
|
||||
// from the eslint react-hooks-deps plugin.
|
||||
function Foo(props) {
|
||||
const x = makeArray(props);
|
||||
// react-hooks-deps lint would already fail here
|
||||
return useMemo(() => [x[0]], [x[0]]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ val: 1 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
11 | const x = makeArray(props);
|
||||
12 | // react-hooks-deps lint would already fail here
|
||||
> 13 | return useMemo(() => [x[0]], [x[0]]);
|
||||
| ^^^^ [ReactForget] InvalidReact: Expected the dependency list for useMemo to be an array of simple expressions (13:13)
|
||||
14 | }
|
||||
15 |
|
||||
16 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// We currently only recognize "hoistable" values (e.g. variable reads
|
||||
// and property loads from named variables) in the source depslist.
|
||||
// This makes validation logic simpler and follows the same constraints
|
||||
// from the eslint react-hooks-deps plugin.
|
||||
function Foo(props) {
|
||||
const x = makeArray(props);
|
||||
// react-hooks-deps lint would already fail here
|
||||
return useMemo(() => [x[0]], [x[0]]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ val: 1 }],
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
const x = {};
|
||||
if (propA?.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA?.a, propB.x.y]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]
|
||||
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propB`, but the source dependencies were [propA.a, propB.x.y]
|
||||
```
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { mutate } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
const x = {};
|
||||
if (propA?.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA?.a, propB.x.y]);
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
const x = {};
|
||||
if (identity(null) ?? propA.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA.a, propB.x.y]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]
|
||||
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propB`, but the source dependencies were [propA.a, propB.x.y]
|
||||
```
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
const x = {};
|
||||
if (identity(null) ?? propA.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA.a, propB.x.y]);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA }) {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
value: propA.x().y,
|
||||
};
|
||||
}, [propA.x]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propA`, but the source dependencies were [propA.x]
|
||||
```
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA }) {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
value: propA.x().y,
|
||||
};
|
||||
}, [propA.x]);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA }) {
|
||||
return useMemo(() => {
|
||||
return propA.x();
|
||||
}, [propA.x]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `propA`, but the source dependencies were [propA.x]
|
||||
```
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA }) {
|
||||
return useMemo(() => {
|
||||
return propA.x();
|
||||
}, [propA.x]);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// Here, Forget infers that the memo block dependency is input1
|
||||
// 1. StartMemoize is emitted before the function expression
|
||||
// (and thus before the depslist arg and its rvalues)
|
||||
// 2. x and y's overlapping reactive scopes forces y's reactive
|
||||
// scope to be extended to after the `mutate(x)` call, after
|
||||
// the StartMemoize instruction.
|
||||
// While this is technically a false positive, this example would
|
||||
// already fail the exhaustive-deps eslint rule.
|
||||
function useFoo(input1) {
|
||||
const x = {};
|
||||
const y = [input1];
|
||||
const memoized = useMemo(() => {
|
||||
return [y];
|
||||
}, [(mutate(x), y)]);
|
||||
|
||||
return [x, memoized];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Todo: Could not preserve manual memoization because an inferred dependency does not match the dependency list in source. The inferred dependency was `input1`, but the source dependencies were [y]
|
||||
```
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// Here, Forget infers that the memo block dependency is input1
|
||||
// 1. StartMemoize is emitted before the function expression
|
||||
// (and thus before the depslist arg and its rvalues)
|
||||
// 2. x and y's overlapping reactive scopes forces y's reactive
|
||||
// scope to be extended to after the `mutate(x)` call, after
|
||||
// the StartMemoize instruction.
|
||||
// While this is technically a false positive, this example would
|
||||
// already fail the exhaustive-deps eslint rule.
|
||||
function useFoo(input1) {
|
||||
const x = {};
|
||||
const y = [input1];
|
||||
const memoized = useMemo(() => {
|
||||
return [y];
|
||||
}, [(mutate(x), y)]);
|
||||
|
||||
return [x, memoized];
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
// We technically do not need to bailout here if we can check
|
||||
// `someHelper`'s reactive deps are a subset of depslist from
|
||||
// source. This check is somewhat incompatible with our current
|
||||
// representation of manual memoization in HIR, so we bail out
|
||||
// for now.
|
||||
function Component(props) {
|
||||
const x = useMemo(someHelper, []);
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
7 | // for now.
|
||||
8 | function Component(props) {
|
||||
> 9 | const x = useMemo(someHelper, []);
|
||||
| ^^^^^^^^^^ [ReactForget] InvalidReact: Expected the first argument of useMemo to be an inline function expression (9:9)
|
||||
10 | return x;
|
||||
11 | }
|
||||
12 |
|
||||
```
|
||||
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
// We technically do not need to bailout here if we can check
|
||||
// `someHelper`'s reactive deps are a subset of depslist from
|
||||
// source. This check is somewhat incompatible with our current
|
||||
// representation of manual memoization in HIR, so we bail out
|
||||
// for now.
|
||||
function Component(props) {
|
||||
const x = useMemo(someHelper, []);
|
||||
return x;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// This is currently considered valid because we don't ensure that every
|
||||
// instruction within manual memoization gets assigned to a reactive scope
|
||||
// (i.e. inferred non-mutable or non-escaping values don't get memoized)
|
||||
function useFoo({ minWidth, styles, setStyles }) {
|
||||
useMemo(() => {
|
||||
if (styles.width > minWidth) {
|
||||
setStyles(styles);
|
||||
}
|
||||
}, [styles, minWidth, setStyles]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ minWidth: 2, styles: { width: 1 }, setStyles: () => {} }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// This is currently considered valid because we don't ensure that every
|
||||
// instruction within manual memoization gets assigned to a reactive scope
|
||||
// (i.e. inferred non-mutable or non-escaping values don't get memoized)
|
||||
function useFoo(t0) {
|
||||
const { minWidth, styles, setStyles } = t0;
|
||||
let t1;
|
||||
if (styles.width > minWidth) {
|
||||
setStyles(styles);
|
||||
}
|
||||
t1 = undefined;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ minWidth: 2, styles: { width: 1 }, setStyles: () => {} }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// This is currently considered valid because we don't ensure that every
|
||||
// instruction within manual memoization gets assigned to a reactive scope
|
||||
// (i.e. inferred non-mutable or non-escaping values don't get memoized)
|
||||
function useFoo({ minWidth, styles, setStyles }) {
|
||||
useMemo(() => {
|
||||
if (styles.width > minWidth) {
|
||||
setStyles(styles);
|
||||
}
|
||||
}, [styles, minWidth, setStyles]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ minWidth: 2, styles: { width: 1 }, setStyles: () => {} }],
|
||||
};
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// Todo: we currently only generate a `constVal` declaration when
|
||||
// validatePreserveExistingMemoizationGuarantees is enabled, as the
|
||||
// StartMemoize instruction uses `constVal`.
|
||||
// Fix is to rewrite StartMemoize instructions to remove constant
|
||||
// propagated values
|
||||
function useFoo() {
|
||||
const constVal = 0;
|
||||
|
||||
return useMemo(() => [constVal], [constVal]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
// Todo: we currently only generate a `constVal` declaration when
|
||||
// validatePreserveExistingMemoizationGuarantees is enabled, as the
|
||||
// StartMemoize instruction uses `constVal`.
|
||||
// Fix is to rewrite StartMemoize instructions to remove constant
|
||||
// propagated values
|
||||
function useFoo() {
|
||||
const $ = useMemoCache(1);
|
||||
const constVal = 0;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [0];
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
t0 = t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [0]
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// Todo: we currently only generate a `constVal` declaration when
|
||||
// validatePreserveExistingMemoizationGuarantees is enabled, as the
|
||||
// StartMemoize instruction uses `constVal`.
|
||||
// Fix is to rewrite StartMemoize instructions to remove constant
|
||||
// propagated values
|
||||
function useFoo() {
|
||||
const constVal = 0;
|
||||
|
||||
return useMemo(() => [constVal], [constVal]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
const x = propB.x.y;
|
||||
return useCallback(() => {
|
||||
return sum(propA.x, x);
|
||||
}, [propA.x, x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(3);
|
||||
const { propA, propB } = t0;
|
||||
const x = propB.x.y;
|
||||
let t1;
|
||||
if ($[0] !== propA.x || $[1] !== x) {
|
||||
t1 = () => sum(propA.x, x);
|
||||
$[0] = propA.x;
|
||||
$[1] = x;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
const x = propB.x.y;
|
||||
return useCallback(() => {
|
||||
return sum(propA.x, x);
|
||||
}, [propA.x, x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(props) {
|
||||
let contextVar;
|
||||
if (props.cond) {
|
||||
contextVar = { val: 2 };
|
||||
} else {
|
||||
contextVar = {};
|
||||
}
|
||||
|
||||
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
|
||||
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(props) {
|
||||
const $ = useMemoCache(6);
|
||||
let contextVar;
|
||||
if ($[0] !== props.cond) {
|
||||
if (props.cond) {
|
||||
contextVar = { val: 2 };
|
||||
} else {
|
||||
contextVar = {};
|
||||
}
|
||||
$[0] = props.cond;
|
||||
$[1] = contextVar;
|
||||
} else {
|
||||
contextVar = $[1];
|
||||
}
|
||||
|
||||
const t0 = contextVar;
|
||||
let t1;
|
||||
if ($[2] !== t0.val) {
|
||||
t1 = () => [contextVar.val];
|
||||
$[2] = t0.val;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
contextVar;
|
||||
const cb = t1;
|
||||
let t2;
|
||||
if ($[4] !== cb) {
|
||||
t2 = <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
$[4] = cb;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}</div>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(props) {
|
||||
let contextVar;
|
||||
if (props.cond) {
|
||||
contextVar = { val: 2 };
|
||||
} else {
|
||||
contextVar = {};
|
||||
}
|
||||
|
||||
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
|
||||
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// This case is fine, as all reassignments happen before the useCallback
|
||||
function Foo(props) {
|
||||
let x = [];
|
||||
x.push(props);
|
||||
x = makeArray();
|
||||
|
||||
const cb = useCallback(() => [x], [x]);
|
||||
|
||||
return cb;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// This case is fine, as all reassignments happen before the useCallback
|
||||
function Foo(props) {
|
||||
const $ = useMemoCache(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
x = [];
|
||||
x.push(props);
|
||||
x = makeArray();
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = () => [x];
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
x;
|
||||
const cb = t1;
|
||||
return cb;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// This case is fine, as all reassignments happen before the useCallback
|
||||
function Foo(props) {
|
||||
let x = [];
|
||||
x.push(props);
|
||||
x = makeArray();
|
||||
|
||||
const cb = useCallback(() => [x], [x]);
|
||||
|
||||
return cb;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useCallback(() => {
|
||||
if (propA) {
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 1, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(3);
|
||||
const { propA, propB } = t0;
|
||||
let t1;
|
||||
if ($[0] !== propA || $[1] !== propB.x.y) {
|
||||
t1 = () => {
|
||||
if (propA) {
|
||||
return { value: propB.x.y };
|
||||
}
|
||||
};
|
||||
$[0] = propA;
|
||||
$[1] = propB.x.y;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 1, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useCallback(() => {
|
||||
if (propA) {
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 1, propB: { x: { y: [] } } }],
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, useState } from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
// useCallback-produced values can exist in nested reactive blocks, as long
|
||||
// as their reactive dependencies are a subset of depslist from source
|
||||
function useFoo(minWidth, otherProp) {
|
||||
const [width, setWidth] = useState(1);
|
||||
const x = [];
|
||||
const style = useCallback(() => {
|
||||
return {
|
||||
width: Math.max(minWidth, width),
|
||||
};
|
||||
}, [width, minWidth]);
|
||||
arrayPush(x, otherProp);
|
||||
return [style, x];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, "other"],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
unstable_useMemoCache as useMemoCache,
|
||||
} from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
// useCallback-produced values can exist in nested reactive blocks, as long
|
||||
// as their reactive dependencies are a subset of depslist from source
|
||||
function useFoo(minWidth, otherProp) {
|
||||
const $ = useMemoCache(11);
|
||||
const [width] = useState(1);
|
||||
let style;
|
||||
let x;
|
||||
if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) {
|
||||
x = [];
|
||||
let t0;
|
||||
if ($[5] !== minWidth || $[6] !== width) {
|
||||
t0 = () => ({ width: Math.max(minWidth, width) });
|
||||
$[5] = minWidth;
|
||||
$[6] = width;
|
||||
$[7] = t0;
|
||||
} else {
|
||||
t0 = $[7];
|
||||
}
|
||||
style = t0;
|
||||
|
||||
arrayPush(x, otherProp);
|
||||
$[0] = width;
|
||||
$[1] = minWidth;
|
||||
$[2] = otherProp;
|
||||
$[3] = style;
|
||||
$[4] = x;
|
||||
} else {
|
||||
style = $[3];
|
||||
x = $[4];
|
||||
}
|
||||
let t0;
|
||||
if ($[8] !== style || $[9] !== x) {
|
||||
t0 = [style, x];
|
||||
$[8] = style;
|
||||
$[9] = x;
|
||||
$[10] = t0;
|
||||
} else {
|
||||
t0 = $[10];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, "other"],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) ["[[ function params=0 ]]",["other"]]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, useState } from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
// useCallback-produced values can exist in nested reactive blocks, as long
|
||||
// as their reactive dependencies are a subset of depslist from source
|
||||
function useFoo(minWidth, otherProp) {
|
||||
const [width, setWidth] = useState(1);
|
||||
const x = [];
|
||||
const style = useCallback(() => {
|
||||
return {
|
||||
width: Math.max(minWidth, width),
|
||||
};
|
||||
}, [width, minWidth]);
|
||||
arrayPush(x, otherProp);
|
||||
return [style, x];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, "other"],
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function useHook(propA, propB) {
|
||||
return useCallback(() => {
|
||||
const x = {};
|
||||
if (identity(null) ?? propA.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA.a, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useHook,
|
||||
params: [{ a: 1 }, { x: { y: 3 } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function useHook(propA, propB) {
|
||||
const $ = useMemoCache(3);
|
||||
let t0;
|
||||
if ($[0] !== propA.a || $[1] !== propB.x.y) {
|
||||
t0 = () => {
|
||||
const x = {};
|
||||
if (identity(null) ?? propA.a) {
|
||||
mutate(x);
|
||||
return { value: propB.x.y };
|
||||
}
|
||||
};
|
||||
$[0] = propA.a;
|
||||
$[1] = propB.x.y;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useHook,
|
||||
params: [{ a: 1 }, { x: { y: 3 } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { identity, mutate } from "shared-runtime";
|
||||
|
||||
function useHook(propA, propB) {
|
||||
return useCallback(() => {
|
||||
const x = {};
|
||||
if (identity(null) ?? propA.a) {
|
||||
mutate(x);
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA.a, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useHook,
|
||||
params: [{ a: 1 }, { x: { y: 3 } }],
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
// It's correct to produce memo blocks with fewer deps than source
|
||||
function useFoo(a, b) {
|
||||
return useCallback(() => [a], [a, b]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [1, 2],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
// It's correct to produce memo blocks with fewer deps than source
|
||||
function useFoo(a, b) {
|
||||
const $ = useMemoCache(2);
|
||||
let t0;
|
||||
if ($[0] !== a) {
|
||||
t0 = () => [a];
|
||||
$[0] = a;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [1, 2],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
// It's correct to produce memo blocks with fewer deps than source
|
||||
function useFoo(a, b) {
|
||||
return useCallback(() => [a], [a, b]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [1, 2],
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function useFoo() {
|
||||
const val = [1, 2, 3];
|
||||
|
||||
return useCallback(() => {
|
||||
return sum(...val);
|
||||
}, [val]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function useFoo() {
|
||||
const $ = useMemoCache(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = [1, 2, 3];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const val = t0;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = () => sum(...val);
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function useFoo() {
|
||||
const val = [1, 2, 3];
|
||||
|
||||
return useCallback(() => {
|
||||
return sum(...val);
|
||||
}, [val]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { CONST_STRING0 } from "shared-runtime";
|
||||
|
||||
// It's correct to infer a useCallback block has no reactive dependencies
|
||||
function useFoo() {
|
||||
return useCallback(() => [CONST_STRING0], [CONST_STRING0]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { CONST_STRING0 } from "shared-runtime";
|
||||
|
||||
// It's correct to infer a useCallback block has no reactive dependencies
|
||||
function useFoo() {
|
||||
const $ = useMemoCache(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = () => [CONST_STRING0];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { CONST_STRING0 } from "shared-runtime";
|
||||
|
||||
// It's correct to infer a useCallback block has no reactive dependencies
|
||||
function useFoo() {
|
||||
return useCallback(() => [CONST_STRING0], [CONST_STRING0]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
+1
-1
@@ -9,7 +9,7 @@ function Component({ entity, children }) {
|
||||
// showMessage doesn't escape so we don't memoize it.
|
||||
// However, validatePreserveExistingMemoizationGuarantees only sees that the scope
|
||||
// doesn't exist, and thinks the memoization was missed instead of being intentionally dropped.
|
||||
const showMessage = useCallback(() => entity != null);
|
||||
const showMessage = useCallback(() => entity != null, [entity]);
|
||||
|
||||
if (!showMessage()) {
|
||||
return children;
|
||||
+1
-1
@@ -5,7 +5,7 @@ function Component({ entity, children }) {
|
||||
// showMessage doesn't escape so we don't memoize it.
|
||||
// However, validatePreserveExistingMemoizationGuarantees only sees that the scope
|
||||
// doesn't exist, and thinks the memoization was missed instead of being intentionally dropped.
|
||||
const showMessage = useCallback(() => entity != null);
|
||||
const showMessage = useCallback(() => entity != null, [entity]);
|
||||
|
||||
if (!showMessage()) {
|
||||
return children;
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo({ arr1, arr2, foo }) {
|
||||
const x = [arr1];
|
||||
|
||||
let y = [];
|
||||
|
||||
const getVal1 = useCallback(() => {
|
||||
return { x: 2 };
|
||||
}, []);
|
||||
|
||||
const getVal2 = useCallback(() => {
|
||||
return [y];
|
||||
}, [foo ? (y = x.concat(arr2)) : y]);
|
||||
|
||||
return <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ arr1: [1, 2], arr2: [3, 4], foo: true }],
|
||||
sequentialRenders: [
|
||||
{ arr1: [1, 2], arr2: [3, 4], foo: true },
|
||||
{ arr1: [1, 2], arr2: [3, 4], foo: false },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(t0) {
|
||||
const $ = useMemoCache(11);
|
||||
const { arr1, arr2, foo } = t0;
|
||||
let t1;
|
||||
if ($[0] !== arr1) {
|
||||
t1 = [arr1];
|
||||
$[0] = arr1;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
let t2;
|
||||
let getVal1;
|
||||
if ($[2] !== foo || $[3] !== x || $[4] !== arr2) {
|
||||
let y;
|
||||
y = [];
|
||||
let t3;
|
||||
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = () => ({ x: 2 });
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
getVal1 = t3;
|
||||
|
||||
t2 = () => [y];
|
||||
foo ? (y = x.concat(arr2)) : y;
|
||||
$[2] = foo;
|
||||
$[3] = x;
|
||||
$[4] = arr2;
|
||||
$[5] = t2;
|
||||
$[6] = getVal1;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
getVal1 = $[6];
|
||||
}
|
||||
const getVal2 = t2;
|
||||
let t3;
|
||||
if ($[8] !== getVal1 || $[9] !== getVal2) {
|
||||
t3 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
|
||||
$[8] = getVal1;
|
||||
$[9] = getVal2;
|
||||
$[10] = t3;
|
||||
} else {
|
||||
t3 = $[10];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ arr1: [1, 2], arr2: [3, 4], foo: true }],
|
||||
sequentialRenders: [
|
||||
{ arr1: [1, 2], arr2: [3, 4], foo: true },
|
||||
{ arr1: [1, 2], arr2: [3, 4], foo: false },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"val1":{"kind":"Function","result":{"x":2}},"val2":{"kind":"Function","result":[[[1,2],3,4]]},"shouldInvokeFns":true}</div>
|
||||
<div>{"val1":{"kind":"Function","result":{"x":2}},"val2":{"kind":"Function","result":[[]]},"shouldInvokeFns":true}</div>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo({ arr1, arr2, foo }) {
|
||||
const x = [arr1];
|
||||
|
||||
let y = [];
|
||||
|
||||
const getVal1 = useCallback(() => {
|
||||
return { x: 2 };
|
||||
}, []);
|
||||
|
||||
const getVal2 = useCallback(() => {
|
||||
return [y];
|
||||
}, [foo ? (y = x.concat(arr2)) : y]);
|
||||
|
||||
return <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ arr1: [1, 2], arr2: [3, 4], foo: true }],
|
||||
sequentialRenders: [
|
||||
{ arr1: [1, 2], arr2: [3, 4], foo: true },
|
||||
{ arr1: [1, 2], arr2: [3, 4], foo: false },
|
||||
],
|
||||
};
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
// We currently produce invalid output (incorrect scoping for `y` declaration)
|
||||
function useFoo(arr1, arr2) {
|
||||
const x = [arr1];
|
||||
|
||||
let y;
|
||||
const getVal = useCallback(() => {
|
||||
return { y };
|
||||
}, [((y = x.concat(arr2)), y)]);
|
||||
|
||||
return <Stringify getVal={getVal} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
// We currently produce invalid output (incorrect scoping for `y` declaration)
|
||||
function useFoo(arr1, arr2) {
|
||||
const $ = useMemoCache(7);
|
||||
let t0;
|
||||
if ($[0] !== arr1) {
|
||||
t0 = [arr1];
|
||||
$[0] = arr1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const x = t0;
|
||||
let t1;
|
||||
if ($[2] !== x || $[3] !== arr2) {
|
||||
let y;
|
||||
t1 = () => ({ y });
|
||||
|
||||
(y = x.concat(arr2)), y;
|
||||
$[2] = x;
|
||||
$[3] = arr2;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
const getVal = t1;
|
||||
let t2;
|
||||
if ($[5] !== getVal) {
|
||||
t2 = <Stringify getVal={getVal} shouldInvokeFns={true} />;
|
||||
$[5] = getVal;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"getVal":{"kind":"Function","result":{"y":[[1,2],3,4]}},"shouldInvokeFns":true}</div>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
// We currently produce invalid output (incorrect scoping for `y` declaration)
|
||||
function useFoo(arr1, arr2) {
|
||||
const x = [arr1];
|
||||
|
||||
let y;
|
||||
const getVal = useCallback(() => {
|
||||
return { y };
|
||||
}, [((y = x.concat(arr2)), y)]);
|
||||
|
||||
return <Stringify getVal={getVal} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
};
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
// Compiler can produce any memoization it finds valid if the
|
||||
// source listed no memo deps
|
||||
function Component({ propA }) {
|
||||
// @ts-ignore
|
||||
return useCallback(() => {
|
||||
return [propA];
|
||||
});
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
// Compiler can produce any memoization it finds valid if the
|
||||
// source listed no memo deps
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(2);
|
||||
const { propA } = t0;
|
||||
let t1;
|
||||
if ($[0] !== propA) {
|
||||
t1 = () => [propA];
|
||||
$[0] = propA;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
|
||||
// Compiler can produce any memoization it finds valid if the
|
||||
// source listed no memo deps
|
||||
function Component({ propA }) {
|
||||
// @ts-ignore
|
||||
return useCallback(() => {
|
||||
return [propA];
|
||||
});
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2 }],
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
const x = propB.x.y;
|
||||
return useMemo(() => {
|
||||
return sum(propA.x, x);
|
||||
}, [propA.x, x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(3);
|
||||
const { propA, propB } = t0;
|
||||
const x = propB.x.y;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== propA.x || $[1] !== x) {
|
||||
t2 = sum(propA.x, x);
|
||||
$[0] = propA.x;
|
||||
$[1] = x;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
t1 = t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) 5
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { sum } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
const x = propB.x.y;
|
||||
return useMemo(() => {
|
||||
return sum(propA.x, x);
|
||||
}, [propA.x, x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
value: identity(propB?.x.y),
|
||||
other: propA,
|
||||
};
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(5);
|
||||
const { propA, propB } = t0;
|
||||
let t1;
|
||||
|
||||
const t2 = propB?.x.y;
|
||||
let t3;
|
||||
if ($[0] !== t2) {
|
||||
t3 = identity(t2);
|
||||
$[0] = t2;
|
||||
$[1] = t3;
|
||||
} else {
|
||||
t3 = $[1];
|
||||
}
|
||||
let t4;
|
||||
if ($[2] !== t3 || $[3] !== propA) {
|
||||
t4 = { value: t3, other: propA };
|
||||
$[2] = t3;
|
||||
$[3] = propA;
|
||||
$[4] = t4;
|
||||
} else {
|
||||
t4 = $[4];
|
||||
}
|
||||
t1 = t4;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"value":[],"other":2}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
value: identity(propB?.x.y),
|
||||
other: propA,
|
||||
};
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
value: propB?.x.y,
|
||||
other: propA,
|
||||
};
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(3);
|
||||
const { propA, propB } = t0;
|
||||
let t1;
|
||||
|
||||
const t2 = propB?.x.y;
|
||||
let t3;
|
||||
if ($[0] !== t2 || $[1] !== propA) {
|
||||
t3 = { value: t2, other: propA };
|
||||
$[0] = t2;
|
||||
$[1] = propA;
|
||||
$[2] = t3;
|
||||
} else {
|
||||
t3 = $[2];
|
||||
}
|
||||
t1 = t3;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"value":[],"other":2}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
value: propB?.x.y,
|
||||
other: propA,
|
||||
};
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 2, propB: { x: { y: [] } } }],
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
if (propA) {
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 1, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = useMemoCache(2);
|
||||
const { propA, propB } = t0;
|
||||
let t1;
|
||||
bb6: {
|
||||
if (propA) {
|
||||
let t2;
|
||||
if ($[0] !== propB.x.y) {
|
||||
t2 = { value: propB.x.y };
|
||||
$[0] = propB.x.y;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
break bb6;
|
||||
}
|
||||
t1 = undefined;
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 1, propB: { x: { y: [] } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"value":[]}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component({ propA, propB }) {
|
||||
return useMemo(() => {
|
||||
if (propA) {
|
||||
return {
|
||||
value: propB.x.y,
|
||||
};
|
||||
}
|
||||
}, [propA, propB.x.y]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propA: 1, propB: { x: { y: [] } } }],
|
||||
};
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function useFoo(cond) {
|
||||
const sourceDep = 0;
|
||||
const derived1 = useMemo(() => {
|
||||
return identity(sourceDep);
|
||||
}, [sourceDep]);
|
||||
const derived2 = cond ?? Math.min(sourceDep, 1) ? 1 : 2;
|
||||
const derived3 = useMemo(() => {
|
||||
return identity(sourceDep);
|
||||
}, [sourceDep]);
|
||||
const derived4 = Math.min(sourceDep, -1) ?? cond ? 1 : 2;
|
||||
return [derived1, derived2, derived3, derived4];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [true],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function useFoo(cond) {
|
||||
const $ = useMemoCache(5);
|
||||
const sourceDep = 0;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = identity(0);
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
t0 = t1;
|
||||
const derived1 = t0;
|
||||
|
||||
const derived2 = cond ?? Math.min(0, 1) ? 1 : 2;
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = identity(0);
|
||||
$[1] = t3;
|
||||
} else {
|
||||
t3 = $[1];
|
||||
}
|
||||
t2 = t3;
|
||||
const derived3 = t2;
|
||||
|
||||
const derived4 = Math.min(0, -1) ?? cond ? 1 : 2;
|
||||
let t4;
|
||||
if ($[2] !== derived2 || $[3] !== derived4) {
|
||||
t4 = [derived1, derived2, derived3, derived4];
|
||||
$[2] = derived2;
|
||||
$[3] = derived4;
|
||||
$[4] = t4;
|
||||
} else {
|
||||
t4 = $[4];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [true],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [0,1,0,1]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function useFoo(cond) {
|
||||
const sourceDep = 0;
|
||||
const derived1 = useMemo(() => {
|
||||
return identity(sourceDep);
|
||||
}, [sourceDep]);
|
||||
const derived2 = cond ?? Math.min(sourceDep, 1) ? 1 : 2;
|
||||
const derived3 = useMemo(() => {
|
||||
return identity(sourceDep);
|
||||
}, [sourceDep]);
|
||||
const derived4 = Math.min(sourceDep, -1) ?? cond ? 1 : 2;
|
||||
return [derived1, derived2, derived3, derived4];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [true],
|
||||
};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useHook } from "shared-runtime";
|
||||
|
||||
// useMemo values may not be memoized in Forget output if we
|
||||
// infer that their deps always invalidate.
|
||||
// This is still correct as the useMemo in source was effectively
|
||||
// a no-op already.
|
||||
function useFoo(props) {
|
||||
const x = [];
|
||||
useHook();
|
||||
x.push(props);
|
||||
|
||||
return useMemo(() => [x], [x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useHook } from "shared-runtime";
|
||||
|
||||
// useMemo values may not be memoized in Forget output if we
|
||||
// infer that their deps always invalidate.
|
||||
// This is still correct as the useMemo in source was effectively
|
||||
// a no-op already.
|
||||
function useFoo(props) {
|
||||
const x = [];
|
||||
useHook();
|
||||
x.push(props);
|
||||
let t0;
|
||||
t0 = [x];
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[{}]]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useHook } from "shared-runtime";
|
||||
|
||||
// useMemo values may not be memoized in Forget output if we
|
||||
// infer that their deps always invalidate.
|
||||
// This is still correct as the useMemo in source was effectively
|
||||
// a no-op already.
|
||||
function useFoo(props) {
|
||||
const x = [];
|
||||
useHook();
|
||||
x.push(props);
|
||||
|
||||
return useMemo(() => [x], [x]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{}],
|
||||
};
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, useState } from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
// useMemo-produced values can exist in nested reactive blocks, as long
|
||||
// as their reactive dependencies are a subset of depslist from source
|
||||
function useFoo(minWidth, otherProp) {
|
||||
const [width, setWidth] = useState(1);
|
||||
const x = [];
|
||||
const style = useMemo(() => {
|
||||
return {
|
||||
width: Math.max(minWidth, width),
|
||||
};
|
||||
}, [width, minWidth]);
|
||||
arrayPush(x, otherProp);
|
||||
return [style, x];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, "other"],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
unstable_useMemoCache as useMemoCache,
|
||||
} from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
// useMemo-produced values can exist in nested reactive blocks, as long
|
||||
// as their reactive dependencies are a subset of depslist from source
|
||||
function useFoo(minWidth, otherProp) {
|
||||
const $ = useMemoCache(10);
|
||||
const [width] = useState(1);
|
||||
let style;
|
||||
let x;
|
||||
if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) {
|
||||
x = [];
|
||||
let t0;
|
||||
|
||||
const t1 = Math.max(minWidth, width);
|
||||
let t2;
|
||||
if ($[5] !== t1) {
|
||||
t2 = { width: t1 };
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
t0 = t2;
|
||||
style = t0;
|
||||
|
||||
arrayPush(x, otherProp);
|
||||
$[0] = width;
|
||||
$[1] = minWidth;
|
||||
$[2] = otherProp;
|
||||
$[3] = style;
|
||||
$[4] = x;
|
||||
} else {
|
||||
style = $[3];
|
||||
x = $[4];
|
||||
}
|
||||
let t0;
|
||||
if ($[7] !== style || $[8] !== x) {
|
||||
t0 = [style, x];
|
||||
$[7] = style;
|
||||
$[8] = x;
|
||||
$[9] = t0;
|
||||
} else {
|
||||
t0 = $[9];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, "other"],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{"width":2},["other"]]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo, useState } from "react";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
// useMemo-produced values can exist in nested reactive blocks, as long
|
||||
// as their reactive dependencies are a subset of depslist from source
|
||||
function useFoo(minWidth, otherProp) {
|
||||
const [width, setWidth] = useState(1);
|
||||
const x = [];
|
||||
const style = useMemo(() => {
|
||||
return {
|
||||
width: Math.max(minWidth, width),
|
||||
};
|
||||
}, [width, minWidth]);
|
||||
arrayPush(x, otherProp);
|
||||
return [style, x];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, "other"],
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// It's correct to produce memo blocks with fewer deps than source
|
||||
function useFoo(a, b) {
|
||||
return useMemo(() => [a], [a, b]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [1, 2],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
// It's correct to produce memo blocks with fewer deps than source
|
||||
function useFoo(a, b) {
|
||||
const $ = useMemoCache(2);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
t1 = [a];
|
||||
$[0] = a;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [1, 2],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [1]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// It's correct to produce memo blocks with fewer deps than source
|
||||
function useFoo(a, b) {
|
||||
return useMemo(() => [a], [a, b]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [1, 2],
|
||||
};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// It's correct to infer a useMemo value is non-allocating
|
||||
// and not provide it with a reactive scope
|
||||
function useFoo(num1, num2) {
|
||||
return useMemo(() => Math.min(num1, num2), [num1, num2]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, 3],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
|
||||
|
||||
// It's correct to infer a useMemo value is non-allocating
|
||||
// and not provide it with a reactive scope
|
||||
function useFoo(num1, num2) {
|
||||
const $ = useMemoCache(3);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== num1 || $[1] !== num2) {
|
||||
t1 = Math.min(num1, num2);
|
||||
$[0] = num1;
|
||||
$[1] = num2;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
t0 = t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, 3],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) 2
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
// It's correct to infer a useMemo value is non-allocating
|
||||
// and not provide it with a reactive scope
|
||||
function useFoo(num1, num2) {
|
||||
return useMemo(() => Math.min(num1, num2), [num1, num2]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [2, 3],
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user