import {CompilerError, ErrorSeverity} from '..';
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
import {Result} from '../Utils/Result';
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
const errors = new CompilerError();
const useMemos = new Set<IdentifierId>();
const react = new Set<IdentifierId>();
const functions = new Map<IdentifierId, FunctionExpression>();
for (const [, block] of fn.body.blocks) {
for (const {lvalue, value} of block.instructions) {
switch (value.kind) {
case 'LoadGlobal': {
if (value.binding.name === 'useMemo') {
useMemos.add(lvalue.identifier.id);
} else if (value.binding.name === 'React') {
react.add(lvalue.identifier.id);
}
break;
}
case 'PropertyLoad': {
if (react.has(value.object.identifier.id)) {
if (value.property === 'useMemo') {
useMemos.add(lvalue.identifier.id);
}
}
break;
}
case 'FunctionExpression': {
functions.set(lvalue.identifier.id, value);
break;
}
case 'MethodCall':
case 'CallExpression': {
const callee =
value.kind === 'CallExpression'
? value.callee.identifier.id
: value.property.identifier.id;
const isUseMemo = useMemos.has(callee);
if (!isUseMemo || value.args.length === 0) {
continue;
}
const [arg] = value.args;
if (arg.kind !== 'Identifier') {
continue;
}
const body = functions.get(arg.identifier.id);
if (body === undefined) {
continue;
}
if (body.loweredFunc.func.params.length > 0) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo callbacks may not accept any arguments',
description: null,
loc: body.loc,
suggestions: null,
});
}
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
'useMemo callbacks may not be async or generator functions',
description: null,
loc: body.loc,
suggestions: null,
});
}
break;
}
}
}
}
return errors.asResult();
}