Skip to main content

oxilean_kernel/infer/
cache.rs

1//! The types a [`TypeChecker`] has inferred, remembered by term.
2
3use super::types::TypeChecker;
4use crate::error::KernelError;
5use crate::Expr;
6
7impl TypeChecker<'_> {
8    /// Infer the type of an expression.
9    ///
10    /// # The cache
11    ///
12    /// A type that was inferred is kept, by term, and returned the next time
13    /// the same term is asked about (Lean's `m_infer_type`). A proof term is
14    /// a DAG — the terms `omega` and `simp` build reach one subterm along
15    /// thousands of paths — and without the table every path inferred the
16    /// subterm again: millions of inferences for a few thousand distinct
17    /// subterms.
18    ///
19    /// The type of a term is a function of the term, the environment and
20    /// the types of the free variables in it, so an entry stays valid while
21    /// those do:
22    ///
23    /// * the environment is fixed for the checker's life;
24    /// * a free variable keeps its type while it is in scope, and its id is
25    ///   not handed out again after its binder closes — so an entry that
26    ///   mentions a closed variable is simply never asked for again. Should
27    ///   a caller of [`Self::push_local`] bind an id a second time (to
28    ///   another type, or after its binder closed), the table is dropped
29    ///   (where the checker mirrors a local into its reducer);
30    /// * in checking mode an entry also records that the term CHECKED, which
31    ///   depends on the transparency mode: [`Self::set_transparency`] drops
32    ///   the table. The mode itself (checking / infer-only) is fixed per
33    ///   checker.
34    ///
35    /// Only successes are kept. A failure is recomputed: it may be the
36    /// per-declaration budget running out, which is not a property of the
37    /// term. A success reached while the budget was running out is exact
38    /// (see [`crate::fuel`]) and safe to keep.
39    #[allow(clippy::result_large_err)]
40    pub fn infer_type(&mut self, expr: &Expr) -> Result<Expr, KernelError> {
41        // C16b: inference is the third loop (besides `whnf` and `is_def_eq`)
42        // that must observe the per-declaration fuel latch. A `let`-tower or
43        // dependent application chain grows its instantiated bodies
44        // geometrically *between* whnf/def-eq steps, so without this check a
45        // single `infer_type` call tree can allocate unboundedly while the
46        // budget is long exhausted (Init decl #3864,
47        // `Array.extract_append_extract._proof_1_1`, reached >12 GiB). The
48        // error is a typed abort, mapped by callers to the NAMED resource
49        // limit — never a rejection, and never a truncated term.
50        if crate::fuel::is_exhausted() {
51            return Err(KernelError::Other(
52                "per-declaration resource budget exhausted".to_string(),
53            ));
54        }
55        // A `Sort`, a literal and a loose bound variable are read straight
56        // off the term: nothing to look up, nothing to keep.
57        let remembered = !matches!(expr, Expr::Sort(_) | Expr::BVar(_) | Expr::Lit(_));
58        if remembered {
59            if let Some(ty) = self.infer_cache.get(expr) {
60                return Ok(ty.clone());
61            }
62        }
63        let ty = self.infer_type_uncached(expr)?;
64        if remembered {
65            self.infer_cache.insert(expr, ty.clone());
66        }
67        Ok(ty)
68    }
69}