mod body;
pub(crate) mod builtins;
mod call;
pub(crate) mod decls;
mod expr;
pub(crate) mod graph;
mod host;
mod load;
pub(crate) mod model;
mod pattern;
mod schema;
pub(crate) mod suggest;
mod summary;
mod totality;
pub(crate) mod types;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
pub use host::{
Host, HostModule, ImplicitRefs, ImportRoot, LoadError, Loader, MemoryLoader, RelPath, RootSpec,
Source, SymbolType,
};
pub use load::{FileId, MAX_SOURCE_LEN};
pub use schema::{SchemaField, SchemaIndex, SchemaStruct, SchemaSum, SchemaVariant};
use self::decls::{FileScope, Global};
use self::load::{Origin, Program, Role, file_kind};
use self::model::{Fact, LocalId, SelfRead, Target, Unit, UnitId};
use self::totality::{CallGraph, Node};
use self::types::{StructId, Types};
use crate::diagnostic::{Diagnostic, Diagnostics, FileDiagnostic, NamedSource, Severity};
use crate::span::Span;
use crate::syntax::SyntaxNode;
#[derive(Debug)]
pub struct Checked {
pub(crate) program: Program,
pub(crate) types: Types,
pub(crate) globals: Vec<Global>,
pub(crate) units: Vec<Unit>,
pub(crate) root: StructId,
pub(crate) root_ty: types::TypeId,
pub(crate) entries: Vec<UnitId>,
pub(crate) implicit_parts: Vec<UnitId>,
pub(crate) let_order: Vec<types::GlobalId>,
calls: CallGraph,
pub(crate) ref_targets: BTreeMap<StructId, Vec<model::Step>>,
warnings: Diagnostics,
inputs: BTreeMap<PathBuf, FileId>,
pub(crate) symbols: Vec<SymbolType>,
pub(crate) implicit_refs: Vec<ImplicitRefs>,
pub(crate) import_roots: Vec<ImportRoot>,
}
impl Checked {
pub fn warnings(&self) -> &Diagnostics {
&self.warnings
}
pub fn files(&self) -> impl Iterator<Item = (FileId, &str)> {
self.program
.files
.iter()
.enumerate()
.map(|(i, f)| (FileId::new(i), f.name.as_str()))
}
pub fn source(&self, file: FileId) -> Option<&str> {
self.program
.files
.get(file.index())
.map(|f| f.parsed.tree.root().text())
}
pub fn inputs(&self) -> impl Iterator<Item = (&Path, &str)> {
self.inputs
.iter()
.filter_map(|(path, &file)| self.source(file).map(|text| (path.as_path(), text)))
}
pub(crate) fn named_sources(&self) -> Vec<NamedSource> {
diagnostics(&self.program, Vec::new()).files
}
pub(crate) fn let_reads(&self, g: types::GlobalId) -> bool {
matches!(
&self.globals[g.index()].kind,
decls::GlobalKind::Let(decls::LetGlobal {
state: decls::LetState::Done { reads, .. },
..
}) if !reads.is_empty()
)
}
pub(crate) fn let_annotation(&self, g: types::GlobalId) -> Option<types::TypeId> {
match &self.globals[g.index()].kind {
decls::GlobalKind::Let(l) => l.annotation,
decls::GlobalKind::Fn(_) => None,
}
}
pub(crate) fn is_fn(&self, g: types::GlobalId) -> bool {
matches!(self.globals[g.index()].kind, decls::GlobalKind::Fn(_))
}
}
fn inputs(program: &Program, spec: &RootSpec) -> BTreeMap<PathBuf, FileId> {
let mut out = BTreeMap::new();
for (i, file) in program.files.iter().enumerate() {
let path = match &file.origin {
Origin::Path { root, rel } => spec
.import_roots
.get(*root)
.map_or_else(|| rel.to_path(), |r| r.path().join(rel.to_path())),
_ => PathBuf::from(&file.name),
};
out.entry(path).or_insert(FileId::new(i));
}
out
}
pub fn check(host: &Host, root: &RootSpec) -> Result<Checked, Diagnostics> {
let (program, mut found) = load::load(host, root);
for (i, file) in program.files.iter().enumerate() {
let id = Some(FileId::new(i));
let syntax = file
.parsed
.diagnostics
.iter()
.cloned()
.chain(file.parsed.file().validate(file_kind(file.role)));
found.extend(syntax.map(|diagnostic| FileDiagnostic {
file: id,
diagnostic,
}));
}
if found
.iter()
.any(|d| d.diagnostic.severity == Severity::Error)
{
return Err(diagnostics(&program, found));
}
let (found, parts) = {
let mut checker = Checker::new(&program, host);
checker.run(root);
checker.finish()
};
match parts {
Some(parts) => {
let warnings = diagnostics(&program, found);
let inputs = inputs(&program, root);
let mut types = parts.types;
let root_ty = types.intern(types::Type::Struct(parts.root));
Ok(Checked {
program,
types,
root_ty,
globals: parts.globals,
units: parts.units,
root: parts.root,
entries: parts.entries,
implicit_parts: parts.implicit_parts,
let_order: parts.let_order,
calls: parts.calls,
ref_targets: parts.ref_targets,
warnings,
inputs,
symbols: host.symbols.clone(),
implicit_refs: host.implicit_refs.clone(),
import_roots: root.import_roots.clone(),
})
}
None => Err(diagnostics(&program, found)),
}
}
struct Parts {
types: Types,
globals: Vec<Global>,
units: Vec<Unit>,
root: StructId,
entries: Vec<UnitId>,
implicit_parts: Vec<UnitId>,
let_order: Vec<types::GlobalId>,
calls: CallGraph,
ref_targets: BTreeMap<StructId, Vec<model::Step>>,
}
fn diagnostics(program: &Program, items: Vec<FileDiagnostic>) -> Diagnostics {
Diagnostics {
files: program
.files
.iter()
.map(|f| NamedSource {
name: f.name.clone(),
text: f.parsed.tree.root().text().to_owned(),
})
.collect(),
items,
}
}
#[derive(Debug, Clone, Copy)]
enum Binding {
Local(LocalId),
Pending,
}
#[derive(Debug, Clone)]
enum SelfMode {
Forbidden(&'static str),
Default(StructId),
Body(BodyCx),
}
#[derive(Debug, Clone)]
struct BodyCx {
top: u32,
frames: Vec<Frame>,
graph: body::SelfGraph,
}
#[derive(Debug, Clone)]
struct Frame {
target: Target,
path: Vec<String>,
}
#[derive(Debug, Clone, Default)]
struct Scope {
bindings: BTreeMap<String, Vec<Binding>>,
order: Vec<String>,
}
impl Scope {
fn len(&self) -> usize {
self.order.len()
}
fn push(&mut self, (name, binding): (String, Binding)) {
self.bindings.entry(name.clone()).or_default().push(binding);
self.order.push(name);
}
fn truncate(&mut self, mark: usize) {
while self.order.len() > mark {
let Some(name) = self.order.pop() else { break };
if let Some(stack) = self.bindings.get_mut(&name) {
stack.pop();
if stack.is_empty() {
self.bindings.remove(&name);
}
}
}
}
fn lookup(&self, name: &str) -> Option<LocalId> {
self.bindings.get(name)?.iter().rev().find_map(|b| match b {
Binding::Local(id) => Some(*id),
Binding::Pending => None,
})
}
fn is_pending(&self, name: &str) -> bool {
self.bindings
.get(name)
.is_some_and(|stack| stack.iter().any(|b| matches!(b, Binding::Pending)))
}
fn names(&self) -> impl Iterator<Item = &str> {
self.bindings.keys().map(String::as_str)
}
}
#[derive(Debug, Clone)]
struct Cx {
unit: UnitId,
file: FileId,
scope: Scope,
self_mode: SelfMode,
owner: Option<Node>,
reads: Vec<SelfRead>,
limit: Option<usize>,
partial: bool,
}
impl Cx {
fn new(unit: UnitId, file: FileId, self_mode: SelfMode, owner: Option<Node>) -> Self {
Self {
unit,
file,
scope: Scope::default(),
self_mode,
owner,
reads: Vec::new(),
limit: None,
partial: false,
}
}
fn lookup(&self, name: &str) -> Option<LocalId> {
self.scope.lookup(name)
}
}
struct Checker<'p> {
program: &'p Program,
host: &'p Host,
diagnostics: Vec<FileDiagnostic>,
seen: BTreeSet<(Option<FileId>, &'static str, Span, String)>,
types: Types,
scopes: Vec<FileScope>,
prelude: FileScope,
globals: Vec<Global>,
units: Vec<Unit>,
unit_keys: BTreeMap<(FileId, Option<StructId>), UnitId>,
root: Option<StructId>,
entries: Vec<UnitId>,
implicit_parts: Vec<UnitId>,
entry_tops: BTreeMap<FileId, u32>,
next_top: u32,
calls: CallGraph,
let_order: Vec<types::GlobalId>,
ref_targets: BTreeMap<StructId, Vec<model::Step>>,
ref_uses: Vec<(FileId, Span, StructId)>,
target_maps: Option<BTreeMap<StructId, Vec<Vec<model::Step>>>>,
pending_predicates: Vec<decls::PendingPredicate<'p>>,
predicate_args: Vec<decls::PredicateArgs<'p>>,
parts: Vec<(UnitId, FileId, StructId)>,
}
impl<'p> Checker<'p> {
fn new(program: &'p Program, host: &'p Host) -> Self {
Self {
program,
host,
diagnostics: Vec::new(),
seen: BTreeSet::new(),
types: Types::new(),
scopes: (0..program.files.len())
.map(|_| FileScope::default())
.collect(),
prelude: FileScope::default(),
globals: Vec::new(),
units: Vec::new(),
unit_keys: BTreeMap::new(),
root: None,
entries: Vec::new(),
implicit_parts: Vec::new(),
entry_tops: BTreeMap::new(),
next_top: 0,
calls: CallGraph::default(),
let_order: Vec::new(),
ref_targets: BTreeMap::new(),
ref_uses: Vec::new(),
target_maps: None,
pending_predicates: Vec::new(),
predicate_args: Vec::new(),
parts: Vec::new(),
}
}
fn run(&mut self, spec: &RootSpec) {
self.declare();
self.resolve_declarations();
self.find_root(&spec.root_struct);
self.check_structure();
self.check_declaration_bodies();
if let Some(root) = self.root {
for &file in &self.program.implicit_parts {
let unit = self.part_unit(file, root);
self.implicit_parts.push(unit);
}
for &file in &self.program.entries {
let unit = self.entry_body(file, root);
self.entries.push(unit);
}
}
while !self.parts.is_empty() || !self.predicate_args.is_empty() {
self.check_parts();
self.check_predicate_args();
}
self.check_totality();
}
fn finish(mut self) -> (Vec<FileDiagnostic>, Option<Parts>) {
self.diagnostics
.sort_by_key(|d| (d.file, d.diagnostic.span.start, d.diagnostic.span.end));
let errors = self
.diagnostics
.iter()
.any(|d| d.diagnostic.severity == Severity::Error);
let parts = match self.root {
Some(root) if !errors => Some(Parts {
types: self.types,
globals: self.globals,
units: self.units,
root,
entries: self.entries,
implicit_parts: self.implicit_parts,
let_order: self.let_order,
calls: self.calls,
ref_targets: self.ref_targets,
}),
_ => None,
};
(self.diagnostics, parts)
}
fn report(&mut self, file: Option<FileId>, diagnostic: Diagnostic) {
let key = (
file,
diagnostic.code,
diagnostic.span,
diagnostic.message.clone(),
);
if self.seen.insert(key) {
self.diagnostics.push(FileDiagnostic { file, diagnostic });
}
}
fn error(&mut self, file: FileId, diagnostic: Diagnostic) {
self.report(Some(file), diagnostic);
}
fn fact(&mut self, cx: &Cx, node: SyntaxNode<'_>, fact: Fact) {
if let Some(slot) = self
.units
.get_mut(cx.unit.index())
.and_then(|unit| unit.facts.get_mut(node.index()))
{
*slot = fact;
}
}
fn file_unit(&mut self, file: FileId) -> UnitId {
self.unit(file, None).0
}
fn unit(&mut self, file: FileId, target: Option<StructId>) -> (UnitId, bool) {
if let Some(&unit) = self.unit_keys.get(&(file, target)) {
return (unit, false);
}
let unit = UnitId::new(self.units.len());
let count = self.program.file(file).parsed.tree.node_count();
self.units.push(Unit {
file,
target,
facts: vec![Fact::None; count],
locals: Vec::new(),
});
self.unit_keys.insert((file, target), unit);
(unit, true)
}
fn new_top(&mut self) -> u32 {
self.next_top += 1;
self.next_top
}
fn role(&self, file: FileId) -> Role {
self.program.file(file).role
}
}