cove_sema/facts.rs
1//! What the checker worked out about each expression.
2//!
3//! The type checker settles a type for every expression it walks and, until
4//! now, threw each one away as soon as the surrounding form had been checked
5//! against it. Everything downstream that needed one had to work it out
6//! again — and a pass that walks a tree without the checker's tables cannot,
7//! so it guessed from the shape of the source instead. ADR 0019 states the
8//! rule this module exists to make keepable: the lowering reads the
9//! checker's answers rather than recomputing them, so the two cannot
10//! disagree.
11//!
12//! # Recording is not deciding
13//!
14//! Nothing here participates in checking. A fact is written after the
15//! checker has settled it and is read by nobody during the walk, so adding
16//! one changes no diagnostic. That is the property this table is worth
17//! having only if it keeps.
18//!
19//! # Keys are dense integers, so a lookup is a load
20//!
21//! An expression is named by the file it was parsed from and its
22//! [`ExprId`], which [`cove_syntax::number::number_unit`] hands out as
23//! `0..n` over one file with no gaps. Both halves are therefore an index
24//! into a `Vec` rather than a hash of anything, which is what lets the
25//! checker afford a push per expression.
26//!
27//! # An unknown is an answer
28//!
29//! [`Facts::ty`] answers `None` only for an expression the checker never
30//! walked. An expression it walked and could say nothing about answers
31//! `Some(`[`Ty::Unknown`]`(..))`, because "the checker abstained" and "I
32//! never asked" are different facts and a consumer specialising on one must
33//! not act on the other.
34//!
35//! # A name a call resolves is not an expression the checker types
36//!
37//! The table is total over a function's expressions with one exception, and
38//! it is the checker's shape rather than an omission here. A callee is
39//! walked only when the call goes through a value: `f(1)` where `f` is a
40//! binding evaluates `f`, and so does a call through a field holding a
41//! closure. A callee that instead *names* a declaration — a function, a
42//! struct being initialized, an enum case, a type's associated function, or
43//! a method reached through a receiver — is resolved against the checker's
44//! tables and never given a type, because several of those have none to be
45//! given: `Point` in `Point(x: 0.0)` names a type, not a value.
46//!
47//! So `ty` answers `None` for those, and that answer carries the
48//! distinction rather than losing it: a callee with a recorded type is a
49//! call through a value, and a callee without one is a call to a
50//! declaration — which [`Facts::target`] then names.
51//!
52//! # A declaration's boundary is a fact too, and it is a small one
53//!
54//! An expression is not the only thing the checker settles. It also resolves
55//! every declaration's signature — what each parameter is, what the receiver
56//! is, what comes back — and a consumer that has to know where a parameter
57//! lives, or which stack an answer comes back on, is asking about the
58//! signature rather than about any expression inside the body. Re-deriving
59//! it from the source would be the same mistake in a new place: a `->
60//! module.Thing` written in one module and read in another is a name whose
61//! meaning only the checker holds.
62//!
63//! [`Facts::signature`] is that table, and it is keyed differently from the
64//! expression tables on purpose — see [`Facts::signature`] for why a hash is
65//! the right shape here and the wrong one there.
66
67use std::collections::HashMap;
68
69use cove_diag::{FileId, Span};
70use cove_syntax::ast::ExprId;
71
72use crate::typeck::Ty;
73
74/// The declaration a call resolved to, named the way the package names it.
75///
76/// A method call is written against a value, and which declaration it
77/// reaches is decided by that value's type — which only the checker knows.
78/// A pass reading the source alone can do no better than match the method's
79/// name, and a name is not unique: two types may declare one, and a declared
80/// type and a builtin may share one. Recording the answer is what turns that
81/// guess into a lookup.
82///
83/// The three parts name a declaration exactly. `module` is the module whose
84/// `impl` block writes it, spelled out even when the call is in that same
85/// module, so a target read anywhere in the package means the same thing.
86/// `type_name` is the type's bare name within that module, and `method` is
87/// the name as declared.
88#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
89pub struct MethodTarget {
90 /// The module that declares the type the method is written on.
91 pub module: String,
92 /// The type's name inside `module`, without the module qualifier.
93 pub type_name: String,
94 /// The method's own name.
95 pub method: String,
96}
97
98/// A declaration's boundary, as the checker resolved it for that body.
99///
100/// Every type here is the one the checker held while it walked *this*
101/// declaration's body, not a re-derivation from the source: an annotation is
102/// resolved once, against the module it was written in, and this is that
103/// answer rather than a second reading of it.
104///
105/// The three parts are kept apart the way a declaration keeps them. `params`
106/// is in declaration order and holds one entry per written parameter;
107/// `receiver` is the type of `self` and is `Some` only for a method, so a
108/// consumer that has to place arguments knows the receiver comes first
109/// without inferring it from a count. `ret` is `Ty::Unit` for a declaration
110/// with no `->`, because a function with no declared return type returns
111/// `()` and that is a settled type rather than an absence.
112///
113/// # Three kinds of declaration have one
114///
115/// A function or a method, written with a `fn`; a struct, whose initializer
116/// `Point(x: 0.0)` is a call the checker synthesizes a signature for out of
117/// the fields, so `params` is the field types in declaration order; and one
118/// case of an enum, whose `params` is its payload types. The last two are
119/// what publishes a declared type's *shape* — the only other thing that knows
120/// it is the lowering, which turns it into slot numbers and keeps no names.
121///
122/// A struct's and a case's types are the declaration's own, so a generic
123/// declaration records the `Ty::Param` it was written with; a consumer
124/// holding a *use* completes them with `Ty::instantiate`.
125#[derive(Clone, Debug, PartialEq)]
126pub struct Signature {
127 /// The type of `self`, for a method, and nothing for a free function.
128 pub receiver: Option<Ty>,
129 /// The declared parameters, in declaration order, receiver excluded.
130 ///
131 /// # Which question this answers
132 ///
133 /// **What a call supplies**, not what the callee's binding holds. The
134 /// two are the same type for every parameter shape but one, and the
135 /// exception is worth stating because a consumer that reads this field
136 /// as the second question gets a wrong answer silently.
137 ///
138 /// A variadic parameter is recorded as its **element** type, because a
139 /// call site passes elements: `fn count(items: Int...)` records `Int`
140 /// here. Inside the body `items` is the `Array<Int>` the callee made of
141 /// them, and nothing in this struct says so. A consumer that needs the
142 /// binding's type reads `variadic` off the declaration's own `Param` and
143 /// wraps — which is what `cove_ir::lower` does too, after first asking
144 /// this field and being told `Int`.
145 ///
146 /// A `var` parameter needs no such note: it names the caller's storage,
147 /// and the type of that storage is the type recorded here. What a `var`
148 /// changes is where the binding lives, not what it holds, and this
149 /// struct records no marking at all.
150 pub params: Vec<Ty>,
151 /// What a call to this declaration answers.
152 pub ret: Ty,
153}
154
155/// What the checker worked out about each expression.
156///
157/// One of these covers a whole package: every file the checker walked,
158/// within each file every expression it settled something about, and the
159/// boundary of every declaration it resolved. It is published on
160/// [`Program`](crate::resolve::Program), which is what a consumer of a
161/// checked package already holds.
162#[derive(Debug, Default)]
163pub struct Facts {
164 /// Indexed by [`FileId`]. A file the checker never walked is an empty
165 /// entry rather than a missing one, because the index has to stay the
166 /// id.
167 files: Vec<FileFacts>,
168 /// One entry per declared function, method, struct, and enum case, keyed
169 /// by the file it was written in and the start offset of its
170 /// declaration.
171 ///
172 /// A hash where the expression tables are a `Vec`, and the difference is
173 /// a difference in size rather than a change of mind. A declaration's
174 /// span has no dense numbering to index by — it is a byte offset into a
175 /// file, and the offsets of a file's declarations are sparse across its
176 /// whole length — so a dense table would be one slot per byte. What
177 /// makes a hash affordable anyway is that there is one entry per
178 /// declaration rather than one per expression, and it is read twice per
179 /// function at lowering time, once for the function's own boundary and
180 /// once at each call site that names it. Nothing on a hot path reads it
181 /// at all.
182 signatures: HashMap<(FileId, u32), Signature>,
183}
184
185/// Everything recorded about one file, indexed by [`ExprId`].
186///
187/// The two tables are separate rather than one table of pairs because they
188/// are populated at different densities: a type is recorded for every
189/// expression, and a target for the few that are calls to a declared
190/// method. Kept together, the sparse half would cost a slot per expression.
191#[derive(Debug, Default)]
192struct FileFacts {
193 types: Vec<Option<Ty>>,
194 targets: Vec<Option<MethodTarget>>,
195}
196
197impl Facts {
198 /// The type of the expression, if the checker settled one.
199 ///
200 /// `None` means this expression was never walked — a body the checker
201 /// stopped before reaching, or a tree that was never part of a checked
202 /// package. It never means the checker was unsure: see the module docs.
203 pub fn ty(&self, file: FileId, id: ExprId) -> Option<&Ty> {
204 self.files
205 .get(file.0 as usize)?
206 .types
207 .get(id.0 as usize)?
208 .as_ref()
209 }
210
211 /// The declaration this call resolved to, if it resolved to one.
212 ///
213 /// Only a call the checker matched against a declaration written in an
214 /// `impl` block answers here. A call to a builtin method, to a host
215 /// operation, or through a trait bound answers `None`, because none of
216 /// those names a declaration of this package.
217 pub fn target(&self, file: FileId, id: ExprId) -> Option<&MethodTarget> {
218 self.files
219 .get(file.0 as usize)?
220 .targets
221 .get(id.0 as usize)?
222 .as_ref()
223 }
224
225 /// The boundary of the declaration written at `decl`, if the checker
226 /// resolved one.
227 ///
228 /// `decl` is the `FnDecl`'s own span, which is what a consumer holding a
229 /// declaration already has and what makes the key need no side channel:
230 /// the checker records against the same span, so a declaration found in
231 /// the tree and the fact recorded about it meet without either naming
232 /// the other.
233 ///
234 /// `decl` is a `fn` declaration's span, a struct declaration's, or one
235 /// enum case's — see [`Signature`] for what each records.
236 ///
237 /// `None` means the checker never resolved this declaration — a body it
238 /// stopped before reaching, or a tree that was never part of a checked
239 /// package. As everywhere else here, it does not mean the checker was
240 /// unsure: a parameter it could say nothing about is recorded as
241 /// [`Ty::Unknown`], which is an answer.
242 pub fn signature(&self, file: FileId, decl: Span) -> Option<&Signature> {
243 self.signatures.get(&(file, decl.start))
244 }
245
246 /// Every expression the checker settled a type for, in file and then
247 /// expression order.
248 ///
249 /// The tables are indexed rather than keyed, so a reader that wants
250 /// *every* answer rather than one has no key to ask with. This is that
251 /// reader's way in, and it exists for one: the invariant that a package
252 /// which checked without error carries no unresolved type. Asking that
253 /// question of one expression at a time would need the question to know
254 /// which expressions there are, which is exactly what only this table
255 /// knows.
256 pub fn types(&self) -> impl Iterator<Item = (FileId, ExprId, &Ty)> {
257 self.files.iter().enumerate().flat_map(|(file, held)| {
258 held.types.iter().enumerate().filter_map(move |(id, ty)| {
259 Some((FileId(file as u32), ExprId(id as u32), ty.as_ref()?))
260 })
261 })
262 }
263
264 /// Records the type the checker settled for one expression.
265 ///
266 /// A later record for the same id replaces an earlier one. That is what
267 /// makes a probe — a walk whose diagnostics are discarded and which
268 /// always precedes the real walk of the same tree — leave the real
269 /// answer behind rather than its own.
270 pub(crate) fn record_ty(&mut self, file: FileId, id: ExprId, ty: &Ty) {
271 let Some(index) = index_of(id) else {
272 return;
273 };
274 *slot(&mut self.file_mut(file).types, index) = Some(ty.clone());
275 }
276
277 /// Records the declaration a call resolved to.
278 pub(crate) fn record_target(&mut self, file: FileId, id: ExprId, target: MethodTarget) {
279 let Some(index) = index_of(id) else {
280 return;
281 };
282 *slot(&mut self.file_mut(file).targets, index) = Some(target);
283 }
284
285 /// Records the boundary the checker resolved for one declaration.
286 ///
287 /// A later record for the same declaration replaces an earlier one, for
288 /// the reason [`Facts::record_ty`] gives: a probe walks a tree before
289 /// the real walk of it does, and what is left behind has to be the real
290 /// walk's answer.
291 pub(crate) fn record_signature(&mut self, file: FileId, decl: Span, signature: Signature) {
292 self.signatures.insert((file, decl.start), signature);
293 }
294
295 /// Takes over everything `other` recorded.
296 ///
297 /// A module is checked by a checker of its own, and a file belongs to
298 /// exactly one module, so in practice the two tables touch different
299 /// files. Merging slot by slot rather than file by file is what keeps
300 /// that an observation about the caller instead of an assumption here.
301 pub(crate) fn merge(&mut self, other: Facts) {
302 for (index, from) in other.files.into_iter().enumerate() {
303 let into = self.file_mut(FileId(index as u32));
304 merge_table(&mut into.types, from.types);
305 merge_table(&mut into.targets, from.targets);
306 }
307 self.signatures.extend(other.signatures);
308 }
309
310 /// The entry for `file`, growing the table until the id is an index into
311 /// it.
312 fn file_mut(&mut self, file: FileId) -> &mut FileFacts {
313 let index = file.0 as usize;
314 if self.files.len() <= index {
315 self.files.resize_with(index + 1, FileFacts::default);
316 }
317 &mut self.files[index]
318 }
319}
320
321/// The index `id` names, or `None` for an id that names no position.
322///
323/// [`ExprId::UNSET`] is `u32::MAX` and marks a tree that was built by hand
324/// rather than parsed. Treating it as an index would grow a table to four
325/// billion entries, so it records nothing and reads back as nothing.
326fn index_of(id: ExprId) -> Option<usize> {
327 (id != ExprId::UNSET).then_some(id.0 as usize)
328}
329
330/// The slot at `index`, growing `table` until there is one.
331fn slot<T>(table: &mut Vec<Option<T>>, index: usize) -> &mut Option<T> {
332 if table.len() <= index {
333 table.resize_with(index + 1, || None);
334 }
335 &mut table[index]
336}
337
338/// Writes every recorded entry of `from` over `into`, leaving the rest.
339fn merge_table<T>(into: &mut Vec<Option<T>>, from: Vec<Option<T>>) {
340 if into.len() < from.len() {
341 into.resize_with(from.len(), || None);
342 }
343 for (slot, value) in into.iter_mut().zip(from) {
344 if value.is_some() {
345 *slot = value;
346 }
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use std::collections::BTreeMap;
353 use std::path::PathBuf;
354
355 use cove_diag::SourceMap;
356 use cove_syntax::ast::{
357 Arg, Block, Expr, ExprKind, Item, ItemKind, MatchArm, Param, Pattern, PatternKind,
358 SourceUnit, StmtKind, StrPart,
359 };
360
361 use super::*;
362 use crate::compile::Compiler;
363 use crate::config::Config;
364 use crate::package::{Module, Package, Unit};
365 use crate::resolve::resolve;
366 use crate::typeck::{check_facts, Ty};
367
368 /// A package written inline, and everything a fact about it is read
369 /// against.
370 struct Checked {
371 sources: SourceMap,
372 package: Package,
373 facts: Facts,
374 }
375
376 impl Checked {
377 /// The id of the file `module` was written in.
378 fn file(&self, module: &str) -> FileId {
379 self.package.modules[module].units[0].file
380 }
381
382 /// The tree of `module`.
383 fn unit(&self, module: &str) -> &SourceUnit {
384 &self.package.modules[module].units[0].ast
385 }
386
387 /// The source `expr` was written as.
388 fn text(&self, module: &str, expr: &Expr) -> &str {
389 let file = self.sources.get(self.file(module));
390 &file.text[expr.span.start as usize..expr.span.end as usize]
391 }
392
393 /// The one expression of `module` written exactly as `source`.
394 ///
395 /// Naming an expression by its own text is what keeps a test about
396 /// the fact rather than about the numbering: nothing here has to
397 /// know which id the parser handed out.
398 #[track_caller]
399 fn id(&self, module: &str, source: &str) -> ExprId {
400 let mut found: Vec<ExprId> = Vec::new();
401 for expr in collect(self.unit(module)) {
402 if self.text(module, &expr) == source {
403 found.push(expr.id);
404 }
405 }
406 assert_eq!(
407 found.len(),
408 1,
409 "`{source}` names {} expressions of `{module}`, and a test names one",
410 found.len()
411 );
412 found[0]
413 }
414
415 /// The type recorded for the one expression written as `source`.
416 #[track_caller]
417 fn ty(&self, module: &str, source: &str) -> &Ty {
418 let id = self.id(module, source);
419 self.facts
420 .ty(self.file(module), id)
421 .unwrap_or_else(|| panic!("nothing was recorded for `{source}`"))
422 }
423
424 /// The target recorded for the one expression written as `source`.
425 #[track_caller]
426 fn target(&self, module: &str, source: &str) -> Option<&MethodTarget> {
427 let id = self.id(module, source);
428 self.facts.target(self.file(module), id)
429 }
430
431 /// The signature recorded for the declaration of `module` named
432 /// `name`, which is `Type.method` for a method.
433 ///
434 /// The declaration is found in the tree rather than in a table of
435 /// the checker's, so a test reads the fact through the same span a
436 /// consumer holding a `FnDecl` would read it through.
437 #[track_caller]
438 fn signature(&self, module: &str, name: &str) -> &Signature {
439 let mut found: Option<Span> = None;
440 for item in &self.unit(module).items {
441 match &item.kind {
442 ItemKind::Fn(decl) if decl.name.node == name => found = Some(decl.span),
443 ItemKind::Impl(block) => {
444 for item in &block.items {
445 if let ItemKind::Fn(decl) = &item.kind {
446 if format!("{}.{}", block.type_name.node, decl.name.node) == name {
447 found = Some(decl.span);
448 }
449 }
450 }
451 }
452 _ => {}
453 }
454 }
455 let span = found.unwrap_or_else(|| panic!("`{module}` declares no `{name}`"));
456 self.facts
457 .signature(self.file(module), span)
458 .unwrap_or_else(|| panic!("nothing was recorded for `{name}`"))
459 }
460
461 /// A signature as `receiver | params -> ret`, written out so a test
462 /// reads as the declaration does.
463 #[track_caller]
464 fn written(&self, module: &str, name: &str) -> String {
465 let signature = self.signature(module, name);
466 let params: Vec<String> = signature.params.iter().map(Ty::to_string).collect();
467 let receiver = match &signature.receiver {
468 Some(ty) => format!("{ty} | "),
469 None => String::new(),
470 };
471 format!("{receiver}({}) -> {}", params.join(", "), signature.ret)
472 }
473 }
474
475 /// Resolves and checks modules written inline, the way the pipeline
476 /// does, so the facts under test are the ones a consumer receives.
477 #[track_caller]
478 fn compile(modules: &[(&str, &str)]) -> Checked {
479 let (sources, package) = packaged(modules);
480 let program = Compiler::new()
481 .compile(&package)
482 .unwrap_or_else(|errors| panic!("test package checks: {}", first(&errors)));
483 Checked {
484 sources,
485 package,
486 facts: program.facts,
487 }
488 }
489
490 /// Checks modules written inline that are not expected to check, and
491 /// hands back what the checker recorded anyway.
492 ///
493 /// A package with an error never reaches [`Program::facts`], and the
494 /// facts are still the ones the check produced, because the recording
495 /// and the reporting are the same walk.
496 #[track_caller]
497 fn check_anyway(modules: &[(&str, &str)]) -> Checked {
498 let (sources, package) = packaged(modules);
499 let program = resolve(&package).expect("test package resolves");
500 let (_, facts) = check_facts(&package, &program, Compiler::new().host_schemas());
501 Checked {
502 sources,
503 package,
504 facts,
505 }
506 }
507
508 fn packaged(modules: &[(&str, &str)]) -> (SourceMap, Package) {
509 let mut sources = SourceMap::new();
510 let mut map = BTreeMap::new();
511 for (name, source) in modules {
512 let path = PathBuf::from(format!("{name}.cove"));
513 let file = sources.add(path.clone(), *source);
514 let ast = cove_syntax::parse_file(&sources, file).expect("test source parses");
515 map.insert(
516 (*name).to_string(),
517 Module {
518 name: (*name).to_string(),
519 dir: PathBuf::from(*name),
520 units: vec![Unit { file, path, ast }],
521 },
522 );
523 }
524 for (name, module) in crate::stdlib::attach(&mut sources).expect("stdlib parses") {
525 map.insert(name, module);
526 }
527 let package = Package {
528 root: PathBuf::new(),
529 config: Config::default(),
530 modules: map,
531 };
532 (sources, package)
533 }
534
535 fn first(errors: &[cove_diag::Diagnostic]) -> String {
536 errors
537 .iter()
538 .map(|d| format!("{}: {}", d.code, d.message))
539 .collect::<Vec<_>>()
540 .join("; ")
541 }
542
543 // ------------------------------------------------------------- source
544
545 /// A type and the methods written on it, in a module of its own so that
546 /// a target read from another module has a module to name.
547 const GEOMETRY: &str = r#"/// A point on the plane.
548export struct Point {
549 x: Float
550 y: Float
551}
552
553impl Point {
554 /// Scales both coordinates.
555 export fn scaled(self, by: Float) -> Point {
556 Point(x: self.x * by, y: self.y * by)
557 }
558
559 /// How many coordinates a point has, which is always two.
560 ///
561 /// It is named after a builtin method on purpose: `Array` has one too,
562 /// and which of the two a call reaches is decided by the receiver's type
563 /// and by nothing else.
564 export fn length(self) -> Int {
565 2
566 }
567
568 /// The point both coordinates are measured from.
569 export fn origin() -> Point {
570 Point(x: 0.0, y: 0.0)
571 }
572}
573"#;
574
575 /// A function written the way one is written: a parameter with a
576 /// default, an interpolation, an array, a struct, a method call, a
577 /// `match`, a block used for its value, and a loop that assigns.
578 const REPORT: &str = r#"use geometry.Point
579
580/// Summarises a batch of readings.
581export fn report(readings: Array<Int>, offset: Int = 7) -> String {
582 let total = 1.5 + 2.5
583 let large = offset > 3
584 let label = "offset {offset}"
585 let scores = [1, 2, 3]
586 let start = Point.origin()
587 let moved = start.scaled(by: total)
588 let height = moved.y
589 let chosen = match offset {
590 0 => "none"
591 other => label
592 }
593 let counted = {
594 readings.length()
595 }
596 let both = moved.length() + counted
597 var sum = 0
598 for score in scores {
599 sum = sum + score
600 }
601 "{label} {large} {height} {chosen} {both} {sum}"
602}
603"#;
604
605 fn reporting() -> Checked {
606 compile(&[("geometry", GEOMETRY), ("main", REPORT)])
607 }
608
609 // -------------------------------------------------------------- tests
610
611 /// The table is total over a function, which is the property a consumer
612 /// specialising on it depends on: one missing entry is one construct it
613 /// silently stops specialising.
614 #[test]
615 fn every_expression_of_a_function_has_a_recorded_type() {
616 let checked = reporting();
617 let mut missing: Vec<String> = Vec::new();
618 for module in ["geometry", "main"] {
619 let file = checked.file(module);
620 for expr in collect(checked.unit(module)) {
621 if checked.facts.ty(file, expr.id).is_none() {
622 missing.push(format!("{module}: `{}`", checked.text(module, &expr)));
623 }
624 }
625 }
626 // Everything the checker types is here, and what is left is exactly
627 // the names calls resolve — the two `Point` initializers, the
628 // associated function and the type it is reached through, and the
629 // three method callees. The list is written out rather than
630 // summarised so that a form losing its type fails this, and so that
631 // the exception widening is a deliberate edit rather than a silent
632 // one. See the module docs for why a name has no type to record.
633 assert_eq!(
634 missing,
635 vec![
636 "geometry: `Point`",
637 "geometry: `Point`",
638 "main: `Point.origin`",
639 "main: `Point`",
640 "main: `start.scaled`",
641 "main: `readings.length`",
642 "main: `moved.length`",
643 ]
644 );
645 }
646
647 /// The two callee positions are told apart by whether a type is
648 /// recorded, which is what makes the exception above readable rather
649 /// than merely tolerable.
650 #[test]
651 fn a_callee_records_a_type_when_the_call_goes_through_a_value() {
652 let source = r#"/// Doc.
653fn scale(n: Int) -> Int {
654 n * 3
655}
656
657/// Doc.
658fn raise(n: Int) -> Int {
659 n + 1
660}
661
662/// Doc.
663export fn apply(n: Int) -> Int {
664 let f: fn(Int) -> Int = scale
665 f(n) + raise(n)
666}
667"#;
668 let checked = compile(&[("main", source)]);
669 let file = checked.file("main");
670
671 // `scale` is given to a place, so it is evaluated and typed.
672 assert!(matches!(checked.ty("main", "scale"), Ty::Fn(_)));
673 // `f` names a binding, so calling it evaluates it.
674 assert!(matches!(checked.ty("main", "f"), Ty::Fn(_)));
675 // `raise` names a declaration, so calling it resolves a name.
676 assert_eq!(checked.facts.ty(file, checked.id("main", "raise")), None);
677 }
678
679 #[test]
680 fn an_int_literal_records_int() {
681 assert_eq!(reporting().ty("main", "7"), &Ty::Int);
682 }
683
684 #[test]
685 fn a_float_addition_records_float() {
686 assert_eq!(reporting().ty("main", "1.5 + 2.5"), &Ty::Float);
687 }
688
689 #[test]
690 fn a_comparison_records_bool() {
691 assert_eq!(reporting().ty("main", "offset > 3"), &Ty::Bool);
692 }
693
694 #[test]
695 fn a_string_interpolation_records_str() {
696 assert_eq!(reporting().ty("main", "\"offset {offset}\""), &Ty::Str);
697 }
698
699 #[test]
700 fn an_array_literal_records_its_element_type() {
701 assert_eq!(
702 reporting().ty("main", "[1, 2, 3]"),
703 &Ty::Array(Box::new(Ty::Int))
704 );
705 }
706
707 #[test]
708 fn a_struct_field_read_records_the_field_s_type() {
709 assert_eq!(reporting().ty("main", "moved.y"), &Ty::Float);
710 }
711
712 /// An empty collection literal records the element type the place that
713 /// holds it states, because a backend needing a static layout reads the
714 /// recorded type and nothing else: `Vector<_>` has no layout, and the
715 /// annotation, the return type or the field that says what it holds is
716 /// already there to be read.
717 #[test]
718 fn an_empty_collection_literal_records_the_type_its_place_states() {
719 let source = r#"/// Doc.
720export struct Basket {
721 items: Vector<Int>
722}
723
724/// Doc.
725export fn taking(names: Set<String>) -> Int {
726 names.length()
727}
728
729/// Doc.
730export fn returned() -> Map<String, Int> {
731 Map.of()
732}
733
734/// Doc.
735export fn annotated() -> Int {
736 let items: Vector<Int> = Vector.of()
737 items.length()
738}
739
740/// Doc.
741export fn passed() -> Int {
742 taking(Set.of())
743}
744
745/// Doc.
746export fn built() -> Basket {
747 Basket(items: Vector.of())
748}
749"#;
750 let checked = compile(&[("main", source)]);
751 assert_eq!(
752 checked.ty("main", "Map.of()"),
753 &Ty::Map(Box::new(Ty::Str), Box::new(Ty::Int))
754 );
755 assert_eq!(checked.ty("main", "Set.of()"), &Ty::Set(Box::new(Ty::Str)));
756 // Both `Vector.of()` are written the same way, so they are named by
757 // the declaration they sit in rather than by their text.
758 for expr in collect(checked.unit("main")) {
759 if checked.text("main", &expr) == "Vector.of()" {
760 assert_eq!(
761 checked.facts.ty(checked.file("main"), expr.id),
762 Some(&Ty::Vector(Box::new(Ty::Int)))
763 );
764 }
765 }
766 }
767
768 /// And an empty collection whose place says nothing records what the
769 /// *uses* of the binding holding it settled, which is the other half of
770 /// the same promise: a backend reading a fact after the check finished
771 /// reads a type it can lay out, never the variable the checker carried
772 /// while it was still deciding.
773 #[test]
774 fn an_empty_collection_literal_records_the_type_its_uses_settle() {
775 let source = r#"/// Doc.
776export fn built(text: String) -> Array<String> {
777 var log = Vector.of()
778 log.push(text)
779 log.freeze()
780}
781"#;
782 let checked = compile(&[("main", source)]);
783 assert_eq!(
784 checked.ty("main", "Vector.of()"),
785 &Ty::Vector(Box::new(Ty::Str))
786 );
787 assert_eq!(
788 checked.ty("main", "log.freeze()"),
789 &Ty::Array(Box::new(Ty::Str))
790 );
791 }
792
793 #[test]
794 fn a_call_records_what_it_produces() {
795 let checked = reporting();
796 assert_eq!(
797 checked.ty("main", "start.scaled(by: total)"),
798 &Ty::Struct("geometry.Point".into(), Vec::new())
799 );
800 }
801
802 #[test]
803 fn a_match_records_the_type_its_arms_agree_on() {
804 let source = "match offset {\n 0 => \"none\"\n other => label\n }";
805 assert_eq!(reporting().ty("main", source), &Ty::Str);
806 }
807
808 #[test]
809 fn a_block_records_the_type_of_its_tail() {
810 let checked = reporting();
811 assert_eq!(checked.ty("main", "readings.length()"), &Ty::Int);
812 assert_eq!(
813 checked.ty("main", "{\n readings.length()\n }"),
814 &Ty::Int
815 );
816 }
817
818 /// An unknown is what the checker settled, not the absence of an answer.
819 /// A consumer specialises on a recorded type and leaves an unrecorded id
820 /// alone, so the two have to be told apart.
821 #[test]
822 fn an_abstention_records_an_unknown_and_a_gap_records_nothing() {
823 let source = r#"/// Doc.
824export fn broken() -> Int {
825 missing()
826}
827"#;
828 let checked = check_anyway(&[("main", source)]);
829 let file = checked.file("main");
830 assert!(
831 matches!(checked.ty("main", "missing()"), Ty::Unknown(_)),
832 "the checker abstains about a call to a name it cannot find"
833 );
834
835 // An id past the end of the file names no expression of it, and a
836 // file the check never saw has no entries at all. Both read as
837 // "never recorded" rather than as an unknown.
838 let past_end = collect(checked.unit("main")).len() as u32;
839 assert_eq!(checked.facts.ty(file, ExprId(past_end)), None);
840 assert_eq!(checked.facts.ty(file, ExprId::UNSET), None);
841 // Past every file the source map holds, including the standard
842 // library `compile` attaches, so this is a file the check truly
843 // never saw rather than one that merely sorts after `main`.
844 let past_every_file = checked.sources.files().count() as u32;
845 assert_eq!(checked.facts.ty(FileId(past_every_file), ExprId(0)), None);
846 }
847
848 /// Every file numbers from zero, so an id alone names nothing. Reading
849 /// one against the wrong file has to answer that file's expression, not
850 /// this one's.
851 #[test]
852 fn ids_do_not_collide_across_files() {
853 let checked = compile(&[
854 ("counter", "/// Doc.\nexport fn count() -> Int {\n 1\n}\n"),
855 (
856 "greeter",
857 "/// Doc.\nexport fn greet() -> String {\n \"hi\"\n}\n",
858 ),
859 ]);
860 assert_eq!(checked.id("counter", "1"), ExprId(0));
861 assert_eq!(checked.id("greeter", "\"hi\""), ExprId(0));
862 assert_eq!(checked.ty("counter", "1"), &Ty::Int);
863 assert_eq!(checked.ty("greeter", "\"hi\""), &Ty::Str);
864 }
865
866 /// The receiver's type decides which declaration a call reaches, and it
867 /// is the one thing a pass reading the source alone does not have.
868 /// `Point` and `Array` both declare `length`.
869 #[test]
870 fn a_declared_method_records_its_declaration() {
871 let checked = reporting();
872 assert_eq!(
873 checked.target("main", "moved.length()"),
874 Some(&MethodTarget {
875 module: "geometry".to_string(),
876 type_name: "Point".to_string(),
877 method: "length".to_string(),
878 })
879 );
880 assert_eq!(
881 checked.target("main", "start.scaled(by: total)"),
882 Some(&MethodTarget {
883 module: "geometry".to_string(),
884 type_name: "Point".to_string(),
885 method: "scaled".to_string(),
886 })
887 );
888 }
889
890 /// A builtin method belongs to no `impl` block, so there is no
891 /// declaration to name and the fact is that there is none.
892 #[test]
893 fn a_builtin_method_records_no_target() {
894 assert_eq!(reporting().target("main", "readings.length()"), None);
895 }
896
897 /// An associated function is named through its type rather than a
898 /// receiver, and it is a declaration like any other.
899 #[test]
900 fn an_associated_function_records_its_declaration() {
901 assert_eq!(
902 reporting().target("main", "Point.origin()"),
903 Some(&MethodTarget {
904 module: "geometry".to_string(),
905 type_name: "Point".to_string(),
906 method: "origin".to_string(),
907 })
908 );
909 }
910
911 /// A method of the module being checked names that module, so a target
912 /// means the same thing wherever it is read.
913 #[test]
914 fn a_target_names_the_declaring_module_even_from_inside_it() {
915 let checked = reporting();
916 assert_eq!(
917 checked.target("geometry", "Point(x: self.x * by, y: self.y * by)"),
918 None,
919 "a struct initializer names a type, not a method"
920 );
921 let inside = compile(&[(
922 "main",
923 "/// Doc.\nexport struct Tally {\n n: Int\n}\n\nimpl Tally {\n /// Doc.\n fn bumped(self) -> Tally {\n Tally(n: self.n + 1)\n }\n\n /// Doc.\n export fn twice(self) -> Tally {\n self.bumped().bumped()\n }\n}\n",
924 )]);
925 assert_eq!(
926 inside.target("main", "self.bumped()"),
927 Some(&MethodTarget {
928 module: "main".to_string(),
929 type_name: "Tally".to_string(),
930 method: "bumped".to_string(),
931 })
932 );
933 }
934
935 /// A free function's boundary is recorded as the checker resolved it,
936 /// which is what a consumer placing arguments reads instead of reading
937 /// the annotations again.
938 #[test]
939 fn a_declarations_signature_is_recorded() {
940 assert_eq!(
941 reporting().written("main", "report"),
942 "(Array<Int>, Int) -> String"
943 );
944 }
945
946 /// A method's receiver is recorded apart from its parameters, because a
947 /// call supplies it first and a consumer must not have to infer that
948 /// from a count.
949 #[test]
950 fn a_methods_signature_records_its_receiver_apart_from_its_parameters() {
951 let checked = reporting();
952 assert_eq!(
953 checked.written("geometry", "Point.scaled"),
954 "Point | (Float) -> Point"
955 );
956 assert_eq!(
957 checked.written("geometry", "Point.length"),
958 "Point | () -> Int"
959 );
960 assert_eq!(
961 checked.written("geometry", "Point.origin"),
962 "() -> Point",
963 "an associated function has no receiver"
964 );
965 }
966
967 /// A declaration with no `->` returns `()`, and `()` is a type. Recording
968 /// it as one is what keeps "the checker said nothing" and "the checker
969 /// said `Unit`" apart here as everywhere else.
970 #[test]
971 fn a_declaration_with_no_return_type_records_unit() {
972 let checked = compile(&[(
973 "main",
974 "/// Doc.\nexport fn note(what: String) {\n let _ignored = what\n}\n",
975 )]);
976 assert_eq!(checked.written("main", "note"), "(String) -> ()");
977 }
978
979 // ------------------------------------------------- an independent walk
980
981 /// Collects every expression of a unit by a walk written here rather
982 /// than reused from the checker, so that an expression both of them
983 /// forget cannot pass for one neither has.
984 fn collect(unit: &SourceUnit) -> Vec<Expr> {
985 let mut found = Vec::new();
986 for item in &unit.items {
987 item_exprs(item, &mut found);
988 }
989 found
990 }
991
992 fn item_exprs(item: &Item, found: &mut Vec<Expr>) {
993 match &item.kind {
994 ItemKind::Fn(decl) => {
995 param_exprs(&decl.params, found);
996 block_exprs(&decl.body, found);
997 }
998 ItemKind::Struct(_) | ItemKind::Enum(_) | ItemKind::TypeAlias(_) => {}
999 ItemKind::Trait(decl) => {
1000 for method in &decl.methods {
1001 param_exprs(&method.params, found);
1002 if let Some(body) = &method.default {
1003 block_exprs(body, found);
1004 }
1005 }
1006 }
1007 ItemKind::Impl(block) => {
1008 for item in &block.items {
1009 item_exprs(item, found);
1010 }
1011 }
1012 }
1013 }
1014
1015 fn param_exprs(params: &[Param], found: &mut Vec<Expr>) {
1016 for param in params {
1017 if let Some(default) = ¶m.default {
1018 expr_exprs(default, found);
1019 }
1020 }
1021 }
1022
1023 fn block_exprs(block: &Block, found: &mut Vec<Expr>) {
1024 for stmt in &block.statements {
1025 match &stmt.kind {
1026 StmtKind::Let { value, .. } => expr_exprs(value, found),
1027 StmtKind::Expr(value) => expr_exprs(value, found),
1028 StmtKind::Item(item) => item_exprs(item, found),
1029 }
1030 }
1031 if let Some(tail) = &block.tail {
1032 expr_exprs(tail, found);
1033 }
1034 }
1035
1036 fn arm_exprs(arm: &MatchArm, found: &mut Vec<Expr>) {
1037 pattern_exprs(&arm.pattern, found);
1038 expr_exprs(&arm.body, found);
1039 }
1040
1041 fn pattern_exprs(pattern: &Pattern, found: &mut Vec<Expr>) {
1042 match &pattern.kind {
1043 PatternKind::Wildcard | PatternKind::Binding(_) => {}
1044 PatternKind::Literal(value) => expr_exprs(value, found),
1045 PatternKind::Variant { payload, .. } => {
1046 for pattern in payload {
1047 pattern_exprs(pattern, found);
1048 }
1049 }
1050 }
1051 }
1052
1053 fn arg_exprs(args: &[Arg], found: &mut Vec<Expr>) {
1054 for arg in args {
1055 expr_exprs(&arg.value, found);
1056 }
1057 }
1058
1059 fn expr_exprs(expr: &Expr, found: &mut Vec<Expr>) {
1060 found.push(expr.clone());
1061 match &expr.kind {
1062 ExprKind::Int(_)
1063 | ExprKind::Float(_)
1064 | ExprKind::Bool(_)
1065 | ExprKind::Duration(_)
1066 | ExprKind::Unit
1067 | ExprKind::Ident(_)
1068 | ExprKind::Continue => {}
1069 ExprKind::Str(parts) => {
1070 for part in parts {
1071 if let StrPart::Interpolation(inner) = part {
1072 expr_exprs(inner, found);
1073 }
1074 }
1075 }
1076 ExprKind::ArrayLit(items) => {
1077 for item in items {
1078 expr_exprs(item, found);
1079 }
1080 }
1081 ExprKind::Field { base, .. } => expr_exprs(base, found),
1082 ExprKind::Call {
1083 callee,
1084 args,
1085 trailing,
1086 ..
1087 } => {
1088 expr_exprs(callee, found);
1089 arg_exprs(args, found);
1090 if let Some(trailing) = trailing {
1091 expr_exprs(trailing, found);
1092 }
1093 }
1094 ExprKind::Unary { operand, .. } => expr_exprs(operand, found),
1095 ExprKind::Binary { lhs, rhs, .. } => {
1096 expr_exprs(lhs, found);
1097 expr_exprs(rhs, found);
1098 }
1099 ExprKind::Assign { target, value, .. } => {
1100 expr_exprs(target, found);
1101 expr_exprs(value, found);
1102 }
1103 ExprKind::Try(inner) | ExprKind::Await(inner) => expr_exprs(inner, found),
1104 ExprKind::Block(block) => block_exprs(block, found),
1105 ExprKind::If {
1106 condition,
1107 then_branch,
1108 else_branch,
1109 } => {
1110 expr_exprs(condition, found);
1111 block_exprs(then_branch, found);
1112 if let Some(else_branch) = else_branch {
1113 expr_exprs(else_branch, found);
1114 }
1115 }
1116 ExprKind::Match { scrutinee, arms } => {
1117 expr_exprs(scrutinee, found);
1118 for arm in arms {
1119 arm_exprs(arm, found);
1120 }
1121 }
1122 ExprKind::For { iterable, body, .. } => {
1123 expr_exprs(iterable, found);
1124 block_exprs(body, found);
1125 }
1126 ExprKind::While { condition, body } => {
1127 expr_exprs(condition, found);
1128 block_exprs(body, found);
1129 }
1130 ExprKind::Return(value) | ExprKind::Break(value) => {
1131 if let Some(value) = value {
1132 expr_exprs(value, found);
1133 }
1134 }
1135 ExprKind::Lambda { params, body, .. } => {
1136 param_exprs(params, found);
1137 block_exprs(body, found);
1138 }
1139 ExprKind::Scope { body, .. } => block_exprs(body, found),
1140 ExprKind::Range { start, end, .. } => {
1141 expr_exprs(start, found);
1142 expr_exprs(end, found);
1143 }
1144 }
1145 }
1146}