import { CompilerError } from "..";
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);
}
function validateContextVariableLValuesImpl(
fn: HIRFunction,
identifierKinds: IdentifierKinds
): 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");
break;
}
case "LoadContext": {
visit(identifierKinds, value.place, "context");
break;
}
case "StoreLocal":
case "DeclareLocal": {
visit(identifierKinds, value.lvalue.place, "local");
break;
}
case "LoadLocal": {
visit(identifierKinds, value.place, "local");
break;
}
case "PostfixUpdate":
case "PrefixUpdate": {
visit(identifierKinds, value.lvalue, "local");
break;
}
case "Destructure": {
for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
visit(identifierKinds, lvalue, "destructure");
}
break;
}
case "ObjectMethod":
case "FunctionExpression": {
validateContextVariableLValuesImpl(
value.loweredFunc.func,
identifierKinds
);
break;
}
default: {
for (const _ of eachInstructionValueLValue(value)) {
CompilerError.throwTodo({
reason:
"ValidateContextVariableLValues: unhandled instruction variant",
loc: value.loc,
description: `Handle '${value.kind} lvalues`,
suggestions: null,
});
}
}
}
}
}
}
type IdentifierKinds = Map<
IdentifierId,
{ place: Place; kind: "local" | "context" | "destructure" }
>;
function visit(
identifiers: IdentifierKinds,
place: Place,
kind: "local" | "context" | "destructure"
): 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") {
CompilerError.throwTodo({
reason: `Support destructuring of context variables`,
loc: kind === "destructure" ? place.loc : prev.place.loc,
description: null,
suggestions: null,
});
}
CompilerError.invariant(false, {
reason: `Expected all references to a variable to be consistently local or context references`,
loc: place.loc,
description: `Identifier ${printPlace(
place
)} is referenced as a ${kind} variable, but was previously referenced as a ${prev} variable`,
suggestions: null,
});
}
}
identifiers.set(place.identifier.id, { place, kind });
}