@informalsystems/quint
Version:
Core tool for the Quint specification language
146 lines • 5.77 kB
JavaScript
;
/* ----------------------------------------------------------------------------------
* Copyright 2022-2023 Informal Systems
* Licensed under the Apache License, Version 2.0.
* See LICENSE in the project root for license information.
* --------------------------------------------------------------------------------- */
Object.defineProperty(exports, "__esModule", { value: true });
exports.NameResolver = exports.resolveNames = void 0;
/**
* Name resolution for Quint modules.
*
* @author Gabriela Moreira
*
* @module
*/
const IRVisitor_1 = require("../ir/IRVisitor");
const base_1 = require("./base");
const collector_1 = require("./collector");
const lodash_1 = require("lodash");
/**
* Resolves all names in the given Quint modules and returns a lookup table of definitions.
*
* @param quintModules - The Quint modules to resolve names in.
*
* @returns A lookup table of definitions and a mapping of unused definitions if successful, otherwise a list of errors.
*/
function resolveNames(quintModules) {
const visitor = new NameResolver();
quintModules.forEach(module => {
(0, IRVisitor_1.walkModule)(visitor, module);
});
return {
table: visitor.table,
unusedDefinitions: visitor.unusedDefinitions,
errors: visitor.errors,
resolver: visitor,
};
}
exports.resolveNames = resolveNames;
/**
* `NameResolver` uses `NameCollector` to collect top-level definitions. Scoped
* definitions are collected inside of `NameResolver` as it navigates the IR.
*/
class NameResolver {
constructor() {
this.errors = [];
this.table = new Map();
// the current depth of operator definitions: top-level defs are depth 0
// FIXME(#1279): The walk* functions update this value, but they need to be
// initialized to -1 here for that to work on all scenarios.
this.definitionDepth = -1;
this.unusedDefinitions = moduleName => {
const definitions = Array.from(this.collector.definitionsByModule.get(moduleName)?.values() || []).flat();
const usedDefinitions = [...this.table.values()].flat();
return new Set((0, lodash_1.difference)(definitions, usedDefinitions));
};
this.collector = new collector_1.NameCollector();
// bind the errors so they are aggregated in the same array
this.collector.errors = this.errors;
}
switchToModule(moduleName) {
this.collector.switchToModule(moduleName);
}
enterModule(module) {
// First thing to do in resolving names for a module is to collect all
// top-level definitions for that module. This has to be done in a separate
// pass because names can appear before they are defined.
(0, IRVisitor_1.walkModule)(this.collector, module);
}
enterOpDef(def) {
// Top-level definitions were already collected, so we only need to collect
// scoped definitions.
if (this.definitionDepth > 0) {
const newDef = this.collector.collectDefinition({ ...def, depth: this.definitionDepth });
this.table.set(def.id, { ...newDef, depth: this.definitionDepth });
}
else {
// Map the definition to itself so we can recover depth information from the table
this.table.set(def.id, { ...def, depth: this.definitionDepth });
}
}
exitLet(expr) {
// When exiting a let, delete the operator definition so it is not accessed
// outside of the let scope
this.collector.deleteDefinition(expr.opdef.name);
}
enterLambda(expr) {
// Lambda parameters are scoped, so they are collected here
expr.params.forEach(p => {
this.collector.collectDefinition({ ...p, kind: 'param', depth: this.definitionDepth });
});
}
exitLambda(expr) {
// Similar to let, delete the parameter definitions when exiting the lambda
// so they are not accessed outside of the lambda scope
expr.params.forEach(p => {
this.collector.deleteDefinition(p.name);
});
}
enterName(nameExpr) {
// Name expression, check that the name is defined
this.resolveName(nameExpr.name, nameExpr.id);
}
enterApp(appExpr) {
// Application, check that the operator being applied is defined
this.resolveName(appExpr.opcode, appExpr.id);
}
enterConstType(type) {
// Type is a name, check that it is defined
const def = this.collector.getDefinition(type.name);
if (!def || def.kind !== 'typedef') {
this.recordNameError('type', type.name, type.id);
return;
}
this.table.set(type.id, def);
}
enterInstance(def) {
// Resolve overridden param names in the current module
def.overrides.forEach(([param, _]) => {
const qualifiedName = def.qualifiedName ? `${def.qualifiedName}::${param.name}` : param.name;
this.resolveName(qualifiedName, param.id);
});
}
resolveName(name, id) {
if (base_1.builtinNames.includes(name)) {
return;
}
const def = this.collector.getDefinition(name);
if (!def || def.kind === 'typedef') {
this.recordNameError('name', name, id);
return;
}
this.table.set(id, def);
}
recordNameError(kind, name, id) {
const description = kind === 'name' ? 'Name' : 'Type alias';
this.errors.push({
code: 'QNT404',
message: `${description} '${name}' not found`,
reference: id,
data: {},
});
}
}
exports.NameResolver = NameResolver;
//# sourceMappingURL=resolver.js.map