import {visitReactiveFunction} from '.';
import {CompilerError} from '..';
import {
InstructionId,
Place,
ReactiveFunction,
ReactiveScopeBlock,
ScopeId,
} from '../HIR';
import {getPlaceScope} from '../HIR/HIR';
import {ReactiveFunctionVisitor} from './visitors';
export function assertScopeInstructionsWithinScopes(
fn: ReactiveFunction,
): void {
const existingScopes = new Set<ScopeId>();
visitReactiveFunction(fn, new FindAllScopesVisitor(), existingScopes);
visitReactiveFunction(
fn,
new CheckInstructionsAgainstScopesVisitor(),
existingScopes,
);
}
class FindAllScopesVisitor extends ReactiveFunctionVisitor<Set<ScopeId>> {
override visitScope(block: ReactiveScopeBlock, state: Set<ScopeId>): void {
this.traverseScope(block, state);
state.add(block.scope.id);
}
}
class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
Set<ScopeId>
> {
activeScopes: Set<ScopeId> = new Set();
override visitPlace(
id: InstructionId,
place: Place,
state: Set<ScopeId>,
): void {
const scope = getPlaceScope(id, place);
if (
scope !== null &&
state.has(scope.id) &&
!this.activeScopes.has(scope.id)
) {
CompilerError.invariant(false, {
description: `Instruction [${id}] is part of scope @${scope.id}, but that scope has already completed.`,
loc: place.loc,
reason:
'Encountered an instruction that should be part of a scope, but where that scope has already completed',
suggestions: null,
});
}
}
override visitScope(block: ReactiveScopeBlock, state: Set<ScopeId>): void {
this.activeScopes.add(block.scope.id);
this.traverseScope(block, state);
this.activeScopes.delete(block.scope.id);
}
}