import { CompilerError, ErrorSeverity } from "../CompilerError";
import { HIRFunction, IdentifierId, Place, isSetStateType } from "../HIR";
import { computeUnconditionalBlocks } from "../HIR/ComputeUnconditionalBlocks";
import { eachInstructionValueOperand } from "../HIR/visitors";
import { Err, Ok, Result } from "../Utils/Result";
export function validateNoSetStateInRender(fn: HIRFunction): void {
const unconditionalSetStateFunctions: Set<IdentifierId> = new Set();
validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions).unwrap();
}
function validateNoSetStateInRenderImpl(
fn: HIRFunction,
unconditionalSetStateFunctions: Set<IdentifierId>
): Result<void, CompilerError> {
const unconditionalBlocks = computeUnconditionalBlocks(fn);
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
if (unconditionalBlocks.has(block.id)) {
for (const instr of block.instructions) {
switch (instr.value.kind) {
case "LoadLocal": {
if (
unconditionalSetStateFunctions.has(
instr.value.place.identifier.id
)
) {
unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
}
break;
}
case "StoreLocal": {
if (
unconditionalSetStateFunctions.has(
instr.value.value.identifier.id
)
) {
unconditionalSetStateFunctions.add(
instr.value.lvalue.place.identifier.id
);
unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
}
break;
}
case "ObjectMethod":
case "FunctionExpression": {
if (
[...eachInstructionValueOperand(instr.value)].some(
(operand) =>
isSetStateType(operand.identifier) ||
unconditionalSetStateFunctions.has(operand.identifier.id)
) &&
validateNoSetStateInRenderImpl(
instr.value.loweredFunc.func,
unconditionalSetStateFunctions
).isErr()
) {
unconditionalSetStateFunctions.add(instr.lvalue.identifier.id);
}
break;
}
case "CallExpression": {
validateNonSetState(
errors,
unconditionalSetStateFunctions,
instr.value.callee
);
break;
}
}
}
}
}
if (errors.hasErrors()) {
return Err(errors);
} else {
return Ok(undefined);
}
}
function validateNonSetState(
errors: CompilerError,
unconditionalSetStateFunctions: Set<IdentifierId>,
operand: Place
): void {
if (
isSetStateType(operand.identifier) ||
unconditionalSetStateFunctions.has(operand.identifier.id)
) {
errors.push({
reason:
"This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)",
description: null,
severity: ErrorSeverity.InvalidReact,
loc: typeof operand.loc !== "symbol" ? operand.loc : null,
suggestions: null,
});
}
}