Skip to main content

logicaffeine_kernel/dag/sem/
infer.rs

1//! Type inference and checking over values.
2//!
3//! A term is walked with an environment (the values of its bound variables) and a context (their
4//! types), and its type is produced as a value. An application's type is its function type's
5//! closure instantiated with the argument's value; crossing a binder binds a fresh variable; a
6//! lambda's type is a closure whose codomain is inferred only if something asks for it. With
7//! `check`, every rule of the substitution checker is enforced — universe parameters, safety,
8//! argument types, `let` values, the restrictions on projecting out of a proposition.
9
10use super::value::{CtxId, EnvId, Val, DEAD, V};
11use super::{reject, Sem};
12use crate::dag::env::Safety;
13use crate::dag::error::{KResult, KernelFailure};
14use crate::dag::expr::{ExprId, Node};
15use crate::dag::level::{LevelId, LevelsId};
16use crate::dag::name::NameId;
17
18impl Sem<'_> {
19    /// The type of `e`, whose bound variables have the values `env` and the types `ctx` (an
20    /// empty context means "the types of the values").
21    pub fn infer(&mut self, env: EnvId, ctx: CtxId, e: ExprId, check: bool) -> KResult<V> {
22        self.note_expr(e);
23        let env = self.env_for(env, e);
24        let key = (env.0, e.0);
25        let lparam_free = !self.k.s.has_lparam(e);
26        let safe = self.k.safety == Safety::Safe;
27        let scoped = (self.st.scope, env.0, e.0);
28        let cached = if !check {
29            self.st.infer_cache.get(&key)
30        } else {
31            let lasting = if lparam_free {
32                self.st.safe_checked.get(&key)
33            } else if safe {
34                self.st.scoped_checked.get(&scoped)
35            } else {
36                None
37            };
38            lasting.or_else(|| self.st.check_cache.get(&key))
39        };
40        if let Some(t) = cached {
41            return Ok(V(t));
42        }
43        self.enter()?;
44        let r = self.infer_uncached(env, ctx, e, check);
45        self.leave();
46        let t = r?;
47        if check {
48            if safe && lparam_free {
49                self.st.safe_checked.insert(key, t.0);
50            } else if safe {
51                self.st.scoped_checked.insert(scoped, t.0);
52            } else {
53                self.st.check_cache.insert(key, t.0);
54            }
55        }
56        if !self.st.infer_cache.contains(&key) {
57            self.st.infer_cache.insert(key, t.0);
58        }
59        Ok(t)
60    }
61
62    fn check_level(&self, l: LevelId) -> KResult<()> {
63        if let Some(n) = self.k.s.levels.undef_param(l, &self.k.lparams) {
64            return Err(reject(format!(
65                "invalid reference to undefined universe level parameter '{}'",
66                self.k.s.names.display(n)
67            )));
68        }
69        Ok(())
70    }
71
72    fn infer_constant(&mut self, n: NameId, ls: LevelsId, check: bool) -> KResult<V> {
73        let Some(c) = self.k.env.get(n).cloned() else {
74            return Err(reject(format!(
75                "unknown constant '{}'",
76                self.k.s.names.display(n)
77            )));
78        };
79        let us = self.k.s.levels.get_list(ls).to_vec();
80        if c.lparams.len() != us.len() {
81            return Err(reject(format!(
82                "incorrect number of universe levels for '{}': {} expected, {} provided",
83                self.k.s.names.display(n),
84                c.lparams.len(),
85                us.len()
86            )));
87        }
88        if check {
89            if c.is_unsafe() && self.k.safety != Safety::Unsafe {
90                return Err(reject(format!(
91                    "invalid declaration, it uses unsafe declaration '{}'",
92                    self.k.s.names.display(n)
93                )));
94            }
95            if c.is_partial() && self.k.safety == Safety::Safe {
96                return Err(reject(format!(
97                    "invalid declaration, safe declaration must not contain partial declaration '{}'",
98                    self.k.s.names.display(n)
99                )));
100            }
101            for l in &us {
102                self.check_level(*l)?;
103            }
104        }
105        self.const_type(n, ls)
106    }
107
108    /// Weak-head normal form, required to be a sort.
109    fn ensure_sort(&mut self, t: V, what: &str) -> KResult<LevelId> {
110        match self.sort_of(t)? {
111            Some(l) => Ok(l),
112            None => Err(reject(format!(
113                "type expected, {what} has type {}",
114                self.show(t)
115            ))),
116        }
117    }
118
119    fn is_eager_arg(&self, a: ExprId) -> bool {
120        match self.k.s.const_head(a) {
121            Some((n, _)) => n == self.k.wk.eager_reduce && self.k.s.app_num_args(a) == 2,
122            None => false,
123        }
124    }
125
126    fn infer_uncached(&mut self, env: EnvId, ctx: CtxId, e: ExprId, check: bool) -> KResult<V> {
127        match self.k.s.node(e) {
128            Node::BVar(i) => match self.st.arena.ctx_lookup(ctx, i) {
129                Some(t) => Ok(t),
130                None => {
131                    let v = self.st.arena.lookup(env, i).ok_or_else(|| reject("unexpected bound variable"))?;
132                    self.type_of(v)
133                }
134            },
135            Node::FVar(_) => Err(reject("the value engine met a free variable")),
136            Node::Sort(l) => {
137                if check {
138                    self.check_level(l)?;
139                }
140                let l = self.level_in(env, l);
141                let sl = self.k.s.levels.succ(l);
142                Ok(self.st.arena.sort(sl))
143            }
144            Node::Const(n, ls) => {
145                let ls = self.levels_in(env, ls);
146                self.infer_constant(n, ls, check)
147            }
148            Node::App(..) => self.infer_app(env, ctx, e, check),
149            Node::Lam(dom, body) => {
150                if check {
151                    let dt = self.infer(env, ctx, dom, true)?;
152                    self.ensure_sort(dt, "a binder type")?;
153                }
154                let d = self.eval(env, dom)?;
155                if check {
156                    let ctx2 = self.st.arena.ctx_extend(ctx, d);
157                    let bt = self.under(d, |sem, x| {
158                        let env2 = sem.st.arena.extend(env, x);
159                        sem.infer(env2, ctx2, body, true)
160                    })?;
161                    // A body type that mentions no local is the codomain at every argument: the
162                    // λ's type is the plain `Π (_ : d). bt`, which an application instantiates
163                    // without inferring the body again.
164                    if !self.st.arena.is_open(bt) {
165                        return Ok(self.constant_pi(d, bt));
166                    }
167                }
168                Ok(self.st.arena.pi_lam(d, env, ctx, body))
169            }
170            Node::Pi(dom, body) => {
171                let dt = self.infer(env, ctx, dom, check)?;
172                let l1 = self.ensure_sort(dt, "a binder type")?;
173                let d = self.eval(env, dom)?;
174                let ctx2 = self.st.arena.ctx_extend(ctx, d);
175                let l2 = self.under(d, |sem, x| {
176                    let env2 = sem.st.arena.extend(env, x);
177                    let bt = sem.infer(env2, ctx2, body, check)?;
178                    sem.ensure_sort(bt, "a Π codomain")
179                })?;
180                let l = self.k.s.levels.imax(l1, l2);
181                Ok(self.st.arena.sort(l))
182            }
183            Node::Let(ty, val, body) => {
184                if check {
185                    let tt = self.infer(env, ctx, ty, true)?;
186                    self.ensure_sort(tt, "a let type")?;
187                }
188                let tv = self.eval(env, ty)?;
189                if check {
190                    let vt = self.infer(env, ctx, val, true)?;
191                    if !self.is_def_eq(vt, tv)? {
192                        return Err(reject("let value type mismatch").with_detail(format!(
193                            "in `{}`\n  expected type: {}\n  but the value has type: {}",
194                            self.k.show(e),
195                            self.show(tv),
196                            self.show(vt)
197                        )));
198                    }
199                }
200                let v = self.eval(env, val)?;
201                let env2 = self.st.arena.extend(env, v);
202                let ctx2 = self.st.arena.ctx_extend(ctx, tv);
203                self.infer(env2, ctx2, body, check)
204            }
205            Node::NatLit(_) => {
206                if check && !self.k.env.contains(self.k.wk.nat) {
207                    return Err(reject("natural number literal used before 'Nat' is declared"));
208                }
209                self.const0(self.k.wk.nat)
210            }
211            Node::StrLit(_) => {
212                if check && !(self.k.env.contains(self.k.wk.char_of_nat) && self.k.env.contains(self.k.wk.string_of_list))
213                {
214                    return Err(reject("string literal used before 'String.ofList' and 'Char.ofNat' are declared"));
215                }
216                self.const0(self.k.wk.string)
217            }
218            Node::Proj(s, idx, x) => {
219                let xt = self.infer(env, ctx, x, check)?;
220                let xv = self.eval(env, x)?;
221                self.proj_type(s, idx, xv, xt, check)
222            }
223            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Lit(_) | Node::Seq(..) | Node::Nil => {
224                Err(reject("the value engine checks Lean's theory; LOGOS's is checked by the substitution checker"))
225            }
226        }
227    }
228
229    /// `Π (_ : dom). cod` for a codomain that mentions no local: a closure whose body reads `cod`
230    /// from its one-entry environment (`BVar 1` under the binder).
231    fn constant_pi(&mut self, dom: V, cod: V) -> V {
232        let (body, _) = self.k.s.with_persistent(|s| s.bvar(1));
233        let env = self.st.arena.extend(EnvId::EMPTY, cod);
234        self.st.arena.pi(dom, env, body)
235    }
236
237    fn infer_app(&mut self, env: EnvId, ctx: CtxId, e: ExprId, check: bool) -> KResult<V> {
238        let (f, args) = self.k.s.app_args(e);
239        let mut fty = self.infer(env, ctx, f, check)?;
240        for &a in args.iter() {
241            let w = self.whnf(fty)?;
242            let dom = match self.st.arena.get(w) {
243                Val::Pi { dom, .. } | Val::PiLam { dom, .. } => dom,
244                _ => {
245                    return Err(reject(format!("function expected, found {}", self.show(w))));
246                }
247            };
248            if check {
249                let at = self.infer(env, ctx, a, true)?;
250                let ok = if self.is_eager_arg(a) && !self.st.eager {
251                    self.st.eager = true;
252                    let r = self.is_def_eq(dom, at);
253                    self.st.eager = false;
254                    r?
255                } else {
256                    self.is_def_eq(dom, at)?
257                };
258                if !ok {
259                    return Err(
260                        KernelFailure::reject("application type mismatch").with_detail(format!(
261                            "in `{}`\n  expected type: {}\n  but the argument has type: {}",
262                            self.k.show(e),
263                            self.show(dom),
264                            self.show(at)
265                        )),
266                    );
267                }
268            }
269            let av = if self.codomain_ignores_binder(w) {
270                DEAD
271            } else {
272                self.eval(env, a)?
273            };
274            fty = self.instantiate(w, av)?;
275        }
276        Ok(fty)
277    }
278
279    /// Whether a Π-value's codomain provably never mentions its bound variable: then it can be
280    /// instantiated without evaluating the argument (the placeholder is never looked up).
281    #[inline]
282    pub(crate) fn codomain_ignores_binder(&self, pi: V) -> bool {
283        match self.st.arena.get(pi) {
284            Val::Pi { body, .. } | Val::PiLam { body, .. } => {
285                let (n, mask) = self.k.s.loose_bvars(body);
286                n == 0 || mask.is_some_and(|m| m & 1 == 0)
287            }
288            _ => false,
289        }
290    }
291}