import {CompilerDiagnostic, CompilerError} from '..';
import {ErrorCategory} from '../CompilerError';
import {Environment} from '../HIR/Environment';
import {HIRFunction, IdentifierId, Place} from '../HIR';
import {printPlace} from '../HIR/PrintHIR';
import {eachInstructionValueLValue, eachPatternOperand} from '../HIR/visitors';
export function validateContextVariableLValues(fn: HIRFunction): void {
const identifierKinds: IdentifierKinds = new Map();
validateContextVariableLValuesImpl(fn, identifierKinds, fn.env);
}
function validateContextVariableLValuesImpl(
fn: HIRFunction,
identifierKinds: IdentifierKinds,
env: Environment,
): void {
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const {value} = instr;
switch (value.kind) {
case 'DeclareContext':
case 'StoreContext': {
visit(identifierKinds, value.lvalue.place, 'context', env);
break;
}
case 'LoadContext': {
visit(identifierKinds, value.place, 'context', env);
break;
}
case 'StoreLocal':
case 'DeclareLocal': {
visit(identifierKinds, value.lvalue.place, 'local', env);
break;
}
case 'LoadLocal': {
visit(identifierKinds, value.place, 'local', env);
break;
}
case 'PostfixUpdate':
case 'PrefixUpdate': {
visit(identifierKinds, value.lvalue, 'local', env);
break;
}
case 'Destructure': {
for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
visit(identifierKinds, lvalue, 'destructure', env);
}
break;
}
case 'ObjectMethod':
case 'FunctionExpression': {
validateContextVariableLValuesImpl(
value.loweredFunc.func,
identifierKinds,
env,
);
break;
}
default: {
for (const _ of eachInstructionValueLValue(value)) {
fn.env.recordError(
CompilerDiagnostic.create({
category: ErrorCategory.Todo,
reason:
'ValidateContextVariableLValues: unhandled instruction variant',
description: `Handle '${value.kind} lvalues`,
}).withDetails({
kind: 'error',
loc: value.loc,
message: null,
}),
);
}
}
}
}
}
}
type IdentifierKinds = Map<
IdentifierId,
{place: Place; kind: 'local' | 'context' | 'destructure'}
>;
function visit(
identifiers: IdentifierKinds,
place: Place,
kind: 'local' | 'context' | 'destructure',
env: Environment,
): void {
const prev = identifiers.get(place.identifier.id);
if (prev !== undefined) {
const wasContext = prev.kind === 'context';
const isContext = kind === 'context';
if (wasContext !== isContext) {
if (prev.kind === 'destructure' || kind === 'destructure') {
env.recordError(
CompilerDiagnostic.create({
category: ErrorCategory.Todo,
reason: `Support destructuring of context variables`,
description: null,
}).withDetails({
kind: 'error',
loc: kind === 'destructure' ? place.loc : prev.place.loc,
message: null,
}),
);
return;
}
CompilerError.invariant(false, {
reason:
'Expected all references to a variable to be consistently local or context references',
description: `Identifier ${printPlace(place)} is referenced as a ${kind} variable, but was previously referenced as a ${prev.kind} variable`,
message: `this is ${prev.kind}`,
loc: place.loc,
});
}
}
identifiers.set(place.identifier.id, {place, kind});
}