import {
Destructure,
Environment,
IdentifierId,
InstructionKind,
Place,
ReactiveFunction,
ReactiveInstruction,
ReactiveScopeBlock,
ReactiveStatement,
promoteTemporary,
} from "../HIR";
import { eachPatternOperand, mapPatternOperands } from "../HIR/visitors";
import {
ReactiveFunctionTransform,
Transformed,
visitReactiveFunction,
} from "./visitors";
export function extractScopeDeclarationsFromDestructuring(
fn: ReactiveFunction
): void {
const state = new State(fn.env);
visitReactiveFunction(fn, new Visitor(), state);
}
class State {
env: Environment;
declared: Set<IdentifierId> = new Set();
constructor(env: Environment) {
this.env = env;
}
}
class Visitor extends ReactiveFunctionTransform<State> {
override visitScope(scope: ReactiveScopeBlock, state: State): void {
for (const [, declaration] of scope.scope.declarations) {
state.declared.add(declaration.identifier.id);
}
this.traverseScope(scope, state);
}
override transformInstruction(
instruction: ReactiveInstruction,
state: State
): Transformed<ReactiveStatement> {
this.visitInstruction(instruction, state);
if (instruction.value.kind === "Destructure") {
const transformed = transformDestructuring(
state,
instruction,
instruction.value
);
if (transformed) {
return {
kind: "replace-many",
value: transformed.map((instruction) => ({
kind: "instruction",
instruction,
})),
};
}
}
return { kind: "keep" };
}
}
function transformDestructuring(
state: State,
instr: ReactiveInstruction,
destructure: Destructure
): null | Array<ReactiveInstruction> {
let reassigned: Set<IdentifierId> = new Set();
let hasDeclaration = false;
for (const place of eachPatternOperand(destructure.lvalue.pattern)) {
const isDeclared = state.declared.has(place.identifier.id);
if (isDeclared) {
reassigned.add(place.identifier.id);
}
hasDeclaration ||= !isDeclared;
}
if (reassigned.size === 0 || !hasDeclaration) {
return null;
}
const instructions: Array<ReactiveInstruction> = [];
const renamed: Map<Place, Place> = new Map();
mapPatternOperands(destructure.lvalue.pattern, (place) => {
if (!reassigned.has(place.identifier.id)) {
return place;
}
const tempId = state.env.nextIdentifierId;
const temporary = {
...place,
identifier: {
...place.identifier,
id: tempId,
name: null,
},
};
promoteTemporary(temporary.identifier);
renamed.set(place, temporary);
return temporary;
});
instructions.push(instr);
for (const [original, temporary] of renamed) {
instructions.push({
id: instr.id,
lvalue: null,
value: {
kind: "StoreLocal",
lvalue: {
kind: InstructionKind.Reassign,
place: original,
},
value: temporary,
type: null,
loc: destructure.loc,
},
loc: instr.loc,
});
}
return instructions;
}