Update base for Update on "[compiler] Custom type definitions in config"

Summary:
This PR allows custom type definitions for globals to be loaded in the Forget config. We use a slightly different representation than we do internally, in order to ensure that things are well formed before we pass them to the shape registry. The most fundamental issue is that we introduce a notion of IDs (expected to be unique) for types: we can introduce a type and then refer to that type later by a unique ID in a function return type if we have, for example, a global value that returns itself when called.

[ghstack-poisoned]
This commit is contained in:
Mike Vitousek
2024-08-16 15:05:19 -04:00
290 changed files with 5289 additions and 1470 deletions
@@ -104,6 +104,7 @@ import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLoca
import {outlineFunctions} from '../Optimization/OutlineFunctions';
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -244,6 +245,10 @@ function* runWithEnvironment(
validateNoSetStateInRender(hir);
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
inferReactivePlaces(hir);
yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
@@ -428,19 +428,11 @@ function lowerStatement(
loc: id.parentPath.node.loc ?? GeneratedSource,
});
continue;
} else if (!binding.path.get('id').isIdentifier()) {
builder.errors.push({
severity: ErrorSeverity.Todo,
reason: 'Unsupported variable declaration type for hoisting',
description: `variable "${
binding.identifier.name
}" declared with ${binding.path.get('id').type}`,
suggestions: null,
loc: id.parentPath.node.loc ?? GeneratedSource,
});
continue;
} else if (binding.kind !== 'const' && binding.kind !== 'var') {
// Avoid double errors on var declarations, which we do not plan to support anyways
} else if (
binding.kind !== 'const' &&
binding.kind !== 'var' &&
binding.kind !== 'let'
) {
builder.errors.push({
severity: ErrorSeverity.Todo,
reason: 'Handle non-const declarations for hoisting',
@@ -463,10 +455,17 @@ function lowerStatement(
reactive: false,
loc: id.node.loc ?? GeneratedSource,
};
const kind =
// Avoid double errors on var declarations, which we do not plan to support anyways
binding.kind === 'const' || binding.kind === 'var'
? InstructionKind.HoistedConst
: binding.kind === 'let'
? InstructionKind.HoistedLet
: assertExhaustive(binding.kind, 'Unexpected binding kind');
lowerValueToTemporary(builder, {
kind: 'DeclareContext',
lvalue: {
kind: InstructionKind.HoistedConst,
kind,
place,
},
loc: id.node.loc ?? GeneratedSource,
@@ -2386,6 +2385,57 @@ function lowerExpression(
case 'UpdateExpression': {
let expr = exprPath as NodePath<t.UpdateExpression>;
const argument = expr.get('argument');
if (argument.isMemberExpression()) {
const binaryOperator = expr.node.operator === '++' ? '+' : '-';
const leftExpr = argument as NodePath<t.MemberExpression>;
const {object, property, value} = lowerMemberExpression(
builder,
leftExpr,
);
// Store the previous value to a temporary
const previousValuePlace = lowerValueToTemporary(builder, value);
// Store the new value to a temporary
const updatedValue = lowerValueToTemporary(builder, {
kind: 'BinaryExpression',
operator: binaryOperator,
left: {...previousValuePlace},
right: lowerValueToTemporary(builder, {
kind: 'Primitive',
value: 1,
loc: GeneratedSource,
}),
loc: leftExpr.node.loc ?? GeneratedSource,
});
// Save the result back to the property
let newValuePlace;
if (typeof property === 'string') {
newValuePlace = lowerValueToTemporary(builder, {
kind: 'PropertyStore',
object: {...object},
property,
value: {...updatedValue},
loc: leftExpr.node.loc ?? GeneratedSource,
});
} else {
newValuePlace = lowerValueToTemporary(builder, {
kind: 'ComputedStore',
object: {...object},
property: {...property},
value: {...updatedValue},
loc: leftExpr.node.loc ?? GeneratedSource,
});
}
return {
kind: 'LoadLocal',
place: expr.node.prefix
? {...newValuePlace}
: {...previousValuePlace},
loc: exprLoc,
};
}
if (!argument.isIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
@@ -2837,6 +2887,21 @@ function isReorderableExpression(
allowLocalIdentifiers,
);
}
case 'LogicalExpression': {
const logical = expr as NodePath<t.LogicalExpression>;
return (
isReorderableExpression(
builder,
logical.get('left'),
allowLocalIdentifiers,
) &&
isReorderableExpression(
builder,
logical.get('right'),
allowLocalIdentifiers,
)
);
}
case 'ConditionalExpression': {
const conditional = expr as NodePath<t.ConditionalExpression>;
return (
@@ -223,7 +223,7 @@ const EnvironmentConfigSchema = z.object({
validateHooksUsage: z.boolean().default(true),
// Validate that ref values (`ref.current`) are not accessed during render.
validateRefAccessDuringRender: z.boolean().default(false),
validateRefAccessDuringRender: z.boolean().default(true),
/*
* Validates that setState is not unconditionally called during render, as it can lead to
@@ -231,6 +231,12 @@ const EnvironmentConfigSchema = z.object({
*/
validateNoSetStateInRender: z.boolean().default(true),
/**
* Validates that setState is not called directly within a passive effect (useEffect).
* Scheduling a setState (with an event listener, subscription, etc) is valid.
*/
validateNoSetStateInPassiveEffects: z.boolean().default(false),
/**
* Validates that the dependencies of all effect hooks are memoized. This helps ensure
* that Forget does not introduce infinite renders caused by a dependency changing,
@@ -18,6 +18,7 @@ import {
BuiltInUseReducerId,
BuiltInUseRefId,
BuiltInUseStateId,
BuiltInUseTransitionId,
ShapeRegistry,
addFunction,
addHook,
@@ -425,6 +426,17 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
BuiltInUseInsertionEffectHookId,
),
],
[
'useTransition',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInUseTransitionId},
calleeEffect: Effect.Read,
hookKind: 'useTransition',
returnValueKind: ValueKind.Frozen,
}),
],
[
'use',
addFunction(
@@ -741,6 +741,9 @@ export enum InstructionKind {
// hoisted const declarations
HoistedConst = 'HoistedConst',
// hoisted const declarations
HoistedLet = 'HoistedLet',
}
function _staticInvariantInstructionValueHasLocation(
@@ -858,7 +861,10 @@ export type InstructionValue =
| {
kind: 'DeclareContext';
lvalue: {
kind: InstructionKind.Let | InstructionKind.HoistedConst;
kind:
| InstructionKind.Let
| InstructionKind.HoistedConst
| InstructionKind.HoistedLet;
place: Place;
};
loc: SourceLocation;
@@ -1585,6 +1591,10 @@ export function isUseStateType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
}
export function isRefOrRefValue(id: Identifier): boolean {
return isUseRefType(id) || isRefValueType(id);
}
export function isSetStateType(id: Identifier): boolean {
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState';
}
@@ -1595,6 +1605,12 @@ export function isUseActionStateType(id: Identifier): boolean {
);
}
export function isStartTransitionType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInStartTransition'
);
}
export function isSetActionStateType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState'
@@ -1610,7 +1626,13 @@ export function isDispatcherType(id: Identifier): boolean {
}
export function isStableType(id: Identifier): boolean {
return isSetStateType(id) || isSetActionStateType(id) || isDispatcherType(id);
return (
isSetStateType(id) ||
isSetActionStateType(id) ||
isDispatcherType(id) ||
isUseRefType(id) ||
isStartTransitionType(id)
);
}
export function isUseEffectHookType(id: Identifier): boolean {
@@ -126,6 +126,7 @@ export type HookKind =
| 'useInsertionEffect'
| 'useMemo'
| 'useCallback'
| 'useTransition'
| 'Custom';
/*
@@ -209,6 +210,8 @@ export const BuiltInUseOperatorId = 'BuiltInUseOperator';
export const BuiltInUseReducerId = 'BuiltInUseReducer';
export const BuiltInDispatchId = 'BuiltInDispatch';
export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
export const BuiltInUseTransitionId = 'BuiltInUseTransition';
export const BuiltInStartTransitionId = 'BuiltInStartTransition';
// ShapeRegistry with default definitions for built-ins.
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
@@ -444,6 +447,25 @@ addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseTransitionId, [
['0', {kind: 'Primitive'}],
[
'1',
addFunction(
BUILTIN_SHAPES,
[],
{
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
},
BuiltInStartTransitionId,
),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
['0', {kind: 'Poly'}],
[
@@ -760,6 +760,9 @@ export function printLValue(lval: LValue): string {
case InstructionKind.HoistedConst: {
return `HoistedConst ${lvalue}$`;
}
case InstructionKind.HoistedLet: {
return `HoistedLet ${lvalue}$`;
}
default: {
assertExhaustive(lval.kind, `Unexpected lvalue kind \`${lval.kind}\``);
}
@@ -14,8 +14,7 @@ import {
LoweredFunction,
Place,
ReactiveScopeDependency,
isRefValueType,
isUseRefType,
isRefOrRefValue,
makeInstructionId,
} from '../HIR';
import {deadCodeElimination} from '../Optimization';
@@ -139,7 +138,7 @@ function infer(
name = dep.identifier.name;
}
if (isUseRefType(dep.identifier) || isRefValueType(dep.identifier)) {
if (isRefOrRefValue(dep.identifier)) {
/*
* TODO: this is a hack to ensure we treat functions which reference refs
* as having a capture and therefore being considered mutable. this ensures
@@ -11,6 +11,7 @@ import {
Identifier,
InstructionId,
InstructionKind,
isRefOrRefValue,
makeInstructionId,
Place,
} from '../HIR/HIR';
@@ -66,7 +67,9 @@ import {assertExhaustive} from '../Utils/utils';
*/
function infer(place: Place, instrId: InstructionId): void {
place.identifier.mutableRange.end = makeInstructionId(instrId + 1);
if (!isRefOrRefValue(place.identifier)) {
place.identifier.mutableRange.end = makeInstructionId(instrId + 1);
}
}
function inferPlace(
@@ -171,7 +174,10 @@ export function inferMutableLifetimes(
const declaration = contextVariableDeclarationInstructions.get(
instr.value.lvalue.place.identifier,
);
if (declaration != null) {
if (
declaration != null &&
!isRefOrRefValue(instr.value.lvalue.place.identifier)
) {
const range = instr.value.lvalue.place.identifier.mutableRange;
if (range.start === 0) {
range.start = declaration;
@@ -5,7 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import {HIRFunction, Identifier, InstructionId} from '../HIR/HIR';
import {
HIRFunction,
Identifier,
InstructionId,
isRefOrRefValue,
} from '../HIR/HIR';
import DisjointSet from '../Utils/DisjointSet';
export function inferMutableRangesForAlias(
@@ -19,7 +24,8 @@ export function inferMutableRangesForAlias(
* mutated.
*/
const mutatingIdentifiers = [...aliasSet].filter(
id => id.mutableRange.end - id.mutableRange.start > 1,
id =>
id.mutableRange.end - id.mutableRange.start > 1 && !isRefOrRefValue(id),
);
if (mutatingIdentifiers.length > 0) {
@@ -36,7 +42,10 @@ export function inferMutableRangesForAlias(
* last mutation.
*/
for (const alias of aliasSet) {
if (alias.mutableRange.end < lastMutatingInstructionId) {
if (
alias.mutableRange.end < lastMutatingInstructionId &&
!isRefOrRefValue(alias)
) {
alias.mutableRange.end = lastMutatingInstructionId as InstructionId;
}
}
@@ -30,8 +30,7 @@ import {
isArrayType,
isMutableEffect,
isObjectType,
isRefValueType,
isUseRefType,
isRefOrRefValue,
} from '../HIR/HIR';
import {FunctionSignature} from '../HIR/ObjectShape';
import {
@@ -523,10 +522,7 @@ class InferenceState {
break;
}
case Effect.Mutate: {
if (
isRefValueType(place.identifier) ||
isUseRefType(place.identifier)
) {
if (isRefOrRefValue(place.identifier)) {
// no-op: refs are validate via ValidateNoRefAccessInRender
} else if (valueKind.kind === ValueKind.Context) {
functionEffect = {
@@ -567,10 +563,7 @@ class InferenceState {
break;
}
case Effect.Store: {
if (
isRefValueType(place.identifier) ||
isUseRefType(place.identifier)
) {
if (isRefOrRefValue(place.identifier)) {
// no-op: refs are validate via ValidateNoRefAccessInRender
} else if (valueKind.kind === ValueKind.Context) {
functionEffect = {
@@ -994,6 +994,13 @@ function codegenTerminal(
loc: iterableItem.loc,
suggestions: null,
});
case InstructionKind.HoistedLet:
CompilerError.invariant(false, {
reason: 'Unexpected HoistedLet variable in for..in collection',
description: null,
loc: iterableItem.loc,
suggestions: null,
});
default:
assertExhaustive(
iterableItem.value.lvalue.kind,
@@ -1089,6 +1096,13 @@ function codegenTerminal(
loc: iterableItem.loc,
suggestions: null,
});
case InstructionKind.HoistedLet:
CompilerError.invariant(false, {
reason: 'Unexpected HoistedLet variable in for..of collection',
description: null,
loc: iterableItem.loc,
suggestions: null,
});
default:
assertExhaustive(
iterableItem.value.lvalue.kind,
@@ -1289,6 +1303,15 @@ function codegenInstructionNullable(
case InstructionKind.Catch: {
return t.emptyStatement();
}
case InstructionKind.HoistedLet: {
CompilerError.invariant(false, {
reason:
'Expected HoistedLet to have been pruned in PruneHoistedContexts',
description: null,
loc: instr.loc,
suggestions: null,
});
}
case InstructionKind.HoistedConst: {
CompilerError.invariant(false, {
reason:
@@ -23,11 +23,11 @@ import {
* original instruction kind.
*/
export function pruneHoistedContexts(fn: ReactiveFunction): void {
const hoistedIdentifiers: HoistedIdentifiers = new Set();
const hoistedIdentifiers: HoistedIdentifiers = new Map();
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
}
type HoistedIdentifiers = Set<DeclarationId>;
type HoistedIdentifiers = Map<DeclarationId, InstructionKind>;
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
override transformInstruction(
@@ -39,7 +39,21 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedConst'
) {
state.add(instruction.value.lvalue.place.identifier.declarationId);
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Const,
);
return {kind: 'remove'};
}
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedLet'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Let,
);
return {kind: 'remove'};
}
@@ -47,6 +61,9 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
instruction.value.kind === 'StoreContext' &&
state.has(instruction.value.lvalue.place.identifier.declarationId)
) {
const kind = state.get(
instruction.value.lvalue.place.identifier.declarationId,
)!;
return {
kind: 'replace',
value: {
@@ -57,7 +74,7 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind: InstructionKind.Const,
kind,
},
type: null,
kind: 'StoreLocal',
@@ -130,6 +130,16 @@ function getContextReassignment(
*/
contextVariables.add(value.lvalue.place.identifier.id);
}
const reassignment = reassigningFunctions.get(
value.value.identifier.id,
);
if (reassignment !== undefined) {
reassigningFunctions.set(
value.lvalue.place.identifier.id,
reassignment,
);
reassigningFunctions.set(lvalue.identifier.id, reassignment);
}
break;
}
default: {
@@ -11,10 +11,10 @@ import {
IdentifierId,
Place,
SourceLocation,
isRefOrRefValue,
isRefValueType,
isUseRefType,
} from '../HIR';
import {printPlace} from '../HIR/PrintHIR';
import {
eachInstructionValueOperand,
eachTerminalOperand,
@@ -52,34 +52,36 @@ function validateNoRefAccessInRenderImpl(
refAccessingFunctions: Set<IdentifierId>,
): Result<void, CompilerError> {
const errors = new CompilerError();
const lookupLocations: Map<IdentifierId, SourceLocation> = new Map();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
switch (instr.value.kind) {
case 'JsxExpression':
case 'JsxFragment': {
for (const operand of eachInstructionValueOperand(instr.value)) {
if (isRefValueType(operand.identifier)) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
loc: operand.loc,
description: `Cannot access ref value at ${printPlace(
operand,
)}`,
suggestions: null,
});
}
validateNoDirectRefValueAccess(errors, operand, lookupLocations);
}
break;
}
case 'PropertyLoad': {
if (
isRefValueType(instr.lvalue.identifier) &&
instr.value.property === 'current'
) {
lookupLocations.set(instr.lvalue.identifier.id, instr.loc);
}
break;
}
case 'LoadLocal': {
if (refAccessingFunctions.has(instr.value.place.identifier.id)) {
refAccessingFunctions.add(instr.lvalue.identifier.id);
}
if (isRefValueType(instr.lvalue.identifier)) {
const loc = lookupLocations.get(instr.value.place.identifier.id);
if (loc !== undefined) {
lookupLocations.set(instr.lvalue.identifier.id, loc);
}
}
break;
}
case 'StoreLocal': {
@@ -87,6 +89,13 @@ function validateNoRefAccessInRenderImpl(
refAccessingFunctions.add(instr.value.lvalue.place.identifier.id);
refAccessingFunctions.add(instr.lvalue.identifier.id);
}
if (isRefValueType(instr.value.lvalue.place.identifier)) {
const loc = lookupLocations.get(instr.value.value.identifier.id);
if (loc !== undefined) {
lookupLocations.set(instr.value.lvalue.place.identifier.id, loc);
lookupLocations.set(instr.lvalue.identifier.id, loc);
}
}
break;
}
case 'ObjectMethod':
@@ -139,7 +148,11 @@ function validateNoRefAccessInRenderImpl(
reason:
'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)',
loc: callee.loc,
description: `Function ${printPlace(callee)} accesses a ref`,
description:
callee.identifier.name !== null &&
callee.identifier.name.kind === 'named'
? `Function \`${callee.identifier.name.value}\` accesses a ref`
: null,
suggestions: null,
});
}
@@ -148,7 +161,7 @@ function validateNoRefAccessInRenderImpl(
errors,
refAccessingFunctions,
operand,
operand.loc,
lookupLocations.get(operand.identifier.id) ?? operand.loc,
);
}
}
@@ -161,7 +174,7 @@ function validateNoRefAccessInRenderImpl(
errors,
refAccessingFunctions,
operand,
operand.loc,
lookupLocations.get(operand.identifier.id) ?? operand.loc,
);
}
break;
@@ -174,26 +187,49 @@ function validateNoRefAccessInRenderImpl(
errors,
refAccessingFunctions,
instr.value.object,
instr.loc,
lookupLocations.get(instr.value.object.identifier.id) ?? instr.loc,
);
for (const operand of eachInstructionValueOperand(instr.value)) {
if (operand === instr.value.object) {
continue;
}
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
validateNoRefValueAccess(
errors,
refAccessingFunctions,
lookupLocations,
operand,
);
}
break;
}
case 'StartMemoize':
case 'FinishMemoize':
break;
default: {
for (const operand of eachInstructionValueOperand(instr.value)) {
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
validateNoRefValueAccess(
errors,
refAccessingFunctions,
lookupLocations,
operand,
);
}
break;
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
validateNoRefValueAccess(errors, refAccessingFunctions, operand);
if (block.terminal.kind !== 'return') {
validateNoRefValueAccess(
errors,
refAccessingFunctions,
lookupLocations,
operand,
);
} else {
// Allow functions containing refs to be returned, but not direct ref values
validateNoDirectRefValueAccess(errors, operand, lookupLocations);
}
}
}
@@ -207,6 +243,7 @@ function validateNoRefAccessInRenderImpl(
function validateNoRefValueAccess(
errors: CompilerError,
refAccessingFunctions: Set<IdentifierId>,
lookupLocations: Map<IdentifierId, SourceLocation>,
operand: Place,
): void {
if (
@@ -217,8 +254,12 @@ function validateNoRefValueAccess(
severity: ErrorSeverity.InvalidReact,
reason:
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
loc: operand.loc,
description: `Cannot access ref value at ${printPlace(operand)}`,
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
description:
operand.identifier.name !== null &&
operand.identifier.name.kind === 'named'
? `Cannot access ref value \`${operand.identifier.name.value}\``
: null,
suggestions: null,
});
}
@@ -231,8 +272,7 @@ function validateNoRefAccess(
loc: SourceLocation,
): void {
if (
isRefValueType(operand.identifier) ||
isUseRefType(operand.identifier) ||
isRefOrRefValue(operand.identifier) ||
refAccessingFunctions.has(operand.identifier.id)
) {
errors.push({
@@ -249,3 +289,24 @@ function validateNoRefAccess(
});
}
}
function validateNoDirectRefValueAccess(
errors: CompilerError,
operand: Place,
lookupLocations: Map<IdentifierId, SourceLocation>,
): void {
if (isRefValueType(operand.identifier)) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
description:
operand.identifier.name !== null &&
operand.identifier.name.kind === 'named'
? `Cannot access ref value \`${operand.identifier.name.value}\``
: null,
suggestions: null,
});
}
}
@@ -0,0 +1,152 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
HIRFunction,
IdentifierId,
isSetStateType,
isUseEffectHookType,
Place,
} from '../HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
/**
* Validates against calling setState in the body of a *passive* effect (useEffect),
* while allowing calling setState in callbacks scheduled by the effect.
*
* Calling setState during execution of a useEffect triggers a re-render, which is
* often bad for performance and frequently has more efficient and straightforward
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
*/
export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
const setStateFunctions: Map<IdentifierId, Place> = new Map();
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
switch (instr.value.kind) {
case 'LoadLocal': {
if (setStateFunctions.has(instr.value.place.identifier.id)) {
setStateFunctions.set(
instr.lvalue.identifier.id,
instr.value.place,
);
}
break;
}
case 'StoreLocal': {
if (setStateFunctions.has(instr.value.value.identifier.id)) {
setStateFunctions.set(
instr.value.lvalue.place.identifier.id,
instr.value.value,
);
setStateFunctions.set(
instr.lvalue.identifier.id,
instr.value.value,
);
}
break;
}
case 'FunctionExpression': {
if (
// faster-path to check if the function expression references a setState
[...eachInstructionValueOperand(instr.value)].some(
operand =>
isSetStateType(operand.identifier) ||
setStateFunctions.has(operand.identifier.id),
)
) {
const callee = getSetStateCall(
instr.value.loweredFunc.func,
setStateFunctions,
);
if (callee !== null) {
setStateFunctions.set(instr.lvalue.identifier.id, callee);
}
}
break;
}
case 'MethodCall':
case 'CallExpression': {
const callee =
instr.value.kind === 'MethodCall'
? instr.value.receiver
: instr.value.callee;
if (isUseEffectHookType(callee.identifier)) {
const arg = instr.value.args[0];
if (arg !== undefined && arg.kind === 'Identifier') {
const setState = setStateFunctions.get(arg.identifier.id);
if (setState !== undefined) {
errors.push({
reason:
'Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: setState.loc,
suggestions: null,
});
}
}
}
break;
}
}
}
}
if (errors.hasErrors()) {
throw errors;
}
}
function getSetStateCall(
fn: HIRFunction,
setStateFunctions: Map<IdentifierId, Place>,
): Place | null {
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
switch (instr.value.kind) {
case 'LoadLocal': {
if (setStateFunctions.has(instr.value.place.identifier.id)) {
setStateFunctions.set(
instr.lvalue.identifier.id,
instr.value.place,
);
}
break;
}
case 'StoreLocal': {
if (setStateFunctions.has(instr.value.value.identifier.id)) {
setStateFunctions.set(
instr.value.lvalue.place.identifier.id,
instr.value.value,
);
setStateFunctions.set(
instr.lvalue.identifier.id,
instr.value.value,
);
}
break;
}
case 'CallExpression': {
const callee = instr.value.callee;
if (
isSetStateType(callee.identifier) ||
setStateFunctions.has(callee.identifier.id)
) {
/*
* TODO: once we support multiple locations per error, we should link to the
* original Place in the case that setStateFunction.has(callee)
*/
return callee;
}
}
}
}
}
return null;
}
@@ -40,62 +40,37 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useRef } from "react";
function Component() {
const $ = _c(10);
const $ = _c(2);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const setRef = () => {
if (ref.current !== null) {
ref.current = "";
}
};
t0 = () => {
setRef();
};
$[0] = t0;
} else {
t0 = $[0];
}
const setRef = t0;
const onClick = t0;
let t1;
if ($[1] !== setRef) {
t1 = () => {
setRef();
};
$[1] = setRef;
$[2] = t1;
} else {
t1 = $[2];
}
const onClick = t1;
let t2;
if ($[3] !== ref) {
t2 = <input ref={ref} />;
$[3] = ref;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== onClick) {
t3 = <button onClick={onClick} />;
$[5] = onClick;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] !== t2 || $[8] !== t3) {
t4 = (
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<>
{t2}
{t3}
<input ref={ref} />
<button onClick={onClick} />
</>
);
$[7] = t2;
$[8] = t3;
$[9] = t4;
$[1] = t1;
} else {
t4 = $[9];
t1 = $[1];
}
return t4;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -36,7 +36,7 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useRef } from "react";
function Component() {
const $ = _c(8);
const $ = _c(2);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -51,36 +51,18 @@ function Component() {
}
const onClick = t0;
let t1;
if ($[1] !== ref) {
t1 = <input ref={ref} />;
$[1] = ref;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] !== onClick) {
t2 = <button onClick={onClick} />;
$[3] = onClick;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== t1 || $[6] !== t2) {
t3 = (
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<>
{t1}
{t2}
<input ref={ref} />
<button onClick={onClick} />
</>
);
$[5] = t1;
$[6] = t2;
$[7] = t3;
$[1] = t1;
} else {
t3 = $[7];
t1 = $[1];
}
return t3;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -40,62 +40,37 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useRef } from "react";
function Component() {
const $ = _c(10);
const $ = _c(2);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const setRef = () => {
if (ref.current !== null) {
ref.current.value = "";
}
};
t0 = () => {
setRef();
};
$[0] = t0;
} else {
t0 = $[0];
}
const setRef = t0;
const onClick = t0;
let t1;
if ($[1] !== setRef) {
t1 = () => {
setRef();
};
$[1] = setRef;
$[2] = t1;
} else {
t1 = $[2];
}
const onClick = t1;
let t2;
if ($[3] !== ref) {
t2 = <input ref={ref} />;
$[3] = ref;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== onClick) {
t3 = <button onClick={onClick} />;
$[5] = onClick;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] !== t2 || $[8] !== t3) {
t4 = (
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<>
{t2}
{t3}
<input ref={ref} />
<button onClick={onClick} />
</>
);
$[7] = t2;
$[8] = t3;
$[9] = t4;
$[1] = t1;
} else {
t4 = $[9];
t1 = $[1];
}
return t4;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -36,7 +36,7 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useRef } from "react";
function Component() {
const $ = _c(8);
const $ = _c(2);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -51,36 +51,18 @@ function Component() {
}
const onClick = t0;
let t1;
if ($[1] !== ref) {
t1 = <input ref={ref} />;
$[1] = ref;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] !== onClick) {
t2 = <button onClick={onClick} />;
$[3] = onClick;
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== t1 || $[6] !== t2) {
t3 = (
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<>
{t1}
{t2}
<input ref={ref} />
<button onClick={onClick} />
</>
);
$[5] = t1;
$[6] = t2;
$[7] = t3;
$[1] = t1;
} else {
t3 = $[7];
t1 = $[1];
}
return t3;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -14,15 +14,14 @@ function Component(props) {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(1);
const ref = useRef(null);
let t0;
if ($[0] !== ref) {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Foo ref={ref} />;
$[0] = ref;
$[1] = t0;
$[0] = t0;
} else {
t0 = $[1];
t0 = $[0];
}
return t0;
}
@@ -46,7 +46,7 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useCallback, useEffect, useRef, useState } from "react";
function Component() {
const $ = _c(9);
const $ = _c(7);
const ref = useRef(null);
const [state, setState] = useState(false);
let t0;
@@ -60,47 +60,42 @@ function Component() {
}
const setRef = t0;
let t1;
if ($[1] !== setRef) {
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setRef();
};
$[1] = setRef;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[3] = t2;
$[1] = t1;
$[2] = t2;
} else {
t2 = $[3];
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
let t3;
let t4;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
setState(true);
};
t4 = [];
$[4] = t3;
$[5] = t4;
$[3] = t3;
$[4] = t4;
} else {
t3 = $[4];
t4 = $[5];
t3 = $[3];
t4 = $[4];
}
useEffect(t3, t4);
const t5 = String(state);
let t6;
if ($[6] !== t5 || $[7] !== ref) {
if ($[5] !== t5) {
t6 = <Child key={t5} ref={ref} />;
$[6] = t5;
$[7] = ref;
$[8] = t6;
$[5] = t5;
$[6] = t6;
} else {
t6 = $[8];
t6 = $[6];
}
return t6;
}
@@ -42,7 +42,7 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useEffect, useRef, useState } from "react";
function Component() {
const $ = _c(7);
const $ = _c(6);
const ref = useRef(null);
const [state, setState] = useState(false);
let t0;
@@ -76,13 +76,12 @@ function Component() {
const t4 = String(state);
let t5;
if ($[4] !== t4 || $[5] !== ref) {
if ($[4] !== t4) {
t5 = <Child key={t4} ref={ref} />;
$[4] = t4;
$[5] = ref;
$[6] = t5;
$[5] = t5;
} else {
t5 = $[6];
t5 = $[5];
}
return t5;
}
@@ -44,7 +44,7 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useEffect, useRef, useState } from "react";
function Component() {
const $ = _c(7);
const $ = _c(6);
const ref = useRef(null);
const [state, setState] = useState(false);
let t0;
@@ -77,13 +77,12 @@ function Component() {
const t4 = String(state);
let t5;
if ($[4] !== t4 || $[5] !== ref) {
if ($[4] !== t4) {
t5 = <Child key={t4} ref={ref} />;
$[4] = t4;
$[5] = ref;
$[6] = t5;
$[5] = t5;
} else {
t5 = $[6];
t5 = $[5];
}
return t5;
}
@@ -1,70 +0,0 @@
## Input
```javascript
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {
const position = currentPosition.current;
const nextPosition = direction === 'left' ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = {
handler: handleKey('left'),
};
const moveRight = {
handler: handleKey('right'),
};
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { addOne } from "shared-runtime";
function useKeyCommand() {
const $ = _c(2);
const currentPosition = useRef(0);
const handleKey = (direction) => () => {
const position = currentPosition.current;
const nextPosition = direction === "left" ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = { handler: handleKey("left") };
const t0 = handleKey("right");
let t1;
if ($[0] !== t0) {
t1 = { handler: t0 };
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
const moveRight = t1;
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
### Eval output
(kind: ok) [{"handler":"[[ function params=0 ]]"},{"handler":"[[ function params=0 ]]"}]
@@ -0,0 +1,50 @@
## Input
```javascript
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {
const position = currentPosition.current;
const nextPosition = direction === 'left' ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = {
handler: handleKey('left'),
};
const moveRight = {
handler: handleKey('right'),
};
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
## Error
```
10 | };
11 | const moveLeft = {
> 12 | handler: handleKey('left'),
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12)
InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15)
13 | };
14 | const moveRight = {
15 | handler: handleKey('right'),
```
@@ -18,7 +18,7 @@ function Component() {
1 | function Component() {
2 | let callback = () => {
> 3 | callback = null;
| ^^^^^^^^^^^^^^^ Todo: Handle non-const declarations for hoisting. variable "callback" declared with let (3:3)
| ^^^^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `callback` cannot be reassigned after render (3:3)
4 | };
5 | return <div onClick={callback} />;
6 | }
@@ -15,10 +15,11 @@ function Component(props) {
## Error
```
2 | function Component(props) {
3 | const ref = useRef(null);
4 | const value = ref.current;
> 5 | return value;
| ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $22:TObject<BuiltInRefValue> (5:5)
> 4 | const value = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
5 | return value;
6 | }
7 |
```
@@ -24,7 +24,7 @@ function Component() {
7 | };
8 | const changeRef = setRef;
> 9 | changeRef();
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef). Function mutate? $39[11:13]:TObject<BuiltInFunction> accesses a ref (9:9)
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
10 |
@@ -14,10 +14,11 @@ function Component({ref}) {
## Error
```
1 | // @validateRefAccessDuringRender @compilationMode(infer)
2 | function Component({ref}) {
3 | const value = ref.current;
> 4 | return <div>{value}</div>;
| ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $17:TObject<BuiltInRefValue> (4:4)
> 3 | const value = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
4 | return <div>{value}</div>;
5 | }
6 |
```
@@ -14,10 +14,11 @@ function Component(props) {
## Error
```
1 | // @validateRefAccessDuringRender @compilationMode(infer)
2 | function Component(props) {
3 | const value = props.ref.current;
> 4 | return <div>{value}</div>;
| ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $15:TObject<BuiltInRefValue> (4:4)
> 3 | const value = props.ref.current;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
4 | return <div>{value}</div>;
5 | }
6 |
```
@@ -17,7 +17,7 @@ function Component(props) {
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | return <Foo ref={ref.current} />;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $19:TObject<BuiltInRefValue> (4:4)
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
5 | }
6 |
```
@@ -20,7 +20,7 @@ function Component(props) {
> 4 | ref.current = props.value;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $24:TObject<BuiltInRefValue> (5:5)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
5 | return ref.current;
6 | }
7 |
@@ -18,9 +18,9 @@ function Component(props) {
2 | function Component(props) {
3 | const ref = useRef({inner: null});
> 4 | ref.current.inner = props.value;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $30:TObject<BuiltInRefValue> (5:5)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
5 | return ref.current.inner;
6 | }
7 |
@@ -0,0 +1,37 @@
## Input
```javascript
// @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
const f = () => {
setState(s => s + 1);
};
const g = () => {
f();
};
useEffect(() => {
g();
});
return state;
}
```
## Error
```
11 | };
12 | useEffect(() => {
> 13 | g();
| ^ InvalidReact: Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect) (13:13)
14 | });
15 | return state;
16 | }
```
@@ -0,0 +1,16 @@
// @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
const f = () => {
setState(s => s + 1);
};
const g = () => {
f();
};
useEffect(() => {
g();
});
return state;
}
@@ -0,0 +1,31 @@
## Input
```javascript
// @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
useEffect(() => {
setState(s => s + 1);
});
return state;
}
```
## Error
```
5 | const [state, setState] = useState(0);
6 | useEffect(() => {
> 7 | setState(s => s + 1);
| ^^^^^^^^ InvalidReact: Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect) (7:7)
8 | });
9 | return state;
10 | }
```
@@ -0,0 +1,10 @@
// @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
useEffect(() => {
setState(s => s + 1);
});
return state;
}
@@ -1,31 +0,0 @@
## Input
```javascript
// Let's not support identifiers defined after use for now.
function component(a) {
let y = function () {
m(x);
};
let x = {a};
m(x);
return y;
}
```
## Error
```
2 | function component(a) {
3 | let y = function () {
> 4 | m(x);
| ^^^^ Todo: Handle non-const declarations for hoisting. variable "x" declared with let (4:4)
5 | };
6 |
7 | let x = {a};
```
@@ -1,10 +0,0 @@
// Let's not support identifiers defined after use for now.
function component(a) {
let y = function () {
m(x);
};
let x = {a};
m(x);
return y;
}
@@ -0,0 +1,40 @@
## Input
```javascript
import {Stringify, identity, mutate, CONST_TRUE} from 'shared-runtime';
function Foo(props, ref) {
const value = {};
if (CONST_TRUE) {
mutate(value);
return <Stringify ref={ref} />;
}
mutate(value);
if (CONST_TRUE) {
return <Stringify ref={identity(ref)} />;
}
return value;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}, {current: 'fake-ref-object'}],
};
```
## Error
```
9 | mutate(value);
10 | if (CONST_TRUE) {
> 11 | return <Stringify ref={identity(ref)} />;
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (11:11)
12 | }
13 | return value;
14 | }
```
@@ -20,7 +20,7 @@ function Component() {
1 | function Component() {
2 | let callback = () => {
> 3 | onClick = () => {};
| ^^^^^^^^^^^^^^^^^^ Todo: Handle non-const declarations for hoisting. variable "onClick" declared with let (3:3)
| ^^^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `onClick` cannot be reassigned after render (3:3)
4 | };
5 | let onClick;
6 |
@@ -104,10 +104,6 @@ Todo: (BuildHIR::lowerStatement) Handle ArrayPattern inits in ForOfStatement (38
Todo: (BuildHIR::lowerStatement) Handle ObjectPattern inits in ForOfStatement (40:40)
Todo: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument (49:49)
Todo: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument (50:50)
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `MemberExpression` cannot be safely reordered (57:57)
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `BinaryExpression` cannot be safely reordered (53:53)
@@ -1,54 +0,0 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
function Component(props) {
const ref = useRef({inner: null});
const onChange = useCallback(event => {
// The ref should still be mutable here even though function deps are frozen in
// @enablePreserveExistingMemoizationGuarantees mode
ref.current.inner = event.target.value;
});
// The ref is modified later, extending its range and preventing memoization of onChange
const reset = () => {
ref.current.inner = null;
};
reset();
return <input onChange={onChange} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Error
```
5 | const ref = useRef({inner: null});
6 |
> 7 | const onChange = useCallback(event => {
| ^^^^^^^^^^
> 8 | // The ref should still be mutable here even though function deps are frozen in
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 9 | // @enablePreserveExistingMemoizationGuarantees mode
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 10 | ref.current.inner = event.target.value;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 11 | });
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. (7:11)
12 |
13 | // The ref is modified later, extending its range and preventing memoization of onChange
14 | const reset = () => {
```
@@ -2,7 +2,7 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees
// @enablePreserveExistingMemoizationGuarantees @validateRefAccessDuringRender
import {useCallback, useRef} from 'react';
function Component(props) {
@@ -31,21 +31,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
5 | const ref = useRef({inner: null});
6 |
> 7 | const onChange = useCallback(event => {
| ^^^^^^^^^^
> 8 | // The ref should still be mutable here even though function deps are frozen in
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 9 | // @enablePreserveExistingMemoizationGuarantees mode
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 10 | ref.current.inner = event.target.value;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 11 | });
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. (7:11)
12 |
13 | // The ref is modified later, extending its range and preventing memoization of onChange
14 | ref.current.inner = null;
> 14 | ref.current.inner = null;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (14:14)
15 |
16 | return <input onChange={onChange} />;
17 | }
```
@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees
// @enablePreserveExistingMemoizationGuarantees @validateRefAccessDuringRender
import {useCallback, useRef} from 'react';
function Component(props) {
@@ -0,0 +1,48 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees @validateRefAccessDuringRender
import {useCallback, useRef} from 'react';
function Component(props) {
const ref = useRef({inner: null});
const onChange = useCallback(event => {
// The ref should still be mutable here even though function deps are frozen in
// @enablePreserveExistingMemoizationGuarantees mode
ref.current.inner = event.target.value;
});
// The ref is modified later, extending its range and preventing memoization of onChange
const reset = () => {
ref.current.inner = null;
};
reset();
return <input onChange={onChange} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Error
```
15 | ref.current.inner = null;
16 | };
> 17 | reset();
| ^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (17:17)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (17:17)
18 |
19 | return <input onChange={onChange} />;
20 | }
```
@@ -1,4 +1,4 @@
// @enablePreserveExistingMemoizationGuarantees
// @enablePreserveExistingMemoizationGuarantees @validateRefAccessDuringRender
import {useCallback, useRef} from 'react';
function Component(props) {
@@ -0,0 +1,42 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
import {useCallback, useRef} from 'react';
function Component(props) {
const ref = useRef({inner: null});
const onChange = useCallback(event => {
// The ref should still be mutable here even though function deps are frozen in
// @enablePreserveExistingMemoizationGuarantees mode
ref.current.inner = event.target.value;
});
ref.current.inner = null;
return <input onChange={onChange} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Error
```
11 | });
12 |
> 13 | ref.current.inner = null;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13)
14 |
15 | return <input onChange={onChange} />;
16 | }
```
@@ -0,0 +1,62 @@
## Input
```javascript
//@flow
component Foo() {
function foo() {
return (
<div>
{a} {z} {y}
</div>
);
}
const [a, {x: z, y = 10}] = [1, {x: 2}];
return foo();
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Foo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const foo = function foo() {
return (
<div>
{a} {z} {y}
</div>
);
};
const [t1, t2] = [1, { x: 2 }];
const a = t1;
const { x: t3, y: t4 } = t2;
const z = t3;
const y = t4 === undefined ? 10 : t4;
t0 = foo();
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: ok) <div>1 2 10</div>
@@ -0,0 +1,17 @@
//@flow
component Foo() {
function foo() {
return (
<div>
{a} {z} {y}
</div>
);
}
const [a, {x: z, y = 10}] = [1, {x: 2}];
return foo();
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
@@ -0,0 +1,62 @@
## Input
```javascript
function hoisting(cond) {
let items = [];
if (cond) {
let foo = () => {
items.push(bar());
};
let bar = () => true;
foo();
}
return items;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [true],
isComponent: false,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function hoisting(cond) {
const $ = _c(2);
let items;
if ($[0] !== cond) {
items = [];
if (cond) {
const foo = () => {
items.push(bar());
};
let bar = _temp;
foo();
}
$[0] = cond;
$[1] = items;
} else {
items = $[1];
}
return items;
}
function _temp() {
return true;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [true],
isComponent: false,
};
```
### Eval output
(kind: ok) [true]
@@ -0,0 +1,17 @@
function hoisting(cond) {
let items = [];
if (cond) {
let foo = () => {
items.push(bar());
};
let bar = () => true;
foo();
}
return items;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [true],
isComponent: false,
};
@@ -0,0 +1,65 @@
## Input
```javascript
function hoisting() {
let qux = () => {
let result;
{
result = foo();
}
return result;
};
let foo = () => {
return bar + baz;
};
let bar = 3;
const baz = 2;
return qux(); // OK: called outside of TDZ
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [],
isComponent: false,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function hoisting() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const qux = () => {
let result;
result = foo();
return result;
};
let foo = () => bar + baz;
let bar = 3;
const baz = 2;
t0 = qux();
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [],
isComponent: false,
};
```
### Eval output
(kind: ok) 5
@@ -0,0 +1,21 @@
function hoisting() {
let qux = () => {
let result;
{
result = foo();
}
return result;
};
let foo = () => {
return bar + baz;
};
let bar = 3;
const baz = 2;
return qux(); // OK: called outside of TDZ
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [],
isComponent: false,
};
@@ -0,0 +1,51 @@
## Input
```javascript
function hoisting() {
let foo = () => {
return bar + baz;
};
let bar = 3;
let baz = 2;
return foo(); // OK: called outside of TDZ for bar/baz
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [],
isComponent: false,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function hoisting() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const foo = () => bar + baz;
let bar = 3;
let baz = 2;
t0 = foo();
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [],
isComponent: false,
};
```
### Eval output
(kind: ok) 5
@@ -0,0 +1,14 @@
function hoisting() {
let foo = () => {
return bar + baz;
};
let bar = 3;
let baz = 2;
return foo(); // OK: called outside of TDZ for bar/baz
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
params: [],
isComponent: false,
};
@@ -0,0 +1,39 @@
## Input
```javascript
//@flow
const foo = undefined;
component C(...{scope = foo ?? null}: any) {
return scope;
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{scope: undefined}],
};
```
## Code
```javascript
const foo = undefined;
function C(t0) {
const { scope: t1 } = t0;
const scope = t1 === undefined ? (foo ?? null) : t1;
return scope;
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{ scope: undefined }],
};
```
### Eval output
(kind: ok) null
@@ -0,0 +1,12 @@
//@flow
const foo = undefined;
component C(...{scope = foo ?? null}: any) {
return scope;
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{scope: undefined}],
};
@@ -0,0 +1,63 @@
## Input
```javascript
//@flow
component Foo() {
let x = {a: 1};
x.a++;
x.a--;
console.log(++x.a);
console.log(x.a++);
console.log(x.a);
let y = x.a++;
console.log(y);
console.log(x.a);
console.log((++x.a).toString(), (x.a++).toString(), x.a);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
function Foo() {
const x = { a: 1 };
x.a = x.a + 1;
x.a = x.a - 1;
console.log((x.a = x.a + 1));
const t0 = x.a;
x.a = t0 + 1;
console.log(t0);
console.log(x.a);
const t1 = x.a;
x.a = t1 + 1;
const y = t1;
console.log(y);
console.log(x.a);
const t2 = (x.a = x.a + 1).toString();
const t3 = x.a;
x.a = t3 + 1;
console.log(t2, t3.toString(), x.a);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: ok)
logs: [2,2,3,3,4,'5','5',6]
@@ -0,0 +1,21 @@
//@flow
component Foo() {
let x = {a: 1};
x.a++;
x.a--;
console.log(++x.a);
console.log(x.a++);
console.log(x.a);
let y = x.a++;
console.log(y);
console.log(x.a);
console.log((++x.a).toString(), (x.a++).toString(), x.a);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
@@ -0,0 +1,56 @@
## Input
```javascript
function component(a) {
let y = function () {
m(x);
};
let x = {a};
m(x);
return y;
}
function m(x) {}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: [{name: 'Jason'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a) {
const $ = _c(2);
let y;
if ($[0] !== a) {
y = function () {
m(x);
};
let x = { a };
m(x);
$[0] = a;
$[1] = y;
} else {
y = $[1];
}
return y;
}
function m(x) {}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: [{ name: "Jason" }],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
@@ -0,0 +1,16 @@
function component(a) {
let y = function () {
m(x);
};
let x = {a};
m(x);
return y;
}
function m(x) {}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: [{name: 'Jason'}],
};
@@ -1,89 +0,0 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {
const position = currentPosition.current;
const nextPosition = direction === 'left' ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = {
handler: handleKey('left'),
};
const moveRight = {
handler: handleKey('right'),
};
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false
import { useRef } from "react";
import { addOne } from "shared-runtime";
function useKeyCommand() {
const $ = _c(7);
const currentPosition = useRef(0);
const handleKey = (direction) => () => {
const position = currentPosition.current;
const nextPosition = direction === "left" ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const t0 = handleKey("left");
let t1;
if ($[0] !== t0) {
t1 = { handler: t0 };
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
const moveLeft = t1;
const t2 = handleKey("right");
let t3;
if ($[2] !== t2) {
t3 = { handler: t2 };
$[2] = t2;
$[3] = t3;
} else {
t3 = $[3];
}
const moveRight = t3;
let t4;
if ($[4] !== moveLeft || $[5] !== moveRight) {
t4 = [moveLeft, moveRight];
$[4] = moveLeft;
$[5] = moveRight;
$[6] = t4;
} else {
t4 = $[6];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
### Eval output
(kind: ok) [{"handler":"[[ function params=0 ]]"},{"handler":"[[ function params=0 ]]"}]
@@ -0,0 +1,51 @@
## Input
```javascript
// @enableReactiveScopesInHIR:false
import {useRef} from 'react';
import {addOne} from 'shared-runtime';
function useKeyCommand() {
const currentPosition = useRef(0);
const handleKey = direction => () => {
const position = currentPosition.current;
const nextPosition = direction === 'left' ? addOne(position) : position;
currentPosition.current = nextPosition;
};
const moveLeft = {
handler: handleKey('left'),
};
const moveRight = {
handler: handleKey('right'),
};
return [moveLeft, moveRight];
}
export const FIXTURE_ENTRYPOINT = {
fn: useKeyCommand,
params: [],
};
```
## Error
```
11 | };
12 | const moveLeft = {
> 13 | handler: handleKey('left'),
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (13:13)
InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (16:16)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (16:16)
14 | };
15 | const moveRight = {
16 | handler: handleKey('right'),
```
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
6 | function useFoo() {
7 | const r = useRef();
> 8 | return useMemo(() => makeArray(r), []);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. (8:8)
| ^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
@@ -0,0 +1,48 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
function useFoo({cond}) {
const ref1 = useRef<undefined | (() => undefined)>();
const ref2 = useRef<undefined | (() => undefined)>();
const ref = cond ? ref1 : ref2;
return useCallback(() => {
if (ref != null) {
ref.current();
}
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Error
```
7 | const ref = cond ? ref1 : ref2;
8 |
> 9 | return useCallback(() => {
| ^^^^^^^
> 10 | if (ref != null) {
| ^^^^^^^^^^^^^^^^^^^^^^
> 11 | ref.current();
| ^^^^^^^^^^^^^^^^^^^^^^
> 12 | }
| ^^^^^^^^^^^^^^^^^^^^^^
> 13 | }, []);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (9:13)
14 | }
15 |
16 | export const FIXTURE_ENTRYPOINT = {
```
@@ -0,0 +1,19 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
function useFoo({cond}) {
const ref1 = useRef<undefined | (() => undefined)>();
const ref2 = useRef<undefined | (() => undefined)>();
const ref = cond ? ref1 : ref2;
return useCallback(() => {
if (ref != null) {
ref.current();
}
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
@@ -0,0 +1,31 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {identity} from 'shared-runtime';
component Component(disableLocalRef, ref) {
const localRef = useFooRef();
const mergedRef = useMemo(() => {
return disableLocalRef ? ref : identity(ref, localRef);
}, [disableLocalRef, ref, localRef]);
return <div ref={mergedRef} />;
}
```
## Error
```
5 | const localRef = useFooRef();
6 | const mergedRef = useMemo(() => {
> 7 | return disableLocalRef ? ref : identity(ref, localRef);
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (7:7)
8 | }, [disableLocalRef, ref, localRef]);
9 | return <div ref={mergedRef} />;
10 | }
```
@@ -0,0 +1,56 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
function useFoo() {
const ref = useRef<undefined | (() => undefined)>();
return useCallback(() => {
if (ref != null) {
ref.current();
}
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback, useRef } from "react";
function useFoo() {
const $ = _c(1);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
if (ref != null) {
ref.current();
}
};
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
@@ -0,0 +1,17 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
function useFoo() {
const ref = useRef<undefined | (() => undefined)>();
return useCallback(() => {
if (ref != null) {
ref.current();
}
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
@@ -0,0 +1,52 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback, useTransition} from 'react';
function useFoo() {
const [t, start] = useTransition();
return useCallback(() => {
start();
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback, useTransition } from "react";
function useFoo() {
const $ = _c(1);
const [t, start] = useTransition();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
start();
};
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
@@ -0,0 +1,15 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback, useTransition} from 'react';
function useFoo() {
const [t, start] = useTransition();
return useCallback(() => {
start();
}, []);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
@@ -1,54 +0,0 @@
## Input
```javascript
// @flow @validatePreserveExistingMemoizationGuarantees
import {identity} from 'shared-runtime';
component Component(disableLocalRef, ref) {
const localRef = useFooRef();
const mergedRef = useMemo(() => {
return disableLocalRef ? ref : identity(ref, localRef);
}, [disableLocalRef, ref, localRef]);
return <div ref={mergedRef} />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity } from "shared-runtime";
const Component = React.forwardRef(Component_withRef);
function Component_withRef(t0, ref) {
const $ = _c(6);
const { disableLocalRef } = t0;
const localRef = useFooRef();
let t1;
let t2;
if ($[0] !== disableLocalRef || $[1] !== ref || $[2] !== localRef) {
t2 = disableLocalRef ? ref : identity(ref, localRef);
$[0] = disableLocalRef;
$[1] = ref;
$[2] = localRef;
$[3] = t2;
} else {
t2 = $[3];
}
t1 = t2;
const mergedRef = t1;
let t3;
if ($[4] !== mergedRef) {
t3 = <div ref={mergedRef} />;
$[4] = mergedRef;
$[5] = t3;
} else {
t3 = $[5];
}
return t3;
}
```
@@ -29,7 +29,7 @@ import { c as _c } from "react/compiler-runtime";
const FooContext = React.createContext({ current: null });
function Component(props) {
const $ = _c(6);
const $ = _c(5);
React.useContext(FooContext);
const ref = React.useRef();
const [x, setX] = React.useState(false);
@@ -53,13 +53,12 @@ function Component(props) {
t1 = $[2];
}
let t2;
if ($[3] !== onClick || $[4] !== t1) {
if ($[3] !== t1) {
t2 = <div onClick={onClick}>{t1}</div>;
$[3] = onClick;
$[4] = t1;
$[5] = t2;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[5];
t2 = $[4];
}
return t2;
}
@@ -20,28 +20,21 @@ function VideoTab() {
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender false
function VideoTab() {
const $ = _c(3);
const $ = _c(1);
const ref = useRef();
const t = ref.current;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const x = () => {
console.log(t);
};
t0 = <VideoList videos={x} />;
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let t1;
if ($[1] !== x) {
t1 = <VideoList videos={x} />;
$[1] = x;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}
```
@@ -19,27 +19,20 @@ function VideoTab() {
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender false
function VideoTab() {
const $ = _c(3);
const $ = _c(1);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const x = () => {
console.log(ref.current.x);
};
t0 = <VideoList videos={x} />;
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let t1;
if ($[1] !== x) {
t1 = <VideoList videos={x} />;
$[1] = x;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}
```
@@ -27,7 +27,7 @@ import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
function Component() {
const $ = _c(4);
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { text: { value: null } };
@@ -38,23 +38,16 @@ function Component() {
const ref = useRef(t0);
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (e) => {
const inputChanged = (e) => {
ref.current.text.value = e.target.value;
};
t1 = <input onChange={inputChanged} />;
$[1] = t1;
} else {
t1 = $[1];
}
const inputChanged = t1;
let t2;
if ($[2] !== inputChanged) {
t2 = <input onChange={inputChanged} />;
$[2] = inputChanged;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -18,27 +18,20 @@ function VideoTab() {
```javascript
import { c as _c } from "react/compiler-runtime";
function VideoTab() {
const $ = _c(3);
const $ = _c(1);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const x = () => {
console.log(ref.current);
};
t0 = <VideoList videos={x} />;
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let t1;
if ($[1] !== x) {
t1 = <VideoList videos={x} />;
$[1] = x;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}
```
@@ -18,27 +18,20 @@ function VideoTab() {
```javascript
import { c as _c } from "react/compiler-runtime";
function VideoTab() {
const $ = _c(3);
const $ = _c(1);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const x = () => {
ref.current?.x;
};
t0 = <VideoList videos={x} />;
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let t1;
if ($[1] !== x) {
t1 = <VideoList videos={x} />;
$[1] = x;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}
```
@@ -18,27 +18,20 @@ function VideoTab() {
```javascript
import { c as _c } from "react/compiler-runtime";
function VideoTab() {
const $ = _c(3);
const $ = _c(1);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
const x = () => {
ref.current = 1;
};
t0 = <VideoList videos={x} />;
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let t1;
if ($[1] !== x) {
t1 = <VideoList videos={x} />;
$[1] = x;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}
```
@@ -21,7 +21,7 @@ function Component(props) {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(4);
const $ = _c(3);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -45,12 +45,11 @@ function Component(props) {
}
useEffect(t1);
let t2;
if ($[2] !== onChange) {
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <Foo onChange={onChange} />;
$[2] = onChange;
$[3] = t2;
$[2] = t2;
} else {
t2 = $[3];
t2 = $[2];
}
return t2;
}
@@ -47,7 +47,7 @@ function useCustomRef() {
function _temp() {}
function Foo() {
const $ = _c(3);
const $ = _c(2);
const ref = useCustomRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -60,12 +60,11 @@ function Foo() {
}
const onClick = t0;
let t1;
if ($[1] !== onClick) {
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <button onClick={onClick} />;
$[1] = onClick;
$[2] = t1;
$[1] = t1;
} else {
t1 = $[2];
t1 = $[1];
}
return t1;
}
@@ -47,7 +47,7 @@ function useCustomRef() {
function _temp() {}
function Foo() {
const $ = _c(3);
const $ = _c(2);
const customRef = useCustomRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -60,12 +60,11 @@ function Foo() {
}
const onClick = t0;
let t1;
if ($[1] !== onClick) {
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <button onClick={onClick} />;
$[1] = onClick;
$[2] = t1;
$[1] = t1;
} else {
t1 = $[2];
t1 = $[1];
}
return t1;
}
@@ -1,83 +0,0 @@
## Input
```javascript
import {Stringify, identity, mutate, CONST_TRUE} from 'shared-runtime';
function Foo(props, ref) {
const value = {};
if (CONST_TRUE) {
mutate(value);
return <Stringify ref={ref} />;
}
mutate(value);
if (CONST_TRUE) {
return <Stringify ref={identity(ref)} />;
}
return value;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}, {current: 'fake-ref-object'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, identity, mutate, CONST_TRUE } from "shared-runtime";
function Foo(props, ref) {
const $ = _c(5);
let value;
let t0;
if ($[0] !== ref) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
value = {};
if (CONST_TRUE) {
mutate(value);
t0 = <Stringify ref={ref} />;
break bb0;
}
mutate(value);
if (CONST_TRUE) {
const t1 = identity(ref);
let t2;
if ($[3] !== t1) {
t2 = <Stringify ref={t1} />;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
t0 = t2;
break bb0;
}
}
$[0] = ref;
$[1] = value;
$[2] = t0;
} else {
value = $[1];
t0 = $[2];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;
}
return value;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}, { current: "fake-ref-object" }],
};
```
### Eval output
(kind: ok) <div>{"ref":{"current":"fake-ref-object"}}</div>
@@ -0,0 +1,55 @@
## Input
```javascript
// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees
import {useRef} from 'react';
component Foo() {
const ref = useRef();
const s = () => {
return ref.current;
};
return s;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
function Foo() {
const $ = _c(1);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => ref.current;
$[0] = t0;
} else {
t0 = $[0];
}
const s = t0;
return s;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
@@ -0,0 +1,18 @@
// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees
import {useRef} from 'react';
component Foo() {
const ref = useRef();
const s = () => {
return ref.current;
};
return s;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
@@ -35,7 +35,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemo
import { useCallback, useRef } from "react";
function Component(props) {
const $ = _c(6);
const $ = _c(4);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { inner: null };
@@ -65,13 +65,11 @@ function Component(props) {
}
const onReset = t2;
let t3;
if ($[3] !== onChange || $[4] !== onReset) {
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <input onChange={onChange} onReset={onReset} />;
$[3] = onChange;
$[4] = onReset;
$[5] = t3;
$[3] = t3;
} else {
t3 = $[5];
t3 = $[3];
}
return t3;
}
@@ -0,0 +1,76 @@
## Input
```javascript
// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
component Foo() {
const ref = useRef();
const s = useCallback(() => {
return ref.current;
});
return <A r={s} />;
}
component A(r: mixed) {
return <div />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useCallback, useRef } from "react";
function Foo() {
const $ = _c(2);
const ref = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => ref.current;
$[0] = t0;
} else {
t0 = $[0];
}
const s = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <A r={s} />;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function A(t0) {
const $ = _c(1);
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <div />;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: ok) <div></div>
@@ -0,0 +1,21 @@
// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees
import {useCallback, useRef} from 'react';
component Foo() {
const ref = useRef();
const s = useCallback(() => {
return ref.current;
});
return <A r={s} />;
}
component A(r: mixed) {
return <div />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
@@ -1,70 +0,0 @@
## Input
```javascript
// @enablePreserveExistingMemoizationGuarantees:false
import {useCallback, useRef} from 'react';
function Component(props) {
const ref = useRef({inner: null});
const onChange = useCallback(event => {
// The ref should still be mutable here even though function deps are frozen in
// @enablePreserveExistingMemoizationGuarantees mode
ref.current.inner = event.target.value;
});
ref.current.inner = null;
return <input onChange={onChange} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
import { useCallback, useRef } from "react";
function Component(props) {
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { inner: null };
$[0] = t0;
} else {
t0 = $[0];
}
const ref = useRef(t0);
const onChange = (event) => {
ref.current.inner = event.target.value;
};
ref.current.inner = null;
let t1;
if ($[1] !== onChange) {
t1 = <input onChange={onChange} />;
$[1] = onChange;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{}],
};
```
### Eval output
(kind: ok) <input>
@@ -31,7 +31,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemo
import { useCallback, useRef } from "react";
function Component(props) {
const $ = _c(4);
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { inner: null };
@@ -51,12 +51,11 @@ function Component(props) {
}
const onChange = t1;
let t2;
if ($[2] !== onChange) {
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <input onChange={onChange} />;
$[2] = onChange;
$[3] = t2;
$[2] = t2;
} else {
t2 = $[3];
t2 = $[2];
}
return t2;
}
@@ -34,7 +34,7 @@ import { useCallback, useRef } from "react";
// Identical to useCallback-set-ref-nested-property-preserve-memoization,
// but with a different set of compiler flags
function Component(t0) {
const $ = _c(4);
const $ = _c(3);
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { inner: null };
@@ -54,12 +54,11 @@ function Component(t0) {
}
const onChange = t2;
let t3;
if ($[2] !== onChange) {
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <input onChange={onChange} />;
$[2] = onChange;
$[3] = t3;
$[2] = t3;
} else {
t3 = $[3];
t3 = $[2];
}
return t3;
}
@@ -31,7 +31,7 @@ import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemo
import { useCallback, useRef } from "react";
function Component(props) {
const $ = _c(3);
const $ = _c(2);
const ref = useRef(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -44,12 +44,11 @@ function Component(props) {
}
const onChange = t0;
let t1;
if ($[1] !== onChange) {
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <input onChange={onChange} />;
$[1] = onChange;
$[2] = t1;
$[1] = t1;
} else {
t1 = $[2];
t1 = $[1];
}
return t1;
}

Some files were not shown because too many files have changed in this diff Show More