Skip to main content

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) = &param.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}