import {
DeclarationId,
Destructure,
Environment,
IdentifierId,
InstructionKind,
Place,
ReactiveFunction,
ReactiveInstruction,
ReactiveScopeBlock,
ReactiveStatement,
promoteTemporary,
} from '../HIR';
import {clonePlaceToTemporary} from '../HIR/HIRBuilder';
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<DeclarationId> = 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.declarationId);
}
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.declarationId);
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 temporary = clonePlaceToTemporary(state.env, place);
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;
}