Skip to main content

logicaffeine_kernel/dag/
tc.rs

1//! Type inference, weak-head normalisation and definitional equality over the shared DAG.
2//!
3//! The algorithm is Lean 4's kernel algorithm — the one `lean4lean` transcribes from the C++ —
4//! so that every verdict agrees with Lean's: lazy δ-reduction ordered by definitional height,
5//! proof irrelevance, η for functions and structures, unit-like types, K-like and structure
6//! recursor reduction, quotient reduction, and `Nat`/`String` literals. What differs is the
7//! representation underneath: structural equality is an id compare, and every cache is keyed on
8//! ids, so a subterm shared a thousand times is inferred, reduced and compared once.
9
10use super::env::{Const, Env, Hint, Kind, QuotKind, RecInfo, Safety};
11use super::error::{KResult, KernelFailure};
12use super::expr::{ExprId, Node, Store};
13use super::level::{LevelId, LevelNode, LevelsId};
14use super::name::NameId;
15use super::nat::Nat;
16use super::wellknown::WellKnown;
17use rustc_hash::{FxHashMap as FxMap, FxHashSet as FxSet};
18use std::rc::Rc;
19
20#[derive(Clone, Copy)]
21pub struct LocalDecl {
22    pub ty: ExprId,
23    pub val: Option<ExprId>,
24}
25
26/// Pairs of expressions proven definitionally equal, closed under congruence but NOT under
27/// transitivity.
28///
29/// Lean's kernel keeps a union-find here, which closes the relation transitively. But its
30/// definitional equality is not transitive: by proof irrelevance `a ≡ b` and `b ≡ c` can hold for
31/// proofs whose types make `a ≢ c`. The Arena's `proj-of-stuck-prop`, `rec-missing-ih` and
32/// `rec-of-subst-prop` turn exactly that closure into a proof of `False`. The official kernel is
33/// exposed only when two hashes collide; a union-find consulted unconditionally would always be.
34/// So this cache answers only what was actually proven, plus congruence (two nodes of the same
35/// shape whose children are pairwise equal or proven equal), which is the algorithm's own rule.
36#[derive(Clone, Default)]
37struct ProvenEqual {
38    pairs: FxSet<(ExprId, ExprId)>,
39    /// How many pairs have been proven: what a remembered refutation is stamped with.
40    generation: u64,
41}
42
43impl ProvenEqual {
44    #[inline]
45    fn key(a: ExprId, b: ExprId) -> (ExprId, ExprId) {
46        if a <= b {
47            (a, b)
48        } else {
49            (b, a)
50        }
51    }
52
53    #[inline]
54    fn contains(&self, a: ExprId, b: ExprId) -> bool {
55        self.pairs.contains(&Self::key(a, b))
56    }
57
58    #[inline]
59    fn insert(&mut self, a: ExprId, b: ExprId) {
60        if a != b && self.pairs.insert(Self::key(a, b)) {
61            self.generation += 1;
62        }
63    }
64
65    fn clear(&mut self) {
66        if self.pairs.capacity() > 1 << 14 {
67            self.pairs = FxSet::default();
68        } else {
69            self.pairs.clear();
70        }
71    }
72}
73
74enum LBool {
75    True,
76    False,
77    Undef,
78}
79
80impl From<bool> for LBool {
81    fn from(b: bool) -> LBool {
82        if b {
83            LBool::True
84        } else {
85            LBool::False
86        }
87    }
88}
89
90enum Step {
91    Continue(ExprId, ExprId),
92    Unknown(ExprId, ExprId),
93    True,
94    False(ExprId, ExprId),
95}
96
97/// Recursion bound for the mutually recursive infer/whnf/defeq calls (checked on a thread whose
98/// stack is sized for it). Exceeding it declines instead of overflowing.
99const MAX_DEPTH: u32 = 250_000;
100
101/// A kernel is plain data (its only shared cells are immutable: interned strings and admitted
102/// constants), so a clone is an independent kernel; `mirror` keeps one as a snapshot to branch from.
103#[derive(Clone)]
104pub struct Kernel {
105    pub s: Store,
106    pub env: Env,
107    pub wk: WellKnown,
108    pub(crate) lctx: Vec<LocalDecl>,
109    pub(crate) lparams: Rc<[NameId]>,
110    pub(crate) safety: Safety,
111    pub(crate) eager: bool,
112    depth: u32,
113    infer_only_cache: FxMap<ExprId, ExprId>,
114    infer_check_cache: FxMap<ExprId, ExprId>,
115    whnf_core_cache: FxMap<ExprId, ExprId>,
116    whnf_cache: FxMap<ExprId, ExprId>,
117    unfold_cache: FxMap<ExprId, ExprId>,
118    failure: FxSet<(ExprId, ExprId)>,
119    eqv: ProvenEqual,
120    /// Pairs refuted in this declaration, keyed with the `eagerReduce` mode (the modes can decide
121    /// a pair differently) and stamped with `eqv`'s generation when their comparison began. Asked
122    /// again in that mode with nothing proven since, a refuted pair is refuted without re-running
123    /// the algorithm — which would retrace the same steps: the terms are closed (a free variable
124    /// names an entry of a local context that only grows within the declaration), their levels
125    /// are the declaration's own, the environment never changes what a constant already present
126    /// means, and the caches consulted are memos of those same steps. A proof reached since can
127    /// make the pair equal (by congruence, or deeper, through a sub-comparison), so any new proven
128    /// pair retires every stamp. A comparison that exceeds the depth bound declines and records
129    /// nothing. Without this, a comparison whose every level re-asks the refutations below it (a
130    /// recursion on a literal, stuck on variables that differ) costs the square of its depth.
131    refuted: FxMap<(ExprId, ExprId, bool), u64>,
132    /// Primitive operations whose definitions were proven to meet their equations, and so are
133    /// computed natively on literals.
134    pub(crate) validated_primitives: FxSet<NameId>,
135    /// A settled constant's type, value and recursor rules at given universe levels depend only
136    /// on persistent terms, so they are interned persistently and shared by every declaration.
137    global_type: FxMap<(NameId, LevelsId), ExprId>,
138    global_value: FxMap<(NameId, LevelsId), ExprId>,
139    global_rule: FxMap<(NameId, u32, LevelsId), ExprId>,
140    /// Which argument positions each settled definition cannot distinguish.
141    pub(crate) relevance: super::relevance::Cache,
142    /// The closure-based conversion engine's per-declaration state.
143    #[cfg(feature = "nbe")]
144    pub(crate) nbe: crate::dag::nbe::State,
145    /// Decide definitional equality with the NbE engine instead of the substitution engine.
146    #[cfg(feature = "nbe")]
147    pub use_nbe: bool,
148    /// Decide every definitional equality with BOTH engines; a disagreement declines.
149    #[cfg(feature = "nbe")]
150    pub dual_conv: bool,
151    /// Which judgment admits definitions, theorems, opaques and axioms.
152    pub engine: Engine,
153    /// The steps the value engine lets one argument pair of a same-head comparison take before it
154    /// unfolds the definitions instead (see `sem::conv`): conversion steps and δ-unfolding steps
155    /// both spend it. It changes what conversion costs, never what it answers.
156    pub probe_steps: u32,
157    /// Definitional-equality steps taken so far (entries into the core of the comparison): what
158    /// conversion has cost, independent of the machine.
159    conv_steps: u64,
160    /// δ-unfoldings weak-head reduction has taken so far: what reduction has cost by unfolding,
161    /// independent of the machine.
162    unfold_steps: u64,
163    /// The value engine's session, created on first use (it moves out of the kernel for the
164    /// length of every check, so it is boxed and taken rather than rebuilt).
165    pub(crate) sem: Option<Box<super::sem::State>>,
166    /// The type theory this kernel checks.
167    pub theory: Theory,
168    /// What checking LOGOS's theory needs beyond the store and the environment (see
169    /// [`super::logos_tc`]); present exactly under [`Theory::Logos`].
170    pub(crate) logos: Option<Box<super::logos_tc::LogosState>>,
171}
172
173/// The type theory a kernel checks.
174#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
175pub enum Theory {
176    /// Lean 4's, exactly: the Lean Kernel Arena's checker and the Lean import path.
177    #[default]
178    Lean,
179    /// LOGOS's Calculus of Inductive Constructions: `match`, `fix` and mutual `fix`, cumulative
180    /// universes, `SProp`, LOGOS's literals.
181    Logos,
182}
183
184/// The engine that checks declaration types and values (inductive families, quotients and the
185/// primitive validations always use the substitution checker).
186#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
187pub enum Engine {
188    /// Substitution: `tc`.
189    #[default]
190    Classic,
191    /// Evaluation into closures: `sem`.
192    Sem,
193    /// Both, and they must agree on every declaration.
194    Dual,
195}
196
197macro_rules! bail {
198    ($self:ident, $($arg:tt)*) => {
199        return Err(KernelFailure::reject(format!($($arg)*)))
200    };
201}
202
203impl Kernel {
204    pub fn new() -> Kernel {
205        let mut s = Store::new();
206        let wk = WellKnown::new(&mut s.names);
207        Kernel {
208            s,
209            env: Env::default(),
210            wk,
211            lctx: Vec::new(),
212            lparams: Rc::from(Vec::new()),
213            safety: Safety::Safe,
214            eager: false,
215            depth: 0,
216            infer_only_cache: FxMap::default(),
217            infer_check_cache: FxMap::default(),
218            whnf_core_cache: FxMap::default(),
219            whnf_cache: FxMap::default(),
220            unfold_cache: FxMap::default(),
221            failure: FxSet::default(),
222            eqv: ProvenEqual::default(),
223            refuted: FxMap::default(),
224            validated_primitives: FxSet::default(),
225            global_type: FxMap::default(),
226            global_value: FxMap::default(),
227            global_rule: FxMap::default(),
228            relevance: Default::default(),
229            #[cfg(feature = "nbe")]
230            nbe: Default::default(),
231            #[cfg(feature = "nbe")]
232            use_nbe: false,
233            #[cfg(feature = "nbe")]
234            dual_conv: false,
235            engine: Engine::Classic,
236            probe_steps: 2048,
237            conv_steps: 0,
238            unfold_steps: 0,
239            sem: None,
240            theory: Theory::Lean,
241            logos: None,
242        }
243    }
244
245    /// Starts checking one declaration: fresh local context, caches, and scratch region.
246    pub fn begin_decl(&mut self, lparams: Rc<[NameId]>, safety: Safety) {
247        self.lctx.clear();
248        self.lparams = lparams;
249        self.safety = safety;
250        self.eager = false;
251        self.depth = 0;
252        self.s.begin_scratch();
253    }
254
255    pub fn end_decl(&mut self) {
256        self.lctx.clear();
257        clear_map(&mut self.infer_only_cache);
258        clear_map(&mut self.infer_check_cache);
259        clear_map(&mut self.whnf_core_cache);
260        clear_map(&mut self.whnf_cache);
261        clear_map(&mut self.unfold_cache);
262        if self.failure.capacity() > 1 << 14 {
263            self.failure = FxSet::default();
264        } else {
265            self.failure.clear();
266        }
267        if self.refuted.capacity() > 1 << 14 {
268            self.refuted = FxMap::default();
269        } else {
270            self.refuted.clear();
271        }
272        self.eqv.clear();
273        #[cfg(feature = "nbe")]
274        self.nbe.reset();
275        if let Some(l) = self.logos.as_mut() {
276            l.end_decl();
277        }
278        self.s.end_scratch();
279    }
280
281    /// The store's memory, plus the kernel's own caches and environment.
282    pub fn memory_report(&self) -> Vec<(&'static str, usize)> {
283        let mut r = self.s.memory_report();
284        r.push(("declarations (count)", self.env.count));
285        if let Some(st) = self.sem.as_ref() {
286            r.extend(st.memory_report());
287        }
288        let c = self
289            .sem
290            .as_ref()
291            .map(|s| s.counters.clone())
292            .unwrap_or_default();
293        for (what, n) in [
294            ("value engine: sessions", c.sessions),
295            ("value engine: resets (epoch)", c.resets_epoch),
296            ("value engine: resets (scratch)", c.resets_scratch),
297            ("value engine: resets (budget)", c.resets_budget),
298            (
299                "value engine: scratch from unsettled constants",
300                c.scratch_unsettled,
301            ),
302            (
303                "value engine: scratch from tainted instances",
304                c.scratch_instance,
305            ),
306            ("value engine: scratch from scratch terms", c.scratch_expr),
307            ("value engine: proof status inferred", c.proof_probes),
308        ] {
309            r.push((what, n as usize));
310        }
311        r.push((
312            "global type/value/rule cache entries",
313            self.global_type.len() + self.global_value.len() + self.global_rule.len(),
314        ));
315        r
316    }
317
318    pub fn set_safety(&mut self, safety: Safety) {
319        self.safety = safety;
320    }
321
322    // ---- local context ----
323
324    pub fn push_local(&mut self, ty: ExprId, val: Option<ExprId>) -> ExprId {
325        let i = self.lctx.len() as u32;
326        self.lctx.push(LocalDecl { ty, val });
327        self.s.fvar(i)
328    }
329
330    pub fn fvar_index(&self, e: ExprId) -> u32 {
331        match self.s.node(e) {
332            Node::FVar(i) => i,
333            _ => unreachable!("not a free variable"),
334        }
335    }
336
337    /// `mkForall`/`mkLambda` over local declarations (lets become `let`).
338    pub fn mk_binding(&mut self, pi: bool, fvars: &[ExprId], body: ExprId) -> ExprId {
339        let idx: Vec<u32> = fvars.iter().map(|f| self.fvar_index(*f)).collect();
340        let mut r = self.s.abstract_fvars(body, &idx);
341        for i in (0..idx.len()).rev() {
342            let d = self.lctx[idx[i] as usize];
343            let ty = self.s.abstract_fvars(d.ty, &idx[..i]);
344            r = match d.val {
345                Some(v) => {
346                    let v = self.s.abstract_fvars(v, &idx[..i]);
347                    self.s.let_(ty, v, r)
348                }
349                None => {
350                    if pi {
351                        self.s.pi(ty, r)
352                    } else {
353                        self.s.lam(ty, r)
354                    }
355                }
356            };
357        }
358        r
359    }
360
361    pub fn mk_pi(&mut self, fvars: &[ExprId], body: ExprId) -> ExprId {
362        self.mk_binding(true, fvars, body)
363    }
364
365    pub fn mk_lambda(&mut self, fvars: &[ExprId], body: ExprId) -> ExprId {
366        self.mk_binding(false, fvars, body)
367    }
368
369    fn enter(&mut self) -> KResult<()> {
370        self.depth += 1;
371        if self.depth > MAX_DEPTH {
372            return Err(KernelFailure::resource("recursion depth limit reached"));
373        }
374        Ok(())
375    }
376
377    fn leave(&mut self) {
378        self.depth -= 1;
379    }
380
381    // ---- constants ----
382
383    pub fn get_const(&self, n: NameId) -> KResult<Rc<Const>> {
384        self.env.get(n).cloned().ok_or_else(|| {
385            KernelFailure::reject(format!("unknown constant '{}'", self.s.names.display(n)))
386        })
387    }
388
389    /// `c`'s type at universe arguments `ls`.
390    pub fn instantiate_type_lparams(&mut self, c: &Const, ls: LevelsId) -> ExprId {
391        if c.lparams.is_empty() {
392            return c.ty;
393        }
394        let settled = self.env.is_settled(c.name);
395        if settled {
396            if let Some(r) = self.global_type.get(&(c.name, ls)) {
397                return *r;
398            }
399        }
400        let us = self.s.levels.get_list(ls).to_vec();
401        if settled {
402            let (r, persistent) = self
403                .s
404                .with_persistent(|s| s.instantiate_lparams(c.ty, &c.lparams, &us));
405            if persistent {
406                self.global_type.insert((c.name, ls), r);
407            }
408            r
409        } else {
410            self.s.instantiate_lparams(c.ty, &c.lparams, &us)
411        }
412    }
413
414    fn check_level(&self, l: LevelId) -> KResult<()> {
415        if let Some(n) = self.s.levels.undef_param(l, &self.lparams) {
416            bail!(
417                self,
418                "invalid reference to undefined universe level parameter '{}'",
419                self.s.names.display(n)
420            );
421        }
422        Ok(())
423    }
424
425    fn infer_constant(&mut self, n: NameId, ls: LevelsId, infer_only: bool) -> KResult<ExprId> {
426        let c = self.get_const(n)?;
427        let us = self.s.levels.get_list(ls).to_vec();
428        if c.lparams.len() != us.len() {
429            bail!(
430                self,
431                "incorrect number of universe levels for '{}': {} expected, {} provided",
432                self.s.names.display(n),
433                c.lparams.len(),
434                us.len()
435            );
436        }
437        if !infer_only {
438            if c.is_unsafe() && self.safety != Safety::Unsafe {
439                bail!(
440                    self,
441                    "invalid declaration, it uses unsafe declaration '{}'",
442                    self.s.names.display(n)
443                );
444            }
445            if c.is_partial() && self.safety == Safety::Safe {
446                bail!(
447                    self,
448                    "invalid declaration, safe declaration must not contain partial declaration '{}'",
449                    self.s.names.display(n)
450                );
451            }
452            for l in &us {
453                self.check_level(*l)?;
454                if self.s.levels.mentions_sprop(*l) {
455                    bail!(
456                        self,
457                        "'{}' is instantiated at SProp, which is a sort and not a universe level",
458                        self.s.names.display(n)
459                    );
460                }
461            }
462        }
463        Ok(self.instantiate_type_lparams(&c, ls))
464    }
465
466    // ---- inference ----
467
468    pub fn infer(&mut self, e: ExprId, infer_only: bool) -> KResult<ExprId> {
469        if self.s.has_loose_bvars(e) {
470            bail!(self, "type checker does not support loose bound variables");
471        }
472        let cached = if infer_only {
473            self.infer_only_cache.get(&e)
474        } else {
475            self.infer_check_cache.get(&e)
476        };
477        if let Some(r) = cached {
478            return Ok(*r);
479        }
480        self.enter()?;
481        let r = self.infer_uncached(e, infer_only);
482        self.leave();
483        let r = r?;
484        if infer_only {
485            self.infer_only_cache.insert(e, r);
486        } else {
487            self.infer_check_cache.insert(e, r);
488        }
489        Ok(r)
490    }
491
492    fn infer_uncached(&mut self, e: ExprId, infer_only: bool) -> KResult<ExprId> {
493        match self.s.node(e) {
494            Node::BVar(_) => bail!(self, "unexpected bound variable"),
495            Node::FVar(i) => match self.lctx.get(i as usize) {
496                Some(d) => Ok(d.ty),
497                None => bail!(self, "unknown free variable"),
498            },
499            Node::Sort(l) => {
500                if !infer_only {
501                    self.check_level(l)?;
502                }
503                let sl = self.s.levels.succ(l);
504                Ok(self.s.sort(sl))
505            }
506            Node::Const(n, ls) => self.infer_constant(n, ls, infer_only),
507            Node::Lam(..) => self.infer_lambda(e, infer_only),
508            Node::Pi(..) => self.infer_pi(e, infer_only),
509            Node::Let(..) => self.infer_let(e, infer_only),
510            Node::App(f, a) => {
511                if infer_only {
512                    self.infer_app(e)
513                } else {
514                    let ft = self.infer(f, false)?;
515                    let ft = self.ensure_pi(ft)?;
516                    if self.theory == Theory::Logos {
517                        let Node::Pi(dom, body) = self.s.node(ft) else {
518                            unreachable!()
519                        };
520                        if self.check_fix_against(a, dom)? {
521                            return Ok(self.s.instantiate1(body, a));
522                        }
523                    }
524                    let at = self.infer(a, false)?;
525                    let Node::Pi(dom, body) = self.s.node(ft) else {
526                        unreachable!()
527                    };
528                    let eager_arg = self.is_app_of_arity(a, self.wk.eager_reduce, 2);
529                    let ok = if eager_arg && !self.eager {
530                        self.eager = true;
531                        let r = self.accepts_argument(dom, at);
532                        self.eager = false;
533                        r?
534                    } else {
535                        self.accepts_argument(dom, at)?
536                    };
537                    if !ok {
538                        return Err(
539                            KernelFailure::reject("application type mismatch".to_string())
540                                .with_detail(self.describe_mismatch(e, dom, at)),
541                        );
542                    }
543                    Ok(self.s.instantiate1(body, a))
544                }
545            }
546            Node::NatLit(_) | Node::StrLit(_) if self.theory == Theory::Logos => {
547                self.infer_logos(e, infer_only)
548            }
549            Node::NatLit(_) => {
550                if !infer_only && !self.env.contains(self.wk.nat) {
551                    bail!(self, "natural number literal used before 'Nat' is declared");
552                }
553                Ok(self.s.cnst(self.wk.nat, LevelsId::EMPTY))
554            }
555            Node::StrLit(_) => {
556                if !infer_only
557                    && !(self.env.contains(self.wk.char_of_nat)
558                        && self.env.contains(self.wk.string_of_list))
559                {
560                    bail!(
561                        self,
562                        "string literal used before 'String.ofList' and 'Char.ofNat' are declared"
563                    );
564                }
565                Ok(self.s.cnst(self.wk.string, LevelsId::EMPTY))
566            }
567            Node::Proj(sn, idx, x) => {
568                let xt = self.infer(x, infer_only)?;
569                self.infer_proj(e, sn, idx, x, xt)
570            }
571            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Lit(_) => {
572                self.infer_logos(e, infer_only)
573            }
574            Node::Seq(..) | Node::Nil => bail!(self, "a sequence is not a term"),
575        }
576    }
577
578    fn infer_lambda(&mut self, mut e: ExprId, infer_only: bool) -> KResult<ExprId> {
579        let mut fvars = Vec::new();
580        while let Node::Lam(t, b) = self.s.node(e) {
581            let d = self.s.instantiate_rev(t, &fvars);
582            if !infer_only {
583                let dt = self.infer(d, false)?;
584                self.ensure_sort(dt)?;
585            }
586            fvars.push(self.push_local(d, None));
587            e = b;
588        }
589        let body = self.s.instantiate_rev(e, &fvars);
590        let r = self.infer(body, infer_only)?;
591        let r = self.cheap_beta_reduce(r);
592        Ok(self.mk_pi(&fvars, r))
593    }
594
595    fn infer_pi(&mut self, mut e: ExprId, infer_only: bool) -> KResult<ExprId> {
596        let mut fvars = Vec::new();
597        let mut us = Vec::new();
598        while let Node::Pi(t, b) = self.s.node(e) {
599            let d = self.s.instantiate_rev(t, &fvars);
600            let dt = self.infer(d, infer_only)?;
601            let s = self.ensure_sort(dt)?;
602            us.push(self.sort_level(s));
603            fvars.push(self.push_local(d, None));
604            e = b;
605        }
606        let body = self.s.instantiate_rev(e, &fvars);
607        let r = self.infer(body, infer_only)?;
608        let s = self.ensure_sort(r)?;
609        let mut l = self.sort_level(s);
610        for u in us.into_iter().rev() {
611            l = self.s.levels.imax(u, l);
612        }
613        Ok(self.s.sort(l))
614    }
615
616    fn infer_let(&mut self, mut e: ExprId, infer_only: bool) -> KResult<ExprId> {
617        let mut fvars = Vec::new();
618        while let Node::Let(t, v, b) = self.s.node(e) {
619            let ty = self.s.instantiate_rev(t, &fvars);
620            let val = self.s.instantiate_rev(v, &fvars);
621            if !infer_only {
622                let tt = self.infer(ty, false)?;
623                self.ensure_sort(tt)?;
624                if !(self.theory == Theory::Logos && self.check_fix_against(val, ty)?) {
625                    let vt = self.infer(val, false)?;
626                    if !self.conforms(vt, ty)? {
627                        return Err(KernelFailure::reject("let value type mismatch".to_string())
628                            .with_detail(self.describe_mismatch(val, ty, vt)));
629                    }
630                }
631            }
632            fvars.push(self.push_local(ty, Some(val)));
633            e = b;
634        }
635        let body = self.s.instantiate_rev(e, &fvars);
636        let r = self.infer(body, infer_only)?;
637        let r = self.cheap_beta_reduce(r);
638        Ok(self.mk_pi(&fvars, r))
639    }
640
641    fn infer_app(&mut self, e: ExprId) -> KResult<ExprId> {
642        let (f, args) = self.s.app_args(e);
643        let mut ft = self.infer(f, true)?;
644        let mut j = 0;
645        for i in 0..args.len() {
646            match self.s.node(ft) {
647                Node::Pi(_, body) => ft = body,
648                _ => {
649                    let t = self.s.instantiate_rev(ft, &args[j..i]);
650                    let t = self.ensure_pi(t)?;
651                    let Node::Pi(_, body) = self.s.node(t) else {
652                        unreachable!()
653                    };
654                    ft = body;
655                    j = i;
656                }
657            }
658        }
659        Ok(self.s.instantiate_rev(ft, &args[j..]))
660    }
661
662    fn infer_proj(
663        &mut self,
664        e: ExprId,
665        sname: NameId,
666        idx: u32,
667        x: ExprId,
668        xt: ExprId,
669    ) -> KResult<ExprId> {
670        let ty = self.whnf(xt)?;
671        let (head, args) = self.s.app_args(ty);
672        let fail = |k: &Kernel| KernelFailure::reject(format!("invalid projection {}", k.show(e)));
673        let Node::Const(iname, ils) = self.s.node(head) else {
674            return Err(fail(self));
675        };
676        if iname != sname {
677            return Err(fail(self));
678        }
679        let Some(ind) = self.env.inductive(iname).cloned() else {
680            return Err(fail(self));
681        };
682        if ind.ctors.len() != 1 || args.len() != (ind.num_params + ind.num_indices) as usize {
683            return Err(fail(self));
684        }
685        let ctor = self.get_const(ind.ctors[0])?;
686        let mut r = self.instantiate_type_lparams(&ctor, ils);
687        for arg in args.iter().take(ind.num_params as usize) {
688            let w = self.whnf(r)?;
689            let Node::Pi(_, b) = self.s.node(w) else {
690                return Err(fail(self));
691            };
692            r = self.s.instantiate1(b, *arg);
693        }
694        let sl = self.get_sort_level(ty)?;
695        let maybe_prop = !self.s.levels.is_never_zero(sl);
696        for i in 0..idx {
697            let w = self.whnf(r)?;
698            let Node::Pi(dom, b) = self.s.node(w) else {
699                return Err(fail(self));
700            };
701            if self.s.has_loose_bvars(b) {
702                if maybe_prop && !self.is_prop(dom)? {
703                    return Err(fail(self));
704                }
705                let p = self.s.proj(iname, i, x);
706                r = self.s.instantiate1(b, p);
707            } else {
708                r = b;
709            }
710        }
711        let w = self.whnf(r)?;
712        let Node::Pi(dom, _) = self.s.node(w) else {
713            return Err(fail(self));
714        };
715        if maybe_prop && !self.is_prop(dom)? {
716            return Err(fail(self));
717        }
718        Ok(dom)
719    }
720
721    /// Lean's `cheapBetaReduce`.
722    fn cheap_beta_reduce(&mut self, e: ExprId) -> ExprId {
723        let Node::App(..) = self.s.node(e) else {
724            return e;
725        };
726        let (f, args) = self.s.app_args(e);
727        let Node::Lam(..) = self.s.node(f) else {
728            return e;
729        };
730        let mut i = 0;
731        let mut body = f;
732        while i < args.len() {
733            match self.s.node(body) {
734                Node::Lam(_, b) => {
735                    body = b;
736                    i += 1;
737                }
738                _ => break,
739            }
740        }
741        if !self.s.has_loose_bvars(body) {
742            self.s.mk_app_n(body, &args[i..])
743        } else if let Node::BVar(n) = self.s.node(body) {
744            let n = n as usize;
745            if n < i {
746                let head = args[i - n - 1];
747                self.s.mk_app_n(head, &args[i..])
748            } else {
749                e
750            }
751        } else {
752            e
753        }
754    }
755
756    pub fn sort_level(&self, s: ExprId) -> LevelId {
757        match self.s.node(s) {
758            Node::Sort(l) => l,
759            _ => unreachable!("sort_level of a non-sort"),
760        }
761    }
762
763    pub fn ensure_sort(&mut self, e: ExprId) -> KResult<ExprId> {
764        if let Node::Sort(_) = self.s.node(e) {
765            return Ok(e);
766        }
767        let w = self.whnf(e)?;
768        if let Node::Sort(_) = self.s.node(w) {
769            return Ok(w);
770        }
771        Err(KernelFailure::reject(format!(
772            "type expected, found {}",
773            self.show(e)
774        )))
775    }
776
777    pub fn ensure_pi(&mut self, e: ExprId) -> KResult<ExprId> {
778        if let Node::Pi(..) = self.s.node(e) {
779            return Ok(e);
780        }
781        let w = self.whnf(e)?;
782        if let Node::Pi(..) = self.s.node(w) {
783            return Ok(w);
784        }
785        Err(KernelFailure::reject(format!(
786            "function expected, found {}",
787            self.show(e)
788        )))
789    }
790
791    pub fn get_sort_level(&mut self, e: ExprId) -> KResult<LevelId> {
792        let t = self.infer(e, true)?;
793        let s = self.ensure_sort(t)?;
794        Ok(self.sort_level(s))
795    }
796
797    /// `e`'s type is `Prop`.
798    pub fn is_prop(&mut self, e: ExprId) -> KResult<bool> {
799        let l = self.get_sort_level(e)?;
800        Ok(self.s.levels.is_always_zero(l))
801    }
802
803    /// Infers `e`'s type with full checking.
804    pub fn check(&mut self, e: ExprId) -> KResult<ExprId> {
805        self.infer(e, false)
806    }
807
808    // ---- reduction ----
809
810    fn is_let_fvar(&self, e: ExprId) -> Option<ExprId> {
811        match self.s.node(e) {
812            Node::FVar(i) => self.lctx.get(i as usize).and_then(|d| d.val),
813            _ => None,
814        }
815    }
816
817    pub fn whnf_core(&mut self, e: ExprId, cheap_proj: bool) -> KResult<ExprId> {
818        match self.s.node(e) {
819            Node::BVar(_)
820            | Node::Sort(_)
821            | Node::Pi(..)
822            | Node::Const(..)
823            | Node::Lam(..)
824            | Node::NatLit(_)
825            | Node::StrLit(_)
826            | Node::Lit(_)
827            | Node::Fix(_)
828            | Node::MutualFix(..) => return Ok(e),
829            Node::FVar(_) => {
830                if self.is_let_fvar(e).is_none() {
831                    return Ok(e);
832                }
833            }
834            _ => {}
835        }
836        if let Some(r) = self.whnf_core_cache.get(&e) {
837            return Ok(*r);
838        }
839        self.enter()?;
840        let r = self.whnf_core_uncached(e, cheap_proj);
841        self.leave();
842        r
843    }
844
845    fn whnf_core_uncached(&mut self, e: ExprId, cheap_proj: bool) -> KResult<ExprId> {
846        let save = |k: &mut Kernel, r: ExprId| {
847            if !cheap_proj {
848                k.whnf_core_cache.insert(e, r);
849            }
850            Ok(r)
851        };
852        match self.s.node(e) {
853            Node::FVar(_) => {
854                let v = self.is_let_fvar(e).unwrap();
855                self.whnf_core(v, cheap_proj)
856            }
857            Node::App(..) => {
858                let f0 = self.s.app_fn(e);
859                let f = self.whnf_core(f0, cheap_proj)?;
860                if f == f0 && !matches!(self.s.node(f), Node::Lam(..)) {
861                    if self.theory == Theory::Logos {
862                        return self.whnf_core_logos_app(e, cheap_proj);
863                    }
864                    return match self.reduce_recursor(e)? {
865                        Some(r) => self.whnf_core(r, cheap_proj),
866                        None => Ok(e),
867                    };
868                }
869                let (_, args) = self.s.app_args(e);
870                if let Node::Lam(..) = self.s.node(f) {
871                    let mut m = 0;
872                    let mut body = f;
873                    while m < args.len() {
874                        match self.s.node(body) {
875                            Node::Lam(_, b) => {
876                                body = b;
877                                m += 1;
878                            }
879                            _ => break,
880                        }
881                    }
882                    let r = self.s.instantiate_rev(body, &args[..m]);
883                    let r = self.s.mk_app_n(r, &args[m..]);
884                    let r = self.whnf_core(r, cheap_proj)?;
885                    save(self, r)
886                } else {
887                    let r = self.s.mk_app_n(f, &args);
888                    let r = self.whnf_core(r, cheap_proj)?;
889                    save(self, r)
890                }
891            }
892            Node::Let(_, v, b) => {
893                let r = self.s.instantiate1(b, v);
894                let r = self.whnf_core(r, cheap_proj)?;
895                save(self, r)
896            }
897            Node::Proj(_, idx, x) => match self.reduce_proj(idx, x, cheap_proj)? {
898                Some(m) => {
899                    let r = self.whnf_core(m, cheap_proj)?;
900                    save(self, r)
901                }
902                None => save(self, e),
903            },
904            Node::Match(..) => match self.reduce_match(e)? {
905                Some(m) => {
906                    let r = self.whnf_core(m, cheap_proj)?;
907                    save(self, r)
908                }
909                None => save(self, e),
910            },
911            _ => unreachable!(),
912        }
913    }
914
915    fn reduce_proj(&mut self, idx: u32, x: ExprId, cheap_proj: bool) -> KResult<Option<ExprId>> {
916        let c = if cheap_proj {
917            self.whnf_core(x, cheap_proj)?
918        } else {
919            self.whnf(x)?
920        };
921        self.reduce_proj_core(idx, c)
922    }
923
924    fn reduce_proj_core(&mut self, idx: u32, mut c: ExprId) -> KResult<Option<ExprId>> {
925        if let Node::StrLit(sid) = self.s.node(c) {
926            let expanded = self.str_lit_to_ctor(sid);
927            c = self.whnf(expanded)?;
928        }
929        let (head, args) = self.s.app_args(c);
930        let Node::Const(mk, _) = self.s.node(head) else {
931            return Ok(None);
932        };
933        let Some(ci) = self.env.ctor(mk) else {
934            return Ok(None);
935        };
936        Ok(args.get((ci.num_params + idx) as usize).copied())
937    }
938
939    // Inline at its one call site, `whnf_core`'s recursor step: `whnf_core` also carries LOGOS's
940    // arms, which put it past the inliner's own budget, and called out of line this step costs the
941    // Lean theory's substitution engine about 0.4% of its instructions.
942    #[inline(always)]
943    fn reduce_recursor(&mut self, e: ExprId) -> KResult<Option<ExprId>> {
944        if self.env.quot_init {
945            if let Some(r) = self.quot_reduce_rec(e)? {
946                return Ok(Some(r));
947            }
948        }
949        self.inductive_reduce_rec(e)
950    }
951
952    pub(crate) fn quot_reduce_rec(&mut self, e: ExprId) -> KResult<Option<ExprId>> {
953        let Some((fname, _)) = self.s.const_head(e) else {
954            return Ok(None);
955        };
956        let Some(&q) = self
957            .env
958            .quotients
959            .iter()
960            .find(|q| q.lift == fname || q.ind == fname)
961        else {
962            return Ok(None);
963        };
964        let (mk_pos, arg_pos) = if fname == q.lift { (5, 3) } else { (4, 3) };
965        let (_, args) = self.s.app_args(e);
966        if mk_pos >= args.len() {
967            return Ok(None);
968        }
969        let mk = self.whnf(args[mk_pos])?;
970        if !self.is_app_of_arity(mk, q.mk, 3) {
971            return Ok(None);
972        }
973        let Node::App(_, mk_arg) = self.s.node(mk) else {
974            unreachable!()
975        };
976        let mut r = self.s.app(args[arg_pos], mk_arg);
977        if mk_pos + 1 < args.len() {
978            r = self.s.mk_app_n(r, &args[mk_pos + 1..]);
979        }
980        Ok(Some(r))
981    }
982
983    pub fn is_app_of_arity(&self, e: ExprId, n: NameId, arity: usize) -> bool {
984        match self.s.const_head(e) {
985            Some((h, _)) => h == n && self.s.app_num_args(e) == arity,
986            None => false,
987        }
988    }
989
990    fn inductive_reduce_rec(&mut self, e: ExprId) -> KResult<Option<ExprId>> {
991        let Some((rec_name, ls)) = self.s.const_head(e) else {
992            return Ok(None);
993        };
994        let Some(c) = self.env.get(rec_name) else {
995            return Ok(None);
996        };
997        let Kind::Rec(_) = &c.kind else {
998            return Ok(None);
999        };
1000        let c = c.clone();
1001        let Kind::Rec(info) = &c.kind else {
1002            unreachable!()
1003        };
1004        let (_, rec_args) = self.s.app_args(e);
1005        let major_idx = info.major_idx();
1006        let Some(&major0) = rec_args.get(major_idx) else {
1007            return Ok(None);
1008        };
1009        let mut major = major0;
1010        if info.k {
1011            major = self.to_ctor_when_k(info, major)?;
1012        }
1013        major = match self.eta_major_first(info, major)? {
1014            Some(expanded) => expanded,
1015            None => {
1016                let wm = self.whnf(major)?;
1017                match self.s.node(wm) {
1018                    Node::NatLit(n) if self.reads_nat_literal(self.s.nat(n)) => {
1019                        let v = self.s.nat(n).clone();
1020                        self.nat_lit_to_ctor(&v)
1021                    }
1022                    Node::StrLit(sid) => {
1023                        let x = self.str_lit_to_ctor(sid);
1024                        self.whnf(x)?
1025                    }
1026                    _ => self.to_ctor_when_struct(info.major_induct, wm)?,
1027                }
1028            }
1029        };
1030        let (mhead, margs) = self.s.app_args(major);
1031        let Node::Const(ctor_name, _) = self.s.node(mhead) else {
1032            return Ok(None);
1033        };
1034        let Some(rule_idx) = info.rules.iter().position(|r| r.ctor == ctor_name) else {
1035            return Ok(None);
1036        };
1037        let rule = &info.rules[rule_idx];
1038        let nfields = rule.nfields as usize;
1039        if nfields > margs.len() {
1040            return Ok(None);
1041        }
1042        let us = self.s.levels.get_list(ls).to_vec();
1043        if us.len() != c.lparams.len() {
1044            return Ok(None);
1045        }
1046        let mut rhs = if c.lparams.is_empty() {
1047            rule.rhs
1048        } else if self.env.is_settled(rec_name) {
1049            match self.global_rule.get(&(rec_name, rule_idx as u32, ls)) {
1050                Some(r) => *r,
1051                None => {
1052                    let (r, persistent) = self
1053                        .s
1054                        .with_persistent(|s| s.instantiate_lparams(rule.rhs, &c.lparams, &us));
1055                    if persistent {
1056                        self.global_rule.insert((rec_name, rule_idx as u32, ls), r);
1057                    }
1058                    r
1059                }
1060            }
1061        } else {
1062            self.s.instantiate_lparams(rule.rhs, &c.lparams, &us)
1063        };
1064        rhs = self.s.mk_app_n(rhs, &rec_args[..info.first_index_idx()]);
1065        rhs = self.s.mk_app_n(rhs, &margs[margs.len() - nfields..]);
1066        if major_idx + 1 < rec_args.len() {
1067            rhs = self.s.mk_app_n(rhs, &rec_args[major_idx + 1..]);
1068        }
1069        Ok(Some(rhs))
1070    }
1071
1072    fn mk_nullary_ctor(&mut self, ty: ExprId, nparams: usize) -> Option<ExprId> {
1073        let (head, args) = self.s.app_args(ty);
1074        let Node::Const(d, ls) = self.s.node(head) else {
1075            return None;
1076        };
1077        let ind = self.env.inductive(d)?;
1078        let ctor = *ind.ctors.first()?;
1079        let c = self.s.cnst(ctor, ls);
1080        Some(self.s.mk_app_n(c, &args[..nparams.min(args.len())]))
1081    }
1082
1083    fn to_ctor_when_k(&mut self, info: &RecInfo, e: ExprId) -> KResult<ExprId> {
1084        let et = self.infer(e, true)?;
1085        let app_type = self.whnf(et)?;
1086        let Some((ti, _)) = self.s.const_head(app_type) else {
1087            return Ok(e);
1088        };
1089        if ti != info.major_induct {
1090            return Ok(e);
1091        }
1092        let Some(new_ctor) = self.mk_nullary_ctor(app_type, info.num_params as usize) else {
1093            return Ok(e);
1094        };
1095        let nt = self.infer(new_ctor, true)?;
1096        if !self.is_def_eq(app_type, nt)? {
1097            return Ok(e);
1098        }
1099        Ok(new_ctor)
1100    }
1101
1102    fn is_constructor_app(&self, e: ExprId) -> bool {
1103        match self.s.const_head(e) {
1104            Some((n, _)) => self.env.ctor(n).is_some(),
1105            None => false,
1106        }
1107    }
1108
1109    /// The major premise of a structure's recursor as its constructor applied to its projections, taken before the
1110    /// major is reduced; `None` where that does not apply. By η a structure's value is that application, so the rule
1111    /// fires without the major's weak head normal form, which a proof need not have
1112    /// (`corner-cases/eta-struct-major-no-whnf`). It applies to a structure in a universe that is never `Prop`, and to
1113    /// one in `Prop` whose own recursor eliminates into every universe (`And`), whose fields are all proofs.
1114    fn eta_major_first(&mut self, info: &RecInfo, major: ExprId) -> KResult<Option<ExprId>> {
1115        if !self.env.is_non_rec_structure(info.major_induct)
1116            || matches!(self.s.node(major), Node::NatLit(_) | Node::StrLit(_))
1117            || self.is_constructor_app(major)
1118        {
1119            return Ok(None);
1120        }
1121        let et = self.infer(major, true)?;
1122        let ety = self.whnf(et)?;
1123        let (head, args) = self.s.app_args(ety);
1124        let Node::Const(i, ls) = self.s.node(head) else {
1125            return Ok(None);
1126        };
1127        if i != info.major_induct {
1128            return Ok(None);
1129        }
1130        let tt = self.infer(ety, true)?;
1131        let sort = self.whnf(tt)?;
1132        let Node::Sort(u) = self.s.node(sort) else {
1133            return Ok(None);
1134        };
1135        let levels = &self.s.levels;
1136        if !(levels.is_never_zero(u) || (levels.is_always_zero(u) && self.env.eliminates_into_every_sort(info.major_induct))) {
1137            return Ok(None);
1138        }
1139        Ok(Some(self.expand_eta_struct(i, ls, &args, major)))
1140    }
1141
1142    fn to_ctor_when_struct(&mut self, induct: NameId, e: ExprId) -> KResult<ExprId> {
1143        if !self.env.is_non_rec_structure(induct) || self.is_constructor_app(e) {
1144            return Ok(e);
1145        }
1146        let et = self.infer(e, true)?;
1147        let ety = self.whnf(et)?;
1148        let (head, args) = self.s.app_args(ety);
1149        let Node::Const(i, ls) = self.s.node(head) else {
1150            return Ok(e);
1151        };
1152        if i != induct {
1153            return Ok(e);
1154        }
1155        let tt = self.infer(ety, true)?;
1156        let sort = self.whnf(tt)?;
1157        let Node::Sort(u) = self.s.node(sort) else {
1158            return Ok(e);
1159        };
1160        if !self.s.levels.is_never_zero(u) {
1161            return Ok(e);
1162        }
1163        Ok(self.expand_eta_struct(i, ls, &args, e))
1164    }
1165
1166    fn expand_eta_struct(&mut self, i: NameId, ls: LevelsId, args: &[ExprId], e: ExprId) -> ExprId {
1167        let ctor = self.env.inductive(i).unwrap().ctors[0];
1168        let ci = self.env.ctor(ctor).unwrap().clone();
1169        let c = self.s.cnst(ctor, ls);
1170        let mut r = self
1171            .s
1172            .mk_app_n(c, &args[..(ci.num_params as usize).min(args.len())]);
1173        for f in 0..ci.num_fields {
1174            let p = self.s.proj(i, f, e);
1175            r = self.s.app(r, p);
1176        }
1177        r
1178    }
1179
1180    pub fn nat_lit_to_ctor(&mut self, n: &Nat) -> ExprId {
1181        if n.is_zero() {
1182            return self.s.cnst(self.wk.nat_zero, LevelsId::EMPTY);
1183        }
1184        let succ = self.s.cnst(self.wk.nat_succ, LevelsId::EMPTY);
1185        let pred = self.s.nat_lit(n.sub(&Nat::from_u64(1)));
1186        self.s.app(succ, pred)
1187    }
1188
1189    pub fn str_lit_to_ctor(&mut self, sid: super::expr::StrId) -> ExprId {
1190        let st = self.s.string(sid);
1191        let zero = self.s.levels.list(&[LevelId::ZERO]);
1192        let ch = self.s.cnst(self.wk.char_, LevelsId::EMPTY);
1193        let nil_c = self.s.cnst(self.wk.list_nil, zero);
1194        let nil = self.s.app(nil_c, ch);
1195        let cons_c = self.s.cnst(self.wk.list_cons, zero);
1196        let cons = self.s.app(cons_c, ch);
1197        let of_nat = self.s.cnst(self.wk.char_of_nat, LevelsId::EMPTY);
1198        let mut list = nil;
1199        for c in st.chars().rev() {
1200            let lit = self.s.nat_lit(Nat::from_u64(c as u64));
1201            let chr = self.s.app(of_nat, lit);
1202            let cell = self.s.app(cons, chr);
1203            list = self.s.app(cell, list);
1204        }
1205        let of_list = self.s.cnst(self.wk.string_of_list, LevelsId::EMPTY);
1206        self.s.app(of_list, list)
1207    }
1208
1209    /// The head constant of `e` if it δ-unfolds at the given universe arguments.
1210    fn is_delta(&self, e: ExprId) -> Option<Rc<Const>> {
1211        let (n, ls) = self.s.const_head(e)?;
1212        let c = self.env.get(n)?;
1213        if c.delta_value().is_some() && self.s.levels.get_list(ls).len() == c.lparams.len() {
1214            Some(c.clone())
1215        } else {
1216            None
1217        }
1218    }
1219
1220    fn unfold_definition_core(&mut self, e: ExprId) -> Option<ExprId> {
1221        let Node::Const(n, ls) = self.s.node(e) else {
1222            return None;
1223        };
1224        let c = self.is_delta(e)?;
1225        let v = c.delta_value().unwrap();
1226        if ls == LevelsId::EMPTY || c.lparams.is_empty() {
1227            return Some(v);
1228        }
1229        if !self.env.is_settled(n) {
1230            if let Some(r) = self.unfold_cache.get(&e) {
1231                return Some(*r);
1232            }
1233            let us = self.s.levels.get_list(ls).to_vec();
1234            let r = self.s.instantiate_lparams(v, &c.lparams, &us);
1235            self.unfold_cache.insert(e, r);
1236            return Some(r);
1237        }
1238        if let Some(r) = self.global_value.get(&(n, ls)) {
1239            return Some(*r);
1240        }
1241        let us = self.s.levels.get_list(ls).to_vec();
1242        let (r, persistent) = self
1243            .s
1244            .with_persistent(|s| s.instantiate_lparams(v, &c.lparams, &us));
1245        if persistent {
1246            self.global_value.insert((n, ls), r);
1247        } else {
1248            self.unfold_cache.insert(e, r);
1249        }
1250        Some(r)
1251    }
1252
1253    pub fn unfold_definition(&mut self, e: ExprId) -> Option<ExprId> {
1254        let unfolded = if let Node::App(..) = self.s.node(e) {
1255            let (f0, args) = self.s.app_args(e);
1256            let f = self.unfold_definition_core(f0)?;
1257            Some(self.s.mk_app_n(f, &args))
1258        } else {
1259            self.unfold_definition_core(e)
1260        };
1261        self.unfold_steps += u64::from(unfolded.is_some());
1262        unfolded
1263    }
1264
1265    fn raw_nat_lit_ext(&self, e: ExprId) -> Option<Nat> {
1266        match self.s.node(e) {
1267            Node::NatLit(n) => Some(self.s.nat(n).clone()),
1268            Node::Const(n, ls) if n == self.wk.nat_zero && ls == LevelsId::EMPTY => {
1269                Some(Nat::zero())
1270            }
1271            _ => None,
1272        }
1273    }
1274
1275    pub(super) fn nat_bin_args(&mut self, a: ExprId, b: ExprId) -> KResult<Option<(Nat, Nat)>> {
1276        let wa = self.whnf(a)?;
1277        let Some(x) = self.raw_nat_lit_ext(wa) else {
1278            return Ok(None);
1279        };
1280        let wb = self.whnf(b)?;
1281        let Some(y) = self.raw_nat_lit_ext(wb) else {
1282            return Ok(None);
1283        };
1284        Ok(Some((x, y)))
1285    }
1286
1287    fn bool_lit(&mut self, b: bool) -> ExprId {
1288        let n = if b {
1289            self.wk.bool_true
1290        } else {
1291            self.wk.bool_false
1292        };
1293        self.s.cnst(n, LevelsId::EMPTY)
1294    }
1295
1296    /// Lean's `reduceNat`: `Nat.succ` and the GMP-accelerated binary operations on literals.
1297    pub fn reduce_nat(&mut self, e: ExprId) -> KResult<Option<ExprId>> {
1298        if !self.nat_literal_rules() {
1299            return Ok(None);
1300        }
1301        let Node::App(f, a1) = self.s.node(e) else {
1302            return Ok(None);
1303        };
1304        if let Node::Const(n, ls) = self.s.node(f) {
1305            if n == self.wk.nat_succ && ls == LevelsId::EMPTY {
1306                let w = self.whnf(a1)?;
1307                return Ok(self
1308                    .raw_nat_lit_ext(w)
1309                    .map(|v| v.succ())
1310                    .map(|v| self.s.nat_lit(v)));
1311            }
1312            return Ok(None);
1313        }
1314        let Node::App(g, a0) = self.s.node(f) else {
1315            return Ok(None);
1316        };
1317        let Node::Const(op, _) = self.s.node(g) else {
1318            return Ok(None);
1319        };
1320        let w = &self.wk;
1321        let kind = if op == w.nat_add {
1322            0
1323        } else if op == w.nat_sub {
1324            1
1325        } else if op == w.nat_mul {
1326            2
1327        } else if op == w.nat_pow {
1328            3
1329        } else if op == w.nat_gcd {
1330            4
1331        } else if op == w.nat_mod {
1332            5
1333        } else if op == w.nat_div {
1334            6
1335        } else if op == w.nat_beq {
1336            7
1337        } else if op == w.nat_ble {
1338            8
1339        } else if op == w.nat_land {
1340            9
1341        } else if op == w.nat_lor {
1342            10
1343        } else if op == w.nat_xor {
1344            11
1345        } else if op == w.nat_shift_left {
1346            12
1347        } else if op == w.nat_shift_right {
1348            13
1349        } else {
1350            return Ok(None);
1351        };
1352        if !self.validated_primitives.contains(&op) {
1353            return Ok(None);
1354        }
1355        let Some((x, y)) = self.nat_bin_args(a0, a1)? else {
1356            return Ok(None);
1357        };
1358        let r = match kind {
1359            0 => x.add(&y),
1360            1 => x.sub(&y),
1361            2 => x.mul(&y),
1362            3 => match y.to_u64() {
1363                Some(k) if k <= 1 << 24 => x.pow(k as u32),
1364                _ => return Ok(None),
1365            },
1366            4 => x.gcd(&y),
1367            5 => x.rem(&y),
1368            6 => x.div(&y),
1369            7 => return Ok(Some(self.bool_lit(x == y))),
1370            8 => return Ok(Some(self.bool_lit(x <= y))),
1371            9 => x.land(&y),
1372            10 => x.lor(&y),
1373            11 => x.xor(&y),
1374            12 => match y.to_u64() {
1375                Some(k) if k <= 1 << 24 => x.shl(k),
1376                _ => return Ok(None),
1377            },
1378            13 => match y.to_u64() {
1379                Some(k) => x.shr(k),
1380                None => Nat::zero(),
1381            },
1382            _ => unreachable!(),
1383        };
1384        Ok(Some(self.s.nat_lit(r)))
1385    }
1386
1387    /// What the theory computes natively on literals: a validated primitive, in either theory (in
1388    /// LOGOS's, Lean content's `Nat` operations, checked as in Lean's by `check_primitive_def`), and
1389    /// in LOGOS's a certified program ([`Kernel::reduce_certified_program`]).
1390    #[inline]
1391    fn reduce_native(&mut self, e: ExprId) -> KResult<Option<ExprId>> {
1392        if let Some(r) = self.reduce_nat(e)? {
1393            return Ok(Some(r));
1394        }
1395        if self.theory == Theory::Logos {
1396            return self.reduce_certified_program(e);
1397        }
1398        Ok(None)
1399    }
1400
1401    pub fn whnf(&mut self, e: ExprId) -> KResult<ExprId> {
1402        match self.s.node(e) {
1403            Node::BVar(_)
1404            | Node::Sort(_)
1405            | Node::Pi(..)
1406            | Node::NatLit(_)
1407            | Node::StrLit(_)
1408            | Node::Lit(_) => return Ok(e),
1409            Node::FVar(_) => {
1410                if self.is_let_fvar(e).is_none() {
1411                    return Ok(e);
1412                }
1413            }
1414            _ => {}
1415        }
1416        if let Some(r) = self.whnf_cache.get(&e) {
1417            return Ok(*r);
1418        }
1419        self.enter()?;
1420        let r = self.whnf_loop(e);
1421        self.leave();
1422        let r = r?;
1423        self.whnf_cache.insert(e, r);
1424        Ok(r)
1425    }
1426
1427    fn whnf_loop(&mut self, e: ExprId) -> KResult<ExprId> {
1428        let mut t = e;
1429        loop {
1430            t = self.whnf_core(t, false)?;
1431            if self.theory == Theory::Logos {
1432                if let Some(r) = self.reduce_builtin(t)? {
1433                    return Ok(r);
1434                }
1435            }
1436            if let Some(r) = self.reduce_native(t)? {
1437                return Ok(r);
1438            }
1439            match self.unfold_definition(t) {
1440                Some(u) => t = u,
1441                None => return Ok(t),
1442            }
1443        }
1444    }
1445
1446    // ---- definitional equality ----
1447
1448    /// Whether a term of type `actual` may stand where `expected` is required: definitional
1449    /// equality in Lean's theory, cumulative subtyping in LOGOS's.
1450    #[inline]
1451    pub fn conforms(&mut self, actual: ExprId, expected: ExprId) -> KResult<bool> {
1452        match self.theory {
1453            Theory::Lean => self.is_def_eq(actual, expected),
1454            Theory::Logos => self.is_subtype(actual, expected),
1455        }
1456    }
1457
1458    /// [`Kernel::conforms`] for an argument of type `arg` against a function's domain `dom`.
1459    #[inline]
1460    fn accepts_argument(&mut self, dom: ExprId, arg: ExprId) -> KResult<bool> {
1461        match self.theory {
1462            Theory::Lean => self.is_def_eq(dom, arg),
1463            Theory::Logos => self.is_subtype(arg, dom),
1464        }
1465    }
1466
1467    pub fn is_def_eq(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1468        if t == s {
1469            return Ok(true);
1470        }
1471        #[cfg(feature = "nbe")]
1472        if self.dual_conv {
1473            let classic = self.decide(t, s)?;
1474            let mut st = std::mem::take(&mut self.nbe);
1475            st.eager = self.eager;
1476            let other = crate::dag::nbe::Nbe::new(self, &mut st).is_def_eq(t, s);
1477            self.nbe = st;
1478            let other = other?;
1479            if classic != other {
1480                return Err(KernelFailure::resource(format!(
1481                    "the two conversion engines disagree: substitution {classic}, closure {other}"
1482                ))
1483                .with_detail(format!("{} =?= {}", self.show(t), self.show(s))));
1484            }
1485            return Ok(classic);
1486        }
1487        #[cfg(feature = "nbe")]
1488        if self.use_nbe {
1489            let mut st = std::mem::take(&mut self.nbe);
1490            st.eager = self.eager;
1491            let r = crate::dag::nbe::Nbe::new(self, &mut st).is_def_eq(t, s);
1492            self.nbe = st;
1493            return r;
1494        }
1495        self.decide(t, s)
1496    }
1497
1498    /// `t ≡ s` (`t ≠ s`) by the algorithm, its answer remembered: a proof as a proven pair, a
1499    /// refutation as a refuted pair of the current mode and proof generation (see `refuted`).
1500    fn decide(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1501        let key = if t <= s {
1502            (t, s, self.eager)
1503        } else {
1504            (s, t, self.eager)
1505        };
1506        let stamp = self.eqv.generation;
1507        if self.refuted.get(&key) == Some(&stamp) {
1508            return Ok(false);
1509        }
1510        self.enter()?;
1511        let r = self.is_def_eq_core(t, s);
1512        self.leave();
1513        let r = r?;
1514        if r {
1515            self.eqv.insert(t, s);
1516        } else {
1517            self.refuted.insert(key, stamp);
1518        }
1519        Ok(r)
1520    }
1521
1522    /// Equal, proven equal, or congruent: same shape with children pairwise equal or proven equal.
1523    fn eqv_is_equiv(&mut self, a: ExprId, b: ExprId) -> bool {
1524        if a == b || self.eqv.contains(a, b) {
1525            return true;
1526        }
1527        let r = match (self.s.node(a), self.s.node(b)) {
1528            (Node::App(f1, a1), Node::App(f2, a2)) => {
1529                self.eqv_is_equiv(f1, f2) && self.eqv_is_equiv(a1, a2)
1530            }
1531            (Node::Lam(d1, b1), Node::Lam(d2, b2)) | (Node::Pi(d1, b1), Node::Pi(d2, b2)) => {
1532                self.eqv_is_equiv(d1, d2) && self.eqv_is_equiv(b1, b2)
1533            }
1534            (Node::Proj(_, i1, e1), Node::Proj(_, i2, e2)) => i1 == i2 && self.eqv_is_equiv(e1, e2),
1535            (Node::Let(t1, v1, b1), Node::Let(t2, v2, b2)) => {
1536                self.eqv_is_equiv(t1, t2) && self.eqv_is_equiv(v1, v2) && self.eqv_is_equiv(b1, b2)
1537            }
1538            _ => false,
1539        };
1540        if r {
1541            self.eqv.insert(a, b);
1542        }
1543        r
1544    }
1545
1546    fn quick_is_def_eq(&mut self, t: ExprId, s: ExprId) -> KResult<LBool> {
1547        if self.eqv_is_equiv(t, s) {
1548            return Ok(LBool::True);
1549        }
1550        Ok(match (self.s.node(t), self.s.node(s)) {
1551            (Node::Lam(..), Node::Lam(..)) => self.is_def_eq_binding(t, s, false)?.into(),
1552            (Node::Pi(..), Node::Pi(..)) => self.is_def_eq_binding(t, s, true)?.into(),
1553            (Node::Sort(a), Node::Sort(b)) => self.s.levels.equiv(a, b).into(),
1554            (Node::NatLit(_) | Node::StrLit(_), Node::NatLit(_) | Node::StrLit(_)) => {
1555                (t == s).into()
1556            }
1557            _ => LBool::Undef,
1558        })
1559    }
1560
1561    fn is_def_eq_binding(&mut self, mut t: ExprId, mut s: ExprId, pi: bool) -> KResult<bool> {
1562        let mut subst: Vec<ExprId> = Vec::new();
1563        loop {
1564            let (tn, sn) = (self.s.node(t), self.s.node(s));
1565            let (td, tb, sd, sb) = match (tn, sn) {
1566                (Node::Lam(td, tb), Node::Lam(sd, sb)) if !pi => (td, tb, sd, sb),
1567                (Node::Pi(td, tb), Node::Pi(sd, sb)) if pi => (td, tb, sd, sb),
1568                _ => break,
1569            };
1570            let mut s_type = None;
1571            if td != sd {
1572                let st = self.s.instantiate_rev(sd, &subst);
1573                let tt = self.s.instantiate_rev(td, &subst);
1574                if !self.is_def_eq(tt, st)? {
1575                    return Ok(false);
1576                }
1577                s_type = Some(st);
1578            }
1579            if self.s.has_loose_bvars(tb) || self.s.has_loose_bvars(sb) {
1580                let st = match s_type {
1581                    Some(st) => st,
1582                    None => self.s.instantiate_rev(sd, &subst),
1583                };
1584                let fv = self.push_local(st, None);
1585                subst.push(fv);
1586            } else {
1587                let dummy = self.s.prop();
1588                subst.push(dummy);
1589            }
1590            t = tb;
1591            s = sb;
1592        }
1593        let ti = self.s.instantiate_rev(t, &subst);
1594        let si = self.s.instantiate_rev(s, &subst);
1595        self.is_def_eq(ti, si)
1596    }
1597
1598    /// The arguments of two applications of the same head, pairwise, last to first — skipping
1599    /// the positions the head's definition cannot distinguish (see [`super::relevance`]).
1600    fn is_def_eq_args(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1601        let n = self.s.app_num_args(t);
1602        if n != self.s.app_num_args(s) {
1603            return Ok(false);
1604        }
1605        let mask = match self.s.const_head(t) {
1606            Some((c, ls)) => self.ignorable_args(c, ls)?,
1607            None => 0,
1608        };
1609        let (mut t, mut s) = (t, s);
1610        let mut i = n;
1611        while let (Node::App(tf, ta), Node::App(sf, sa)) = (self.s.node(t), self.s.node(s)) {
1612            i -= 1;
1613            let ignorable = i < super::relevance::MAX_TRACKED && (mask >> i) & 1 == 1;
1614            if !ignorable && !self.is_def_eq(ta, sa)? {
1615                return Ok(false);
1616            }
1617            t = tf;
1618            s = sf;
1619        }
1620        Ok(true)
1621    }
1622
1623    fn try_eta_expansion_core(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1624        if !matches!(self.s.node(t), Node::Lam(..)) || matches!(self.s.node(s), Node::Lam(..)) {
1625            return Ok(false);
1626        }
1627        let st = self.infer(s, true)?;
1628        let w = self.whnf(st)?;
1629        let Node::Pi(dom, _) = self.s.node(w) else {
1630            return Ok(false);
1631        };
1632        let b0 = self.s.bvar(0);
1633        let body = self.s.app(s, b0);
1634        let lam = self.s.lam(dom, body);
1635        self.is_def_eq(t, lam)
1636    }
1637
1638    fn try_eta_struct_core(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1639        if self.theory == Theory::Logos {
1640            return self.logos_eta_struct(t, s);
1641        }
1642        let Some((f, _)) = self.s.const_head(s) else {
1643            return Ok(false);
1644        };
1645        let Some(ci) = self.env.ctor(f).cloned() else {
1646            return Ok(false);
1647        };
1648        let (_, args) = self.s.app_args(s);
1649        if args.len() != (ci.num_params + ci.num_fields) as usize {
1650            return Ok(false);
1651        }
1652        if !self.env.is_non_rec_structure(ci.induct) {
1653            return Ok(false);
1654        }
1655        let tt = self.infer(t, true)?;
1656        let st = self.infer(s, true)?;
1657        if !self.is_def_eq(tt, st)? {
1658            return Ok(false);
1659        }
1660        for i in ci.num_params as usize..args.len() {
1661            let p = self
1662                .s
1663                .proj(ci.induct, (i - ci.num_params as usize) as u32, t);
1664            if !self.is_def_eq(p, args[i])? {
1665                return Ok(false);
1666            }
1667        }
1668        Ok(true)
1669    }
1670
1671    fn is_def_eq_app(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1672        if !matches!(self.s.node(t), Node::App(..)) || !matches!(self.s.node(s), Node::App(..)) {
1673            return Ok(false);
1674        }
1675        let (tf, targs) = self.s.app_args(t);
1676        let (sf, sargs) = self.s.app_args(s);
1677        if targs.len() != sargs.len() {
1678            return Ok(false);
1679        }
1680        if !self.is_def_eq(tf, sf)? {
1681            return Ok(false);
1682        }
1683        for (a, b) in targs.iter().zip(&sargs) {
1684            if !self.is_def_eq(*a, *b)? {
1685                return Ok(false);
1686            }
1687        }
1688        Ok(true)
1689    }
1690
1691    fn is_def_eq_proof_irrel(&mut self, t: ExprId, s: ExprId) -> KResult<LBool> {
1692        let tt = self.infer(t, true)?;
1693        if !self.is_prop(tt)? {
1694            return Ok(LBool::Undef);
1695        }
1696        let st = self.infer(s, true)?;
1697        Ok(self.is_def_eq(tt, st)?.into())
1698    }
1699
1700    fn failed_before(&self, t: ExprId, s: ExprId) -> bool {
1701        let k = if t <= s { (t, s) } else { (s, t) };
1702        self.failure.contains(&k)
1703    }
1704
1705    fn cache_failure(&mut self, t: ExprId, s: ExprId) {
1706        let k = if t <= s { (t, s) } else { (s, t) };
1707        self.failure.insert(k);
1708    }
1709
1710    fn try_unfold_proj_app(&mut self, e: ExprId) -> KResult<Option<ExprId>> {
1711        let f = self.s.app_fn(e);
1712        if !matches!(self.s.node(f), Node::Proj(..)) {
1713            return Ok(None);
1714        }
1715        let e2 = self.whnf_core(e, false)?;
1716        Ok((e2 != e).then_some(e2))
1717    }
1718
1719    fn delta(&mut self, e: ExprId) -> KResult<ExprId> {
1720        let u = self
1721            .unfold_definition(e)
1722            .expect("delta on a non-unfoldable head");
1723        self.whnf_core(u, true)
1724    }
1725
1726    fn lazy_delta_step(&mut self, tn: ExprId, sn: ExprId) -> KResult<Step> {
1727        let cont = |k: &mut Kernel, tn: ExprId, sn: ExprId| -> KResult<Step> {
1728            Ok(match k.quick_is_def_eq(tn, sn)? {
1729                LBool::Undef => Step::Continue(tn, sn),
1730                LBool::True => Step::True,
1731                LBool::False => Step::False(tn, sn),
1732            })
1733        };
1734        match (self.is_delta(tn), self.is_delta(sn)) {
1735            (None, None) => Ok(Step::Unknown(tn, sn)),
1736            (Some(_), None) => {
1737                if let Some(sn2) = self.try_unfold_proj_app(sn)? {
1738                    cont(self, tn, sn2)
1739                } else {
1740                    let tn2 = self.delta(tn)?;
1741                    cont(self, tn2, sn)
1742                }
1743            }
1744            (None, Some(_)) => {
1745                if let Some(tn2) = self.try_unfold_proj_app(tn)? {
1746                    cont(self, tn2, sn)
1747                } else {
1748                    let sn2 = self.delta(sn)?;
1749                    cont(self, tn, sn2)
1750                }
1751            }
1752            (Some(dt), Some(ds)) => {
1753                let (ht, hs) = (dt.hint(), ds.hint());
1754                if ht.lt(hs) {
1755                    let sn2 = self.delta(sn)?;
1756                    cont(self, tn, sn2)
1757                } else if hs.lt(ht) {
1758                    let tn2 = self.delta(tn)?;
1759                    cont(self, tn2, sn)
1760                } else {
1761                    if matches!(self.s.node(tn), Node::App(..))
1762                        && matches!(self.s.node(sn), Node::App(..))
1763                        && dt.name == ds.name
1764                        && matches!(ht, Hint::Regular(_))
1765                        && !self.failed_before(tn, sn)
1766                    {
1767                        let (_, tl) = self.s.const_head(tn).unwrap();
1768                        let (_, sl) = self.s.const_head(sn).unwrap();
1769                        if self.s.levels.equiv_lists(tl, sl) && self.is_def_eq_args(tn, sn)? {
1770                            return Ok(Step::True);
1771                        }
1772                        self.cache_failure(tn, sn);
1773                    }
1774                    let tn2 = self.delta(tn)?;
1775                    let sn2 = self.delta(sn)?;
1776                    cont(self, tn2, sn2)
1777                }
1778            }
1779        }
1780    }
1781
1782    fn is_nat_zero(&self, e: ExprId) -> bool {
1783        match self.s.node(e) {
1784            Node::Const(n, ls) => n == self.wk.nat_zero && ls == LevelsId::EMPTY,
1785            Node::NatLit(n) => self.s.nat(n).is_zero(),
1786            _ => false,
1787        }
1788    }
1789
1790    fn nat_succ_of(&mut self, e: ExprId) -> Option<ExprId> {
1791        match self.s.node(e) {
1792            Node::NatLit(n) => {
1793                let v = self.s.nat(n).clone();
1794                if v.is_zero() {
1795                    None
1796                } else {
1797                    Some(self.s.nat_lit(v.sub(&Nat::from_u64(1))))
1798                }
1799            }
1800            Node::App(f, a) => match self.s.node(f) {
1801                Node::Const(n, _) if n == self.wk.nat_succ => Some(a),
1802                _ => None,
1803            },
1804            _ => None,
1805        }
1806    }
1807
1808    fn is_def_eq_offset(&mut self, t: ExprId, s: ExprId) -> KResult<LBool> {
1809        if self.is_nat_zero(t) && self.is_nat_zero(s) {
1810            return Ok(if self.reads_nat_literal(&Nat::zero()) {
1811                LBool::True
1812            } else {
1813                LBool::Undef
1814            });
1815        }
1816        if !self.nat_literal_rules() {
1817            return Ok(LBool::Undef);
1818        }
1819        match (self.nat_succ_of(t), self.nat_succ_of(s)) {
1820            (Some(a), Some(b)) => Ok(self.is_def_eq_core_guarded(a, b)?.into()),
1821            _ => Ok(LBool::Undef),
1822        }
1823    }
1824
1825    fn is_def_eq_core_guarded(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1826        if t == s {
1827            return Ok(true);
1828        }
1829        self.enter()?;
1830        let r = self.is_def_eq_core(t, s);
1831        self.leave();
1832        r
1833    }
1834
1835    fn lazy_delta_reduction(&mut self, mut tn: ExprId, mut sn: ExprId) -> KResult<Step> {
1836        loop {
1837            match self.is_def_eq_offset(tn, sn)? {
1838                LBool::True => return Ok(Step::True),
1839                LBool::False => return Ok(Step::False(tn, sn)),
1840                LBool::Undef => {}
1841            }
1842            if (!self.s.has_fvar(tn) && !self.s.has_fvar(sn)) || self.eager {
1843                if let Some(tn2) = self.reduce_native(tn)? {
1844                    return Ok(if self.is_def_eq_core_guarded(tn2, sn)? {
1845                        Step::True
1846                    } else {
1847                        Step::False(tn2, sn)
1848                    });
1849                } else if let Some(sn2) = self.reduce_native(sn)? {
1850                    return Ok(if self.is_def_eq_core_guarded(tn, sn2)? {
1851                        Step::True
1852                    } else {
1853                        Step::False(tn, sn2)
1854                    });
1855                }
1856            }
1857            match self.lazy_delta_step(tn, sn)? {
1858                Step::Continue(a, b) => {
1859                    tn = a;
1860                    sn = b;
1861                }
1862                r => return Ok(r),
1863            }
1864        }
1865    }
1866
1867    fn lazy_delta_proj_reduction(
1868        &mut self,
1869        mut tn: ExprId,
1870        mut sn: ExprId,
1871        idx: u32,
1872    ) -> KResult<bool> {
1873        loop {
1874            match self.lazy_delta_step(tn, sn)? {
1875                Step::Continue(a, b) => {
1876                    tn = a;
1877                    sn = b;
1878                }
1879                Step::True => return Ok(true),
1880                Step::Unknown(a, b) | Step::False(a, b) => {
1881                    if let Some(tf) = self.reduce_proj_core(idx, a)? {
1882                        if let Some(sf) = self.reduce_proj_core(idx, b)? {
1883                            return self.is_def_eq_core_guarded(tf, sf);
1884                        }
1885                    }
1886                    return self.is_def_eq_core_guarded(a, b);
1887                }
1888            }
1889        }
1890    }
1891
1892    fn try_string_lit_expansion_core(&mut self, t: ExprId, s: ExprId) -> KResult<LBool> {
1893        let Node::StrLit(sid) = self.s.node(t) else {
1894            return Ok(LBool::Undef);
1895        };
1896        let Node::App(sf, _) = self.s.node(s) else {
1897            return Ok(LBool::Undef);
1898        };
1899        match self.s.node(sf) {
1900            Node::Const(n, ls) if n == self.wk.string_of_list && ls == LevelsId::EMPTY => {}
1901            _ => return Ok(LBool::Undef),
1902        }
1903        let x = self.str_lit_to_ctor(sid);
1904        Ok(self.is_def_eq_core_guarded(x, s)?.into())
1905    }
1906
1907    fn is_def_eq_unit_like(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1908        let tt = self.infer(t, true)?;
1909        let tty = self.whnf(tt)?;
1910        let Some((i, _)) = self.s.const_head(tty) else {
1911            return Ok(false);
1912        };
1913        let Some(ind) = self.env.inductive(i) else {
1914            return Ok(false);
1915        };
1916        if ind.is_rec || ind.ctors.len() != 1 || ind.num_indices != 0 {
1917            return Ok(false);
1918        }
1919        let c = ind.ctors[0];
1920        if self.env.ctor(c).map(|ci| ci.num_fields) != Some(0) {
1921            return Ok(false);
1922        }
1923        let st = self.infer(s, true)?;
1924        self.is_def_eq_core_guarded(tty, st)
1925    }
1926
1927    /// The δ-unfoldings this kernel's weak-head reduction has taken: a primitive at literals computed
1928    /// natively takes none.
1929    pub fn unfold_steps(&self) -> u64 {
1930        self.unfold_steps
1931    }
1932
1933    /// The definitional-equality steps this kernel has taken (see `conv_steps`).
1934    pub fn conversion_steps(&self) -> u64 {
1935        self.conv_steps
1936    }
1937
1938    /// Whether `n` is a `true` the `decide` fast path reduces the other side for: `Bool.true`, and
1939    /// in LOGOS's theory LOGOS's own `true`.
1940    pub(crate) fn is_true_constant(&self, n: NameId) -> bool {
1941        n == self.wk.bool_true || self.wk.logos_true == Some(n)
1942    }
1943
1944    fn is_def_eq_core(&mut self, t: ExprId, s: ExprId) -> KResult<bool> {
1945        self.conv_steps += 1;
1946        match self.quick_is_def_eq(t, s)? {
1947            LBool::True => return Ok(true),
1948            LBool::False => return Ok(false),
1949            LBool::Undef => {}
1950        }
1951        if (!self.s.has_fvar(t) || self.eager)
1952            && matches!(self.s.node(s), Node::Const(n, _) if self.is_true_constant(n))
1953        {
1954            let w = self.whnf(t)?;
1955            if self.s.node(w) == self.s.node(s) {
1956                return Ok(true);
1957            }
1958        }
1959        let tn = self.whnf_core(t, true)?;
1960        let sn = self.whnf_core(s, true)?;
1961        if tn != t || sn != s {
1962            match self.quick_is_def_eq(tn, sn)? {
1963                LBool::True => return Ok(true),
1964                LBool::False => return Ok(false),
1965                LBool::Undef => {}
1966            }
1967        }
1968        match self.is_def_eq_proof_irrel(tn, sn)? {
1969            LBool::True => return Ok(true),
1970            LBool::False => return Ok(false),
1971            LBool::Undef => {}
1972        }
1973        let (tn, sn) = match self.lazy_delta_reduction(tn, sn)? {
1974            Step::True => return Ok(true),
1975            Step::False(..) => return Ok(false),
1976            Step::Unknown(a, b) => (a, b),
1977            Step::Continue(..) => unreachable!(),
1978        };
1979        match (self.s.node(tn), self.s.node(sn)) {
1980            (Node::Const(tf, tl), Node::Const(sf, sl)) => {
1981                if tf == sf && self.s.levels.equiv_lists(tl, sl) {
1982                    return Ok(true);
1983                }
1984            }
1985            (Node::FVar(a), Node::FVar(b)) => {
1986                if a == b {
1987                    return Ok(true);
1988                }
1989            }
1990            (Node::Proj(_, ti, te), Node::Proj(_, si, se)) => {
1991                if ti == si && self.lazy_delta_proj_reduction(te, se, ti)? {
1992                    return Ok(true);
1993                }
1994            }
1995            _ => {}
1996        }
1997        let tnn = self.whnf_core(tn, false)?;
1998        let snn = self.whnf_core(sn, false)?;
1999        if tnn != tn || snn != sn {
2000            return self.is_def_eq_core_guarded(tnn, snn);
2001        }
2002        if self.is_def_eq_app(tn, sn)? {
2003            return Ok(true);
2004        }
2005        if self.try_eta_expansion_core(tn, sn)? || self.try_eta_expansion_core(sn, tn)? {
2006            return Ok(true);
2007        }
2008        if self.try_eta_struct_core(tn, sn)? || self.try_eta_struct_core(sn, tn)? {
2009            return Ok(true);
2010        }
2011        match self.try_string_lit_expansion_core(tn, sn)? {
2012            LBool::True => return Ok(true),
2013            LBool::False => return Ok(false),
2014            LBool::Undef => match self.try_string_lit_expansion_core(sn, tn)? {
2015                LBool::True => return Ok(true),
2016                LBool::False => return Ok(false),
2017                LBool::Undef => {}
2018            },
2019        }
2020        if self.theory == Theory::Logos && self.logos_def_eq(tn, sn)? {
2021            return Ok(true);
2022        }
2023        if self.is_def_eq_unit_like(tn, sn)? {
2024            return Ok(true);
2025        }
2026        Ok(false)
2027    }
2028
2029    // ---- presentation ----
2030
2031    pub fn show(&self, e: ExprId) -> String {
2032        super::pretty::show(self, e)
2033    }
2034
2035    fn describe_mismatch(&self, e: ExprId, expected: ExprId, found: ExprId) -> String {
2036        format!(
2037            "in `{}`\n  expected type: {}\n  but the argument has type: {}",
2038            self.show(e),
2039            self.show(expected),
2040            self.show(found)
2041        )
2042    }
2043
2044    pub fn level_node(&self, l: LevelId) -> LevelNode {
2045        self.s.levels.node(l)
2046    }
2047
2048    pub fn quot_kind(&self, n: NameId) -> Option<QuotKind> {
2049        match self.env.get(n)?.kind {
2050            Kind::Quot(k) => Some(k),
2051            _ => None,
2052        }
2053    }
2054}
2055
2056impl Default for Kernel {
2057    fn default() -> Self {
2058        Self::new()
2059    }
2060}
2061
2062fn clear_map(m: &mut FxMap<ExprId, ExprId>) {
2063    if m.capacity() > 1 << 16 {
2064        *m = FxMap::default();
2065    } else {
2066        m.clear();
2067    }
2068}
2069
2070#[cfg(test)]
2071#[path = "../../../logicaffeine_tests/unit/logicaffeine_kernel/dag/tc/tests.rs"]
2072mod tests;