1mod body;
10pub(crate) mod builtins;
11mod call;
12pub(crate) mod decls;
13mod expr;
14pub(crate) mod graph;
15mod host;
16mod load;
17pub(crate) mod model;
18mod pattern;
19mod schema;
20pub(crate) mod suggest;
21mod summary;
22mod totality;
23pub(crate) mod types;
24
25use std::collections::{BTreeMap, BTreeSet};
26use std::path::{Path, PathBuf};
27
28pub use host::{
29 Host, HostModule, ImplicitRefs, ImportRoot, LoadError, Loader, MemoryLoader, RelPath, RootSpec,
30 Source, SymbolType,
31};
32pub use load::{FileId, MAX_SOURCE_LEN};
33pub use schema::{SchemaField, SchemaIndex, SchemaStruct, SchemaSum, SchemaVariant};
34
35use self::decls::{FileScope, Global};
36use self::load::{Origin, Program, Role, file_kind};
37use self::model::{Fact, LocalId, SelfRead, Target, Unit, UnitId};
38use self::totality::{CallGraph, Node};
39use self::types::{StructId, Types};
40use crate::diagnostic::{Diagnostic, Diagnostics, FileDiagnostic, NamedSource, Severity};
41use crate::span::Span;
42use crate::syntax::SyntaxNode;
43
44#[derive(Debug)]
50pub struct Checked {
51 pub(crate) program: Program,
52 pub(crate) types: Types,
53 pub(crate) globals: Vec<Global>,
54 pub(crate) units: Vec<Unit>,
55 pub(crate) root: StructId,
56 pub(crate) root_ty: types::TypeId,
57 pub(crate) entries: Vec<UnitId>,
58 pub(crate) implicit_parts: Vec<UnitId>,
59 pub(crate) let_order: Vec<types::GlobalId>,
60 calls: CallGraph,
61 pub(crate) ref_targets: BTreeMap<StructId, Vec<model::Step>>,
62 warnings: Diagnostics,
63 inputs: BTreeMap<PathBuf, FileId>,
64 pub(crate) symbols: Vec<SymbolType>,
65 pub(crate) implicit_refs: Vec<ImplicitRefs>,
66 pub(crate) import_roots: Vec<ImportRoot>,
67}
68
69impl Checked {
70 pub fn warnings(&self) -> &Diagnostics {
72 &self.warnings
73 }
74
75 pub fn files(&self) -> impl Iterator<Item = (FileId, &str)> {
78 self.program
79 .files
80 .iter()
81 .enumerate()
82 .map(|(i, f)| (FileId::new(i), f.name.as_str()))
83 }
84
85 pub fn source(&self, file: FileId) -> Option<&str> {
87 self.program
88 .files
89 .get(file.index())
90 .map(|f| f.parsed.tree.root().text())
91 }
92
93 pub fn inputs(&self) -> impl Iterator<Item = (&Path, &str)> {
104 self.inputs
105 .iter()
106 .filter_map(|(path, &file)| self.source(file).map(|text| (path.as_path(), text)))
107 }
108
109 pub(crate) fn named_sources(&self) -> Vec<NamedSource> {
111 diagnostics(&self.program, Vec::new()).files
112 }
113
114 pub(crate) fn let_reads(&self, g: types::GlobalId) -> bool {
116 matches!(
117 &self.globals[g.index()].kind,
118 decls::GlobalKind::Let(decls::LetGlobal {
119 state: decls::LetState::Done { reads, .. },
120 ..
121 }) if !reads.is_empty()
122 )
123 }
124
125 pub(crate) fn let_annotation(&self, g: types::GlobalId) -> Option<types::TypeId> {
127 match &self.globals[g.index()].kind {
128 decls::GlobalKind::Let(l) => l.annotation,
129 decls::GlobalKind::Fn(_) => None,
130 }
131 }
132
133 pub(crate) fn is_fn(&self, g: types::GlobalId) -> bool {
135 matches!(self.globals[g.index()].kind, decls::GlobalKind::Fn(_))
136 }
137}
138
139fn inputs(program: &Program, spec: &RootSpec) -> BTreeMap<PathBuf, FileId> {
142 let mut out = BTreeMap::new();
143 for (i, file) in program.files.iter().enumerate() {
144 let path = match &file.origin {
145 Origin::Path { root, rel } => spec
146 .import_roots
147 .get(*root)
148 .map_or_else(|| rel.to_path(), |r| r.path().join(rel.to_path())),
149 _ => PathBuf::from(&file.name),
150 };
151 out.entry(path).or_insert(FileId::new(i));
152 }
153 out
154}
155
156pub fn check(host: &Host, root: &RootSpec) -> Result<Checked, Diagnostics> {
188 let (program, mut found) = load::load(host, root);
189 for (i, file) in program.files.iter().enumerate() {
190 let id = Some(FileId::new(i));
191 let syntax = file
192 .parsed
193 .diagnostics
194 .iter()
195 .cloned()
196 .chain(file.parsed.file().validate(file_kind(file.role)));
197 found.extend(syntax.map(|diagnostic| FileDiagnostic {
198 file: id,
199 diagnostic,
200 }));
201 }
202 if found
203 .iter()
204 .any(|d| d.diagnostic.severity == Severity::Error)
205 {
206 return Err(diagnostics(&program, found));
207 }
208 let (found, parts) = {
209 let mut checker = Checker::new(&program, host);
210 checker.run(root);
211 checker.finish()
212 };
213 match parts {
214 Some(parts) => {
215 let warnings = diagnostics(&program, found);
216 let inputs = inputs(&program, root);
217 let mut types = parts.types;
218 let root_ty = types.intern(types::Type::Struct(parts.root));
219 Ok(Checked {
220 program,
221 types,
222 root_ty,
223 globals: parts.globals,
224 units: parts.units,
225 root: parts.root,
226 entries: parts.entries,
227 implicit_parts: parts.implicit_parts,
228 let_order: parts.let_order,
229 calls: parts.calls,
230 ref_targets: parts.ref_targets,
231 warnings,
232 inputs,
233 symbols: host.symbols.clone(),
234 implicit_refs: host.implicit_refs.clone(),
235 import_roots: root.import_roots.clone(),
236 })
237 }
238 None => Err(diagnostics(&program, found)),
239 }
240}
241
242struct Parts {
244 types: Types,
245 globals: Vec<Global>,
246 units: Vec<Unit>,
247 root: StructId,
248 entries: Vec<UnitId>,
249 implicit_parts: Vec<UnitId>,
250 let_order: Vec<types::GlobalId>,
251 calls: CallGraph,
252 ref_targets: BTreeMap<StructId, Vec<model::Step>>,
253}
254
255fn diagnostics(program: &Program, items: Vec<FileDiagnostic>) -> Diagnostics {
256 Diagnostics {
257 files: program
258 .files
259 .iter()
260 .map(|f| NamedSource {
261 name: f.name.clone(),
262 text: f.parsed.tree.root().text().to_owned(),
263 })
264 .collect(),
265 items,
266 }
267}
268
269#[derive(Debug, Clone, Copy)]
271enum Binding {
272 Local(LocalId),
273 Pending,
278}
279
280#[derive(Debug, Clone)]
282enum SelfMode {
283 Forbidden(&'static str),
286 Default(StructId),
288 Body(BodyCx),
290}
291
292#[derive(Debug, Clone)]
294struct BodyCx {
295 top: u32,
298 frames: Vec<Frame>,
299 graph: body::SelfGraph,
300}
301
302#[derive(Debug, Clone)]
304struct Frame {
305 target: Target,
306 path: Vec<String>,
309}
310
311#[derive(Debug, Clone, Default)]
313struct Scope {
314 bindings: BTreeMap<String, Vec<Binding>>,
315 order: Vec<String>,
316}
317
318impl Scope {
319 fn len(&self) -> usize {
321 self.order.len()
322 }
323
324 fn push(&mut self, (name, binding): (String, Binding)) {
325 self.bindings.entry(name.clone()).or_default().push(binding);
326 self.order.push(name);
327 }
328
329 fn truncate(&mut self, mark: usize) {
331 while self.order.len() > mark {
332 let Some(name) = self.order.pop() else { break };
333 if let Some(stack) = self.bindings.get_mut(&name) {
334 stack.pop();
335 if stack.is_empty() {
336 self.bindings.remove(&name);
337 }
338 }
339 }
340 }
341
342 fn lookup(&self, name: &str) -> Option<LocalId> {
344 self.bindings.get(name)?.iter().rev().find_map(|b| match b {
345 Binding::Local(id) => Some(*id),
346 Binding::Pending => None,
347 })
348 }
349
350 fn is_pending(&self, name: &str) -> bool {
353 self.bindings
354 .get(name)
355 .is_some_and(|stack| stack.iter().any(|b| matches!(b, Binding::Pending)))
356 }
357
358 fn names(&self) -> impl Iterator<Item = &str> {
359 self.bindings.keys().map(String::as_str)
360 }
361}
362
363#[derive(Debug, Clone)]
365struct Cx {
366 unit: UnitId,
367 file: FileId,
368 scope: Scope,
369 self_mode: SelfMode,
370 owner: Option<Node>,
372 reads: Vec<SelfRead>,
374 limit: Option<usize>,
377 partial: bool,
380}
381
382impl Cx {
383 fn new(unit: UnitId, file: FileId, self_mode: SelfMode, owner: Option<Node>) -> Self {
384 Self {
385 unit,
386 file,
387 scope: Scope::default(),
388 self_mode,
389 owner,
390 reads: Vec::new(),
391 limit: None,
392 partial: false,
393 }
394 }
395
396 fn lookup(&self, name: &str) -> Option<LocalId> {
397 self.scope.lookup(name)
398 }
399}
400
401struct Checker<'p> {
403 program: &'p Program,
404 host: &'p Host,
405 diagnostics: Vec<FileDiagnostic>,
406 seen: BTreeSet<(Option<FileId>, &'static str, Span, String)>,
407 types: Types,
408 scopes: Vec<FileScope>,
409 prelude: FileScope,
410 globals: Vec<Global>,
411 units: Vec<Unit>,
412 unit_keys: BTreeMap<(FileId, Option<StructId>), UnitId>,
413 root: Option<StructId>,
414 entries: Vec<UnitId>,
415 implicit_parts: Vec<UnitId>,
416 entry_tops: BTreeMap<FileId, u32>,
417 next_top: u32,
418 calls: CallGraph,
419 let_order: Vec<types::GlobalId>,
420 ref_targets: BTreeMap<StructId, Vec<model::Step>>,
421 ref_uses: Vec<(FileId, Span, StructId)>,
422 target_maps: Option<BTreeMap<StructId, Vec<Vec<model::Step>>>>,
425 pending_predicates: Vec<decls::PendingPredicate<'p>>,
426 predicate_args: Vec<decls::PredicateArgs<'p>>,
427 parts: Vec<(UnitId, FileId, StructId)>,
431}
432
433impl<'p> Checker<'p> {
434 fn new(program: &'p Program, host: &'p Host) -> Self {
435 Self {
436 program,
437 host,
438 diagnostics: Vec::new(),
439 seen: BTreeSet::new(),
440 types: Types::new(),
441 scopes: (0..program.files.len())
442 .map(|_| FileScope::default())
443 .collect(),
444 prelude: FileScope::default(),
445 globals: Vec::new(),
446 units: Vec::new(),
447 unit_keys: BTreeMap::new(),
448 root: None,
449 entries: Vec::new(),
450 implicit_parts: Vec::new(),
451 entry_tops: BTreeMap::new(),
452 next_top: 0,
453 calls: CallGraph::default(),
454 let_order: Vec::new(),
455 ref_targets: BTreeMap::new(),
456 ref_uses: Vec::new(),
457 target_maps: None,
458 pending_predicates: Vec::new(),
459 predicate_args: Vec::new(),
460 parts: Vec::new(),
461 }
462 }
463
464 fn run(&mut self, spec: &RootSpec) {
465 self.declare();
466 self.resolve_declarations();
467 self.find_root(&spec.root_struct);
468 self.check_structure();
469 self.check_declaration_bodies();
470 if let Some(root) = self.root {
471 for &file in &self.program.implicit_parts {
472 let unit = self.part_unit(file, root);
473 self.implicit_parts.push(unit);
474 }
475 for &file in &self.program.entries {
476 let unit = self.entry_body(file, root);
477 self.entries.push(unit);
478 }
479 }
480 while !self.parts.is_empty() || !self.predicate_args.is_empty() {
481 self.check_parts();
482 self.check_predicate_args();
483 }
484 self.check_totality();
485 }
486
487 fn finish(mut self) -> (Vec<FileDiagnostic>, Option<Parts>) {
490 self.diagnostics
491 .sort_by_key(|d| (d.file, d.diagnostic.span.start, d.diagnostic.span.end));
492 let errors = self
493 .diagnostics
494 .iter()
495 .any(|d| d.diagnostic.severity == Severity::Error);
496 let parts = match self.root {
497 Some(root) if !errors => Some(Parts {
498 types: self.types,
499 globals: self.globals,
500 units: self.units,
501 root,
502 entries: self.entries,
503 implicit_parts: self.implicit_parts,
504 let_order: self.let_order,
505 calls: self.calls,
506 ref_targets: self.ref_targets,
507 }),
508 _ => None,
509 };
510 (self.diagnostics, parts)
511 }
512
513 fn report(&mut self, file: Option<FileId>, diagnostic: Diagnostic) {
514 let key = (
515 file,
516 diagnostic.code,
517 diagnostic.span,
518 diagnostic.message.clone(),
519 );
520 if self.seen.insert(key) {
521 self.diagnostics.push(FileDiagnostic { file, diagnostic });
522 }
523 }
524
525 fn error(&mut self, file: FileId, diagnostic: Diagnostic) {
526 self.report(Some(file), diagnostic);
527 }
528
529 fn fact(&mut self, cx: &Cx, node: SyntaxNode<'_>, fact: Fact) {
530 if let Some(slot) = self
531 .units
532 .get_mut(cx.unit.index())
533 .and_then(|unit| unit.facts.get_mut(node.index()))
534 {
535 *slot = fact;
536 }
537 }
538
539 fn file_unit(&mut self, file: FileId) -> UnitId {
542 self.unit(file, None).0
543 }
544
545 fn unit(&mut self, file: FileId, target: Option<StructId>) -> (UnitId, bool) {
548 if let Some(&unit) = self.unit_keys.get(&(file, target)) {
549 return (unit, false);
550 }
551 let unit = UnitId::new(self.units.len());
552 let count = self.program.file(file).parsed.tree.node_count();
553 self.units.push(Unit {
554 file,
555 target,
556 facts: vec![Fact::None; count],
557 locals: Vec::new(),
558 });
559 self.unit_keys.insert((file, target), unit);
560 (unit, true)
561 }
562
563 fn new_top(&mut self) -> u32 {
564 self.next_top += 1;
565 self.next_top
566 }
567
568 fn role(&self, file: FileId) -> Role {
569 self.program.file(file).role
570 }
571}