Skip to main content

oxilean_kernel/infer/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::proj_target::{proj_target, proj_type_params};
6use crate::declaration::{ConstructorVal, InductiveVal};
7use crate::def_eq::DefEqChecker;
8use crate::error::KernelError;
9use crate::expr_util::{get_app_args, get_app_fn, has_any_fvar, has_loose_bvars, mk_app};
10use crate::instantiate::instantiate_type_lparams;
11use crate::reduce::{Reducer, TransparencyMode};
12use crate::subst::{abstract_expr, instantiate};
13use crate::Node;
14use crate::{BinderInfo, Environment, Expr, FVarId, Level, Literal, Name};
15use std::rc::Rc;
16
17use std::collections::HashMap;
18
19/// A simple directed acyclic graph.
20#[allow(dead_code)]
21pub struct SimpleDag {
22    /// `edges[i]` is the list of direct successors of node `i`.
23    edges: Vec<Vec<usize>>,
24}
25#[allow(dead_code)]
26impl SimpleDag {
27    /// Creates a DAG with `n` nodes and no edges.
28    pub fn new(n: usize) -> Self {
29        Self {
30            edges: vec![Vec::new(); n],
31        }
32    }
33    /// Adds an edge from `from` to `to`.
34    pub fn add_edge(&mut self, from: usize, to: usize) {
35        if from < self.edges.len() {
36            self.edges[from].push(to);
37        }
38    }
39    /// Returns the successors of `node`.
40    pub fn successors(&self, node: usize) -> &[usize] {
41        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
42    }
43    /// Returns `true` if `from` can reach `to` via DFS.
44    pub fn can_reach(&self, from: usize, to: usize) -> bool {
45        let mut visited = vec![false; self.edges.len()];
46        self.dfs(from, to, &mut visited)
47    }
48    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
49        if cur == target {
50            return true;
51        }
52        if cur >= visited.len() || visited[cur] {
53            return false;
54        }
55        visited[cur] = true;
56        for &next in self.successors(cur) {
57            if self.dfs(next, target, visited) {
58                return true;
59            }
60        }
61        false
62    }
63    /// Returns the topological order of nodes, or `None` if a cycle is detected.
64    pub fn topological_sort(&self) -> Option<Vec<usize>> {
65        let n = self.edges.len();
66        let mut in_degree = vec![0usize; n];
67        for succs in &self.edges {
68            for &s in succs {
69                if s < n {
70                    in_degree[s] += 1;
71                }
72            }
73        }
74        let mut queue: std::collections::VecDeque<usize> =
75            (0..n).filter(|&i| in_degree[i] == 0).collect();
76        let mut order = Vec::new();
77        while let Some(node) = queue.pop_front() {
78            order.push(node);
79            for &s in self.successors(node) {
80                if s < n {
81                    in_degree[s] -= 1;
82                    if in_degree[s] == 0 {
83                        queue.push_back(s);
84                    }
85                }
86            }
87        }
88        if order.len() == n {
89            Some(order)
90        } else {
91            None
92        }
93    }
94    /// Returns the number of nodes.
95    pub fn num_nodes(&self) -> usize {
96        self.edges.len()
97    }
98}
99/// A dependency closure builder (transitive closure via BFS).
100#[allow(dead_code)]
101pub struct TransitiveClosure {
102    adj: Vec<Vec<usize>>,
103    n: usize,
104}
105#[allow(dead_code)]
106impl TransitiveClosure {
107    /// Creates a transitive closure builder for `n` nodes.
108    pub fn new(n: usize) -> Self {
109        Self {
110            adj: vec![Vec::new(); n],
111            n,
112        }
113    }
114    /// Adds a direct edge.
115    pub fn add_edge(&mut self, from: usize, to: usize) {
116        if from < self.n {
117            self.adj[from].push(to);
118        }
119    }
120    /// Computes all nodes reachable from `start` (including `start`).
121    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
122        let mut visited = vec![false; self.n];
123        let mut queue = std::collections::VecDeque::new();
124        queue.push_back(start);
125        while let Some(node) = queue.pop_front() {
126            if node >= self.n || visited[node] {
127                continue;
128            }
129            visited[node] = true;
130            for &next in &self.adj[node] {
131                queue.push_back(next);
132            }
133        }
134        (0..self.n).filter(|&i| visited[i]).collect()
135    }
136    /// Returns `true` if `from` can transitively reach `to`.
137    pub fn can_reach(&self, from: usize, to: usize) -> bool {
138        self.reachable_from(from).contains(&to)
139    }
140}
141/// A hierarchical configuration tree.
142#[allow(dead_code)]
143pub struct ConfigNode {
144    key: String,
145    value: Option<String>,
146    children: Vec<ConfigNode>,
147}
148#[allow(dead_code)]
149impl ConfigNode {
150    /// Creates a leaf config node with a value.
151    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
152        Self {
153            key: key.into(),
154            value: Some(value.into()),
155            children: Vec::new(),
156        }
157    }
158    /// Creates a section node with children.
159    pub fn section(key: impl Into<String>) -> Self {
160        Self {
161            key: key.into(),
162            value: None,
163            children: Vec::new(),
164        }
165    }
166    /// Adds a child node.
167    pub fn add_child(&mut self, child: ConfigNode) {
168        self.children.push(child);
169    }
170    /// Returns the key.
171    pub fn key(&self) -> &str {
172        &self.key
173    }
174    /// Returns the value, or `None` for section nodes.
175    pub fn value(&self) -> Option<&str> {
176        self.value.as_deref()
177    }
178    /// Returns the number of children.
179    pub fn num_children(&self) -> usize {
180        self.children.len()
181    }
182    /// Looks up a dot-separated path.
183    pub fn lookup(&self, path: &str) -> Option<&str> {
184        let mut parts = path.splitn(2, '.');
185        let head = parts.next()?;
186        let tail = parts.next();
187        if head != self.key {
188            return None;
189        }
190        match tail {
191            None => self.value.as_deref(),
192            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
193        }
194    }
195    fn lookup_relative(&self, path: &str) -> Option<&str> {
196        let mut parts = path.splitn(2, '.');
197        let head = parts.next()?;
198        let tail = parts.next();
199        if head != self.key {
200            return None;
201        }
202        match tail {
203            None => self.value.as_deref(),
204            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
205        }
206    }
207}
208/// A typing judgment: an expression with its inferred type.
209#[allow(dead_code)]
210#[derive(Debug, Clone)]
211pub struct TypingJudgment {
212    /// The expression.
213    pub expr: Expr,
214    /// The inferred type.
215    pub ty: Expr,
216    /// Whether the inference was successful.
217    pub success: bool,
218}
219impl TypingJudgment {
220    /// Create a successful typing judgment.
221    #[allow(dead_code)]
222    pub fn ok(expr: Expr, ty: Expr) -> Self {
223        Self {
224            expr,
225            ty,
226            success: true,
227        }
228    }
229    /// Create a failed typing judgment (no type available).
230    #[allow(dead_code)]
231    pub fn fail(expr: Expr) -> Self {
232        Self {
233            ty: Expr::Sort(Level::zero()),
234            expr,
235            success: false,
236        }
237    }
238    /// Check if the judgment was successful.
239    #[allow(dead_code)]
240    pub fn is_ok(&self) -> bool {
241        self.success
242    }
243}
244/// A sparse vector: stores only non-default elements.
245#[allow(dead_code)]
246pub struct SparseVec<T: Default + Clone + PartialEq> {
247    entries: std::collections::HashMap<usize, T>,
248    default_: T,
249    logical_len: usize,
250}
251#[allow(dead_code)]
252impl<T: Default + Clone + PartialEq> SparseVec<T> {
253    /// Creates a new sparse vector with logical length `len`.
254    pub fn new(len: usize) -> Self {
255        Self {
256            entries: std::collections::HashMap::new(),
257            default_: T::default(),
258            logical_len: len,
259        }
260    }
261    /// Sets element at `idx`.
262    pub fn set(&mut self, idx: usize, val: T) {
263        if val == self.default_ {
264            self.entries.remove(&idx);
265        } else {
266            self.entries.insert(idx, val);
267        }
268    }
269    /// Gets element at `idx`.
270    pub fn get(&self, idx: usize) -> &T {
271        self.entries.get(&idx).unwrap_or(&self.default_)
272    }
273    /// Returns the logical length.
274    pub fn len(&self) -> usize {
275        self.logical_len
276    }
277    /// Returns whether the collection is empty.
278    pub fn is_empty(&self) -> bool {
279        self.len() == 0
280    }
281    /// Returns the number of non-default elements.
282    pub fn nnz(&self) -> usize {
283        self.entries.len()
284    }
285}
286/// A simple stack-based calculator for arithmetic expressions.
287#[allow(dead_code)]
288pub struct StackCalc {
289    stack: Vec<i64>,
290}
291#[allow(dead_code)]
292impl StackCalc {
293    /// Creates a new empty calculator.
294    pub fn new() -> Self {
295        Self { stack: Vec::new() }
296    }
297    /// Pushes an integer literal.
298    pub fn push(&mut self, n: i64) {
299        self.stack.push(n);
300    }
301    /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
302    /// returns it.  Returns `None`, leaving the stack unchanged, if it holds
303    /// fewer than two values.
304    fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
305        let (rest_len, a, b) = match self.stack[..] {
306            [ref rest @ .., a, b] => (rest.len(), a, b),
307            _ => return None,
308        };
309        let value = op(a, b);
310        self.stack.truncate(rest_len);
311        self.stack.push(value);
312        Some(value)
313    }
314    /// Adds the top two values and returns the sum, which replaces them.
315    /// Returns `None`, leaving the stack unchanged, if it holds fewer than
316    /// two values.
317    pub fn add(&mut self) -> Option<i64> {
318        self.apply_binary(|a, b| a + b)
319    }
320    /// Subtracts top from second and returns the difference, which replaces
321    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
322    /// than two values.
323    pub fn sub(&mut self) -> Option<i64> {
324        self.apply_binary(|a, b| a - b)
325    }
326    /// Multiplies the top two values and returns the product, which replaces
327    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
328    /// than two values.
329    pub fn mul(&mut self) -> Option<i64> {
330        self.apply_binary(|a, b| a * b)
331    }
332    /// Peeks the top value.
333    pub fn peek(&self) -> Option<i64> {
334        self.stack.last().copied()
335    }
336    /// Returns the stack depth.
337    pub fn depth(&self) -> usize {
338        self.stack.len()
339    }
340}
341/// A window iterator that yields overlapping windows of size `n`.
342#[allow(dead_code)]
343pub struct WindowIterator<'a, T> {
344    pub(super) data: &'a [T],
345    pub(super) pos: usize,
346    pub(super) window: usize,
347}
348#[allow(dead_code)]
349impl<'a, T> WindowIterator<'a, T> {
350    /// Creates a new window iterator.
351    pub fn new(data: &'a [T], window: usize) -> Self {
352        Self {
353            data,
354            pos: 0,
355            window,
356        }
357    }
358}
359/// A cache entry for type inference results.
360#[allow(dead_code)]
361#[derive(Debug, Clone)]
362pub struct InferCacheEntry {
363    /// The expression whose type was inferred.
364    pub expr: Expr,
365    /// The inferred type.
366    pub ty: Expr,
367}
368/// Represents a rewrite rule `lhs → rhs`.
369#[allow(dead_code)]
370#[allow(missing_docs)]
371pub struct RewriteRule {
372    /// The name of the rule.
373    pub name: String,
374    /// A string representation of the LHS pattern.
375    pub lhs: String,
376    /// A string representation of the RHS.
377    pub rhs: String,
378    /// Whether this is a conditional rule (has side conditions).
379    pub conditional: bool,
380}
381#[allow(dead_code)]
382impl RewriteRule {
383    /// Creates an unconditional rewrite rule.
384    pub fn unconditional(
385        name: impl Into<String>,
386        lhs: impl Into<String>,
387        rhs: impl Into<String>,
388    ) -> Self {
389        Self {
390            name: name.into(),
391            lhs: lhs.into(),
392            rhs: rhs.into(),
393            conditional: false,
394        }
395    }
396    /// Creates a conditional rewrite rule.
397    pub fn conditional(
398        name: impl Into<String>,
399        lhs: impl Into<String>,
400        rhs: impl Into<String>,
401    ) -> Self {
402        Self {
403            name: name.into(),
404            lhs: lhs.into(),
405            rhs: rhs.into(),
406            conditional: true,
407        }
408    }
409    /// Returns a textual representation.
410    pub fn display(&self) -> String {
411        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
412    }
413}
414/// A set of rewrite rules.
415#[allow(dead_code)]
416pub struct RewriteRuleSet {
417    rules: Vec<RewriteRule>,
418}
419#[allow(dead_code)]
420impl RewriteRuleSet {
421    /// Creates an empty rule set.
422    pub fn new() -> Self {
423        Self { rules: Vec::new() }
424    }
425    /// Adds a rule.
426    pub fn add(&mut self, rule: RewriteRule) {
427        self.rules.push(rule);
428    }
429    /// Returns the number of rules.
430    pub fn len(&self) -> usize {
431        self.rules.len()
432    }
433    /// Returns `true` if the set is empty.
434    pub fn is_empty(&self) -> bool {
435        self.rules.is_empty()
436    }
437    /// Returns all conditional rules.
438    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
439        self.rules.iter().filter(|r| r.conditional).collect()
440    }
441    /// Returns all unconditional rules.
442    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
443        self.rules.iter().filter(|r| !r.conditional).collect()
444    }
445    /// Looks up a rule by name.
446    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
447        self.rules.iter().find(|r| r.name == name)
448    }
449}
450/// A counter that can measure elapsed time between snapshots.
451#[allow(dead_code)]
452pub struct Stopwatch {
453    start: crate::wall_clock::Instant,
454    splits: Vec<f64>,
455}
456#[allow(dead_code)]
457impl Stopwatch {
458    /// Creates and starts a new stopwatch.
459    pub fn start() -> Self {
460        Self {
461            start: crate::wall_clock::Instant::now(),
462            splits: Vec::new(),
463        }
464    }
465    /// Records a split time (elapsed since start).
466    pub fn split(&mut self) {
467        self.splits.push(self.elapsed_ms());
468    }
469    /// Returns total elapsed milliseconds since start.
470    pub fn elapsed_ms(&self) -> f64 {
471        self.start.elapsed().as_secs_f64() * 1000.0
472    }
473    /// Returns all recorded split times.
474    pub fn splits(&self) -> &[f64] {
475        &self.splits
476    }
477    /// Returns the number of splits.
478    pub fn num_splits(&self) -> usize {
479        self.splits.len()
480    }
481}
482/// Statistics collected during type inference.
483#[derive(Clone, Debug, Default)]
484pub struct InferStats {
485    /// Number of types inferred.
486    pub infer_calls: usize,
487    /// Number of WHNF reductions performed.
488    pub whnf_calls: usize,
489    /// Number of definitional equality checks.
490    pub def_eq_calls: usize,
491    /// Number of constant lookups.
492    pub const_lookups: usize,
493    /// Number of cache hits.
494    pub cache_hits: usize,
495}
496impl InferStats {
497    /// Reset all counters to zero.
498    pub fn reset(&mut self) {
499        *self = InferStats::default();
500    }
501    /// Total operations performed.
502    pub fn total_ops(&self) -> usize {
503        self.infer_calls + self.whnf_calls + self.def_eq_calls
504    }
505}
506/// A mutable reference stack for tracking the current "focus" in a tree traversal.
507#[allow(dead_code)]
508pub struct FocusStack<T> {
509    items: Vec<T>,
510}
511#[allow(dead_code)]
512impl<T> FocusStack<T> {
513    /// Creates an empty focus stack.
514    pub fn new() -> Self {
515        Self { items: Vec::new() }
516    }
517    /// Focuses on `item`.
518    pub fn focus(&mut self, item: T) {
519        self.items.push(item);
520    }
521    /// Blurs (pops) the current focus.
522    pub fn blur(&mut self) -> Option<T> {
523        self.items.pop()
524    }
525    /// Returns the current focus, or `None`.
526    pub fn current(&self) -> Option<&T> {
527        self.items.last()
528    }
529    /// Returns the focus depth.
530    pub fn depth(&self) -> usize {
531        self.items.len()
532    }
533    /// Returns `true` if there is no current focus.
534    pub fn is_empty(&self) -> bool {
535        self.items.is_empty()
536    }
537}
538/// A non-empty list (at least one element guaranteed).
539#[allow(dead_code)]
540pub struct NonEmptyVec<T> {
541    head: T,
542    tail: Vec<T>,
543}
544#[allow(dead_code)]
545impl<T> NonEmptyVec<T> {
546    /// Creates a non-empty vec with a single element.
547    pub fn singleton(val: T) -> Self {
548        Self {
549            head: val,
550            tail: Vec::new(),
551        }
552    }
553    /// Pushes an element.
554    pub fn push(&mut self, val: T) {
555        self.tail.push(val);
556    }
557    /// Returns a reference to the first element.
558    pub fn first(&self) -> &T {
559        &self.head
560    }
561    /// Returns a reference to the last element.
562    pub fn last(&self) -> &T {
563        self.tail.last().unwrap_or(&self.head)
564    }
565    /// Returns the number of elements.
566    pub fn len(&self) -> usize {
567        1 + self.tail.len()
568    }
569    /// Returns whether the collection is empty.
570    pub fn is_empty(&self) -> bool {
571        self.len() == 0
572    }
573    /// Returns all elements as a Vec.
574    pub fn to_vec(&self) -> Vec<&T> {
575        let mut v = vec![&self.head];
576        v.extend(self.tail.iter());
577        v
578    }
579}
580/// A pair of `StatSummary` values tracking before/after a transformation.
581#[allow(dead_code)]
582pub struct TransformStat {
583    before: StatSummary,
584    after: StatSummary,
585}
586#[allow(dead_code)]
587impl TransformStat {
588    /// Creates a new transform stat recorder.
589    pub fn new() -> Self {
590        Self {
591            before: StatSummary::new(),
592            after: StatSummary::new(),
593        }
594    }
595    /// Records a before value.
596    pub fn record_before(&mut self, v: f64) {
597        self.before.record(v);
598    }
599    /// Records an after value.
600    pub fn record_after(&mut self, v: f64) {
601        self.after.record(v);
602    }
603    /// Returns the mean reduction ratio (after/before).
604    pub fn mean_ratio(&self) -> Option<f64> {
605        let b = self.before.mean()?;
606        let a = self.after.mean()?;
607        if b.abs() < f64::EPSILON {
608            return None;
609        }
610        Some(a / b)
611    }
612}
613/// A token bucket rate limiter.
614#[allow(dead_code)]
615pub struct TokenBucket {
616    capacity: u64,
617    tokens: u64,
618    refill_per_ms: u64,
619    last_refill: crate::wall_clock::Instant,
620}
621#[allow(dead_code)]
622impl TokenBucket {
623    /// Creates a new token bucket.
624    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
625        Self {
626            capacity,
627            tokens: capacity,
628            refill_per_ms,
629            last_refill: crate::wall_clock::Instant::now(),
630        }
631    }
632    /// Attempts to consume `n` tokens.  Returns `true` on success.
633    pub fn try_consume(&mut self, n: u64) -> bool {
634        self.refill();
635        if self.tokens >= n {
636            self.tokens -= n;
637            true
638        } else {
639            false
640        }
641    }
642    fn refill(&mut self) {
643        let now = crate::wall_clock::Instant::now();
644        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
645        if elapsed_ms > 0 {
646            let new_tokens = elapsed_ms * self.refill_per_ms;
647            self.tokens = (self.tokens + new_tokens).min(self.capacity);
648            self.last_refill = now;
649        }
650    }
651    /// Returns the number of currently available tokens.
652    pub fn available(&self) -> u64 {
653        self.tokens
654    }
655    /// Returns the bucket capacity.
656    pub fn capacity(&self) -> u64 {
657        self.capacity
658    }
659}
660/// A flat list of substitution pairs `(from, to)`.
661#[allow(dead_code)]
662pub struct FlatSubstitution {
663    pairs: Vec<(String, String)>,
664}
665#[allow(dead_code)]
666impl FlatSubstitution {
667    /// Creates an empty substitution.
668    pub fn new() -> Self {
669        Self { pairs: Vec::new() }
670    }
671    /// Adds a pair.
672    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
673        self.pairs.push((from.into(), to.into()));
674    }
675    /// Applies all substitutions to `s` (leftmost-first order).
676    pub fn apply(&self, s: &str) -> String {
677        let mut result = s.to_string();
678        for (from, to) in &self.pairs {
679            result = result.replace(from.as_str(), to.as_str());
680        }
681        result
682    }
683    /// Returns the number of pairs.
684    pub fn len(&self) -> usize {
685        self.pairs.len()
686    }
687    /// Returns `true` if empty.
688    pub fn is_empty(&self) -> bool {
689        self.pairs.is_empty()
690    }
691}
692/// Type-level kind classification of an expression.
693#[allow(dead_code)]
694#[derive(Debug, Clone, Copy, PartialEq, Eq)]
695pub enum TypeKind {
696    /// A proposition (Sort 0 / Prop).
697    Prop,
698    /// A small type (Sort 1, i.e., Type 0).
699    Type0,
700    /// A large type (Sort n for n ≥ 2).
701    LargeType,
702    /// A universe Sort expression itself.
703    Universe,
704    /// A function type (Pi).
705    Pi,
706    /// A lambda (not a type).
707    Lambda,
708    /// An application (not directly a type).
709    Application,
710    /// A free variable.
711    FreeVar,
712    /// A constant.
713    Constant,
714    /// A literal value.
715    Literal,
716    /// Unknown or uncategorized.
717    Unknown,
718}
719/// A write-once cell.
720#[allow(dead_code)]
721pub struct WriteOnce<T> {
722    value: std::cell::Cell<Option<T>>,
723}
724#[allow(dead_code)]
725impl<T: Copy> WriteOnce<T> {
726    /// Creates an empty write-once cell.
727    pub fn new() -> Self {
728        Self {
729            value: std::cell::Cell::new(None),
730        }
731    }
732    /// Writes a value.  Returns `false` if already written.
733    pub fn write(&self, val: T) -> bool {
734        if self.value.get().is_some() {
735            return false;
736        }
737        self.value.set(Some(val));
738        true
739    }
740    /// Returns the value if written.
741    pub fn read(&self) -> Option<T> {
742        self.value.get()
743    }
744    /// Returns `true` if the value has been written.
745    pub fn is_written(&self) -> bool {
746        self.value.get().is_some()
747    }
748}
749/// Unified type checker state.
750///
751/// Owns WHNF reduction, definitional equality checking, and type inference.
752/// Mirrors LEAN 4's `type_checker` class.
753pub struct TypeChecker<'env> {
754    env: &'env Environment,
755    local_ctx: Vec<LocalDecl>,
756    reducer: Reducer,
757    def_eq_checker: DefEqChecker<'env>,
758    next_fvar: u64,
759    /// Whether to perform full checking (vs. infer-only mode).
760    check_mode: bool,
761    /// The types [`Self::infer_type`] has computed, by term — Lean's
762    /// `m_infer_type`. See [`Self::infer_type`] for what makes an entry
763    /// valid and when the table is dropped.
764    pub(super) infer_cache: crate::term_cache::TermCache<Expr>,
765}
766impl<'env> TypeChecker<'env> {
767    /// Create a new type checker for the given environment.
768    pub fn new(env: &'env Environment) -> Self {
769        Self {
770            env,
771            local_ctx: Vec::new(),
772            reducer: Reducer::new(),
773            def_eq_checker: DefEqChecker::new(env),
774            next_fvar: 0,
775            check_mode: true,
776            infer_cache: crate::term_cache::TermCache::new(),
777        }
778    }
779    /// Create a type checker in infer-only mode (skips some checks).
780    pub fn new_infer_only(env: &'env Environment) -> Self {
781        Self {
782            env,
783            local_ctx: Vec::new(),
784            reducer: Reducer::new(),
785            def_eq_checker: DefEqChecker::new(env),
786            next_fvar: 0,
787            check_mode: false,
788            infer_cache: crate::term_cache::TermCache::new(),
789        }
790    }
791    /// Create a type checker whose local context is pre-seeded with the
792    /// given free-variable types (id → type), e.g. the context a [`Reducer`]
793    /// mirrors so K-like iota reduction can type majors that mention local
794    /// FVars. Ids are installed in ascending order (later locals may depend
795    /// on earlier ones).
796    ///
797    /// Every free variable the new checker mints is distinct from every
798    /// seeded one, in BOTH of its id ranges: the checker's own locals
799    /// (`fresh_fvar`, low ids) start past the largest seeded low id, and the
800    /// binder-opening variables of its def-eq checker (ids from
801    /// `DEF_EQ_OPENED_FVAR_BASE`) start past the largest seeded id of that
802    /// range. The seeded locals of the second range are the binders the
803    /// surrounding def-eq checker has opened; a def-eq checker that restarted
804    /// at the base would open its first binder with the id of the surrounding
805    /// checker's first one and identify the two variables (see
806    /// `DefEqChecker::reserve_opened_fvars_through`).
807    pub fn with_fvar_types(
808        env: &'env Environment,
809        locals: &std::collections::HashMap<u64, Expr>,
810    ) -> Self {
811        use crate::def_eq::types::DEF_EQ_OPENED_FVAR_BASE;
812
813        let mut tc = Self::new(env);
814        let mut ids: Vec<u64> = locals.keys().copied().collect();
815        ids.sort_unstable();
816        let mut max_own: Option<u64> = None;
817        let mut max_opened: Option<u64> = None;
818        for id in ids {
819            if let Some(ty) = locals.get(&id) {
820                tc.push_local(LocalDecl {
821                    fvar: FVarId(id),
822                    name: Name::anonymous(),
823                    ty: ty.clone(),
824                    val: None,
825                });
826            }
827            // `ids` is ascending, so the last id seen in each range is its
828            // maximum.
829            if id < DEF_EQ_OPENED_FVAR_BASE {
830                max_own = Some(id);
831            } else {
832                max_opened = Some(id);
833            }
834        }
835        tc.next_fvar = max_own.map_or(0, |m| m + 1);
836        if let Some(m) = max_opened {
837            tc.def_eq_checker.reserve_opened_fvars_through(m);
838        }
839        tc
840    }
841    /// Create a type checker seeded with the locals a [`Reducer`] or def-eq
842    /// checker mirrors: [`Self::with_fvar_types`] over the variables in
843    /// scope, with both id ranges of the new checker also kept clear of every
844    /// id the mirror has EVER held.
845    ///
846    /// The mirror forgets a variable when its binder closes, so the variables
847    /// in scope are no longer all the variables the surrounding checker has
848    /// used. Minting past the ones in scope only would hand the new checker
849    /// the id of a closed binder; minting past the largest id ever bound, in
850    /// each range, gives it exactly the ids it was given when the mirror kept
851    /// every variable — distinct from all of the surrounding checker's,
852    /// whatever terms it is asked about.
853    pub(crate) fn with_local_types(
854        env: &'env Environment,
855        locals: &crate::local_types::LocalTypes,
856    ) -> Self {
857        let mut tc = Self::with_fvar_types(env, locals.live());
858        if let Some(m) = locals.max_own_id() {
859            tc.next_fvar = tc.next_fvar.max(m.saturating_add(1));
860        }
861        if let Some(m) = locals.max_opened_id() {
862            tc.def_eq_checker.reserve_opened_fvars_through(m);
863        }
864        tc
865    }
866    /// The three mirrors of this checker's local context — its reducer's,
867    /// its def-eq checker's and that checker's reducer's — and the id the
868    /// next binder the def-eq checker opens will get (for tests).
869    #[cfg(test)]
870    pub(crate) fn local_mirrors(&self) -> ([&crate::local_types::LocalTypes; 3], u64) {
871        let (def_eq, def_eq_reducer, next_opened) = self.def_eq_checker.local_types();
872        (
873            [self.reducer.local_types(), def_eq, def_eq_reducer],
874            next_opened,
875        )
876    }
877    /// Set the transparency mode.
878    ///
879    /// The types inferred so far are dropped with the reduction and def-eq
880    /// caches: in checking mode a cached type also records that the term
881    /// CHECKED, and what checks depends on what may be unfolded.
882    pub fn set_transparency(&mut self, mode: TransparencyMode) {
883        self.reducer.set_transparency(mode);
884        self.def_eq_checker.set_transparency(mode);
885        self.infer_cache.clear();
886    }
887    /// Mirror a local declaration's type into the def-eq checker and the
888    /// reducer, and drop the inferred types if the binding makes them stale:
889    /// the variable was in scope with another type, or was in scope before
890    /// and its binder has since been closed (`LocalTypes::bind`). A cached
891    /// type may rest on the type the variable had then.
892    fn mirror_local(&mut self, fvar: FVarId, ty: &Expr) {
893        self.def_eq_checker.record_fvar_type(fvar, ty.clone());
894        if self.reducer.bind_fvar_type(fvar, ty.clone()) {
895            self.infer_cache.clear();
896        }
897    }
898    /// Get the environment.
899    pub fn env(&self) -> &Environment {
900        self.env
901    }
902    /// Generate a fresh free variable.
903    pub fn fresh_fvar(&mut self, name: Name, ty: Expr) -> FVarId {
904        let fvar = FVarId(self.next_fvar);
905        self.next_fvar += 1;
906        // Mirror the local context into the def-eq checker (whose
907        // type-dependent rules — one-sided eta expansion, structure eta,
908        // proof irrelevance — must type this free variable) and into the
909        // reducer (whose K-like iota reduction must type recursor majors
910        // that mention it).
911        self.mirror_local(fvar, &ty);
912        self.local_ctx.push(LocalDecl {
913            fvar,
914            name,
915            ty,
916            val: None,
917        });
918        fvar
919    }
920    /// Generate a fresh free variable with a value (let-binding).
921    pub fn fresh_fvar_let(&mut self, name: Name, ty: Expr, val: Expr) -> FVarId {
922        let fvar = FVarId(self.next_fvar);
923        self.next_fvar += 1;
924        self.mirror_local(fvar, &ty);
925        self.local_ctx.push(LocalDecl {
926            fvar,
927            name,
928            ty,
929            val: Some(val),
930        });
931        fvar
932    }
933    /// Push a local declaration onto the context.
934    pub fn push_local(&mut self, decl: LocalDecl) {
935        self.mirror_local(decl.fvar, &decl.ty);
936        self.local_ctx.push(decl);
937    }
938    /// Pop a local declaration from the context.
939    ///
940    /// The variable leaves scope, so its type also leaves the mirrors the
941    /// def-eq checker and the reducers keep of this context (see the
942    /// kernel-private `local_types` module) — unless an earlier declaration of the same
943    /// variable is still in the context, in which case the mirrors are left
944    /// as they are.
945    pub fn pop_local(&mut self) -> Option<LocalDecl> {
946        let decl = self.local_ctx.pop()?;
947        if !self.local_ctx.iter().any(|other| other.fvar == decl.fvar) {
948            self.def_eq_checker.forget_fvar_type(decl.fvar);
949            self.reducer.forget_fvar_type(decl.fvar);
950        }
951        Some(decl)
952    }
953    /// Look up a free variable in the local context.
954    #[allow(clippy::result_large_err)]
955    fn lookup_fvar(&self, fvar: FVarId) -> Result<&LocalDecl, KernelError> {
956        self.local_ctx
957            .iter()
958            .find(|decl| decl.fvar == fvar)
959            .ok_or_else(|| KernelError::Other(format!("free variable not found: {:?}", fvar)))
960    }
961    /// Get the local context.
962    pub fn local_ctx(&self) -> &[LocalDecl] {
963        &self.local_ctx
964    }
965    /// Reduce an expression to WHNF.
966    pub fn whnf(&mut self, expr: &Expr) -> Expr {
967        self.reducer.whnf_env(expr, self.env)
968    }
969    /// Ensure an expression is a Sort, returning the level.
970    ///
971    /// Infers the type, reduces to WHNF, and checks it's a Sort.
972    #[allow(clippy::result_large_err)]
973    pub fn ensure_sort(&mut self, expr: &Expr) -> Result<Level, KernelError> {
974        let ty = self.infer_type(expr)?;
975        let ty_whnf = self.whnf(&ty);
976        match ty_whnf {
977            Expr::Sort(l) => Ok(l),
978            _ => Err(KernelError::NotASort(ty_whnf)),
979        }
980    }
981    /// Ensure an expression's type is a Pi type, returning it.
982    #[allow(clippy::result_large_err)]
983    pub fn ensure_pi(&mut self, expr: &Expr) -> Result<Expr, KernelError> {
984        let ty = self.infer_type(expr)?;
985        let ty_whnf = self.whnf(&ty);
986        if ty_whnf.is_pi() {
987            Ok(ty_whnf)
988        } else {
989            Err(KernelError::NotAFunction(ty_whnf))
990        }
991    }
992    /// Check if two expressions are definitionally equal.
993    pub fn is_def_eq(&mut self, t: &Expr, s: &Expr) -> bool {
994        self.def_eq_checker.is_def_eq(t, s)
995    }
996    /// Check that an expression has the expected type.
997    #[allow(clippy::result_large_err)]
998    pub fn check_type(
999        &mut self,
1000        expr: &Expr,
1001        inferred: &Expr,
1002        expected: &Expr,
1003    ) -> Result<(), KernelError> {
1004        if self.is_def_eq(inferred, expected) {
1005            Ok(())
1006        } else {
1007            Err(KernelError::TypeMismatch {
1008                expected: expected.clone(),
1009                got: inferred.clone(),
1010                context: format!("checking {}", expr),
1011            })
1012        }
1013    }
1014    /// [`Self::infer_type`] for a term that is not in the cache (the
1015    /// caching entry point is in the `cache` module of this directory).
1016    #[allow(clippy::result_large_err)]
1017    pub(super) fn infer_type_uncached(&mut self, expr: &Expr) -> Result<Expr, KernelError> {
1018        match expr {
1019            Expr::Sort(l) => Ok(Expr::Sort(Level::succ(l.clone()))),
1020            Expr::BVar(idx) => Err(KernelError::UnboundVariable(*idx)),
1021            Expr::FVar(fvar) => {
1022                let decl = self.lookup_fvar(*fvar)?;
1023                Ok(decl.ty.clone())
1024            }
1025            Expr::Const(name, levels) => self.infer_const(name, levels),
1026            Expr::App(f, a) => self.infer_app(f, a),
1027            Expr::Lam(bi, name, ty, body) => {
1028                if self.check_mode {
1029                    self.ensure_sort(ty)?;
1030                }
1031                let fvar = self.fresh_fvar(name.clone(), (**ty).clone());
1032                let body_open = instantiate(body, &Expr::FVar(fvar));
1033                let body_ty = self.infer_type(&body_open)?;
1034                self.pop_local();
1035                let body_ty_closed = abstract_expr(&body_ty, fvar);
1036                Ok(Expr::Pi(
1037                    *bi,
1038                    name.clone(),
1039                    ty.clone(),
1040                    Node::new(body_ty_closed),
1041                ))
1042            }
1043            Expr::Pi(_, _, dom, cod) => {
1044                let dom_sort = self.ensure_sort(dom)?;
1045                let fvar = self.fresh_fvar(Name::str("_"), (**dom).clone());
1046                let cod_open = instantiate(cod, &Expr::FVar(fvar));
1047                let cod_sort = self.ensure_sort(&cod_open)?;
1048                self.pop_local();
1049                // Build the Pi's sort with the SMART imax constructor, matching
1050                // Lean's kernel `mk_imax`. This simplifies `imax(u, u) -> u`,
1051                // `imax(_, 0) -> 0`, `imax(0, v) -> v`, etc. so the re-inferred
1052                // sort lands in the same canonical shape the elaborator stored in
1053                // the export; without it, e.g. `Sort u -> Sort u` re-infers to
1054                // `Sort (imax u u)` and (before the def-eq fix) mass-fails.
1055                Ok(Expr::Sort(crate::level::mk_imax(dom_sort, cod_sort)))
1056            }
1057            Expr::Let(_, ty, val, body) => {
1058                if self.check_mode {
1059                    self.ensure_sort(ty)?;
1060                    let val_ty = self.infer_type(val)?;
1061                    self.check_type(val, &val_ty, ty)?;
1062                }
1063                let body_inst = instantiate(body, val);
1064                self.infer_type(&body_inst)
1065            }
1066            Expr::Lit(Literal::Nat(_)) => Ok(Expr::Const(Name::str("Nat"), vec![])),
1067            Expr::Lit(Literal::Str(_)) => Ok(Expr::Const(Name::str("String"), vec![])),
1068            Expr::Proj(struct_name, idx, struct_expr) => {
1069                self.infer_proj(struct_name, *idx, struct_expr)
1070            }
1071        }
1072    }
1073    /// Infer the type of a constant reference.
1074    ///
1075    /// The provided universe-level list must have EXACTLY the constant's
1076    /// universe-parameter arity (S7). Lean's kernel requires this; the previous
1077    /// code returned the uninstantiated type when either list was empty, which
1078    /// let a polymorphic constant's `Param(u)` leak into a context that declares
1079    /// its own `u` (a capture / soundness hole). We now instantiate only on an
1080    /// exact-arity match and hard-error otherwise.
1081    #[allow(clippy::result_large_err)]
1082    fn infer_const(&self, name: &Name, levels: &[Level]) -> Result<Expr, KernelError> {
1083        if let Some(ci) = self.env.find(name) {
1084            return Self::instantiate_const_type(name, ci.level_params(), ci.ty(), levels);
1085        }
1086        let decl = self
1087            .env
1088            .get(name)
1089            .ok_or_else(|| KernelError::UnknownConstant(name.clone()))?;
1090        Self::instantiate_const_type(name, decl.univ_params(), decl.ty(), levels)
1091    }
1092    /// Shared arity check + universe-parameter instantiation for a constant
1093    /// reference, used by both the `ConstantInfo` and legacy `Declaration`
1094    /// lookup paths.
1095    #[allow(clippy::result_large_err)]
1096    fn instantiate_const_type(
1097        name: &Name,
1098        params: &[Name],
1099        ty: &Expr,
1100        levels: &[Level],
1101    ) -> Result<Expr, KernelError> {
1102        if params.len() != levels.len() {
1103            return Err(KernelError::Other(format!(
1104                "universe parameter count mismatch for {}: expected {}, got {}",
1105                name,
1106                params.len(),
1107                levels.len()
1108            )));
1109        }
1110        if params.is_empty() {
1111            // No universe polymorphism: no substitution needed.
1112            return Ok(ty.clone());
1113        }
1114        Ok(instantiate_type_lparams(ty, params, levels))
1115    }
1116    /// Infer the type of a function application.
1117    #[allow(clippy::result_large_err)]
1118    fn infer_app(&mut self, f: &Expr, a: &Expr) -> Result<Expr, KernelError> {
1119        let f_ty = self.infer_type(f)?;
1120        let f_ty_whnf = self.whnf(&f_ty);
1121        match &f_ty_whnf {
1122            Expr::Pi(_, _, dom, cod) => {
1123                if self.check_mode {
1124                    let a_ty = self.infer_type(a)?;
1125                    self.check_type(a, &a_ty, dom)?;
1126                }
1127                Ok(instantiate(cod, a))
1128            }
1129            _ => Err(KernelError::NotAFunction(f_ty_whnf)),
1130        }
1131    }
1132    /// Infer the type of a projection. Which inductive type it may name —
1133    /// any with exactly one constructor, recursive and indexed ones
1134    /// included, as in Lean's `infer_proj` — is decided in `proj_target`.
1135    #[allow(clippy::result_large_err)]
1136    fn infer_proj(
1137        &mut self,
1138        struct_name: &Name,
1139        idx: u32,
1140        struct_expr: &Expr,
1141    ) -> Result<Expr, KernelError> {
1142        let (ind_val, ctor_val) = proj_target(self.env, struct_name, idx)?;
1143        let struct_ty = self.infer_type(struct_expr)?;
1144        self.infer_proj_field_type(ind_val, ctor_val, idx, struct_expr, &struct_ty)
1145    }
1146    /// Compute the type of a projection field.
1147    ///
1148    /// Telescopes through the constructor's Pi-type, instantiating:
1149    /// 1. Universe parameters from the struct type's head constant
1150    /// 2. Inductive parameters from the struct type's applied arguments
1151    ///    (the leading ones: the indices that follow are arguments of the
1152    ///    type, not of the constructor)
1153    /// 3. Preceding fields with `Proj(S, j, struct_expr)` for `j < idx`
1154    ///
1155    /// Returns the domain of the `idx`-th Pi binder. Any mismatch — the
1156    /// operand's type not being the structure type, an argument-count
1157    /// mismatch, or the constructor telescope running short — is a typed
1158    /// error rather than a fabricated type.
1159    #[allow(clippy::result_large_err)]
1160    fn infer_proj_field_type(
1161        &mut self,
1162        ind_val: &InductiveVal,
1163        ctor_val: &ConstructorVal,
1164        idx: u32,
1165        struct_expr: &Expr,
1166        struct_ty: &Expr,
1167    ) -> Result<Expr, KernelError> {
1168        let ctor_ty = ctor_val.common.ty.clone();
1169        let struct_ty_whnf = self.whnf(struct_ty);
1170        let levels: Vec<Level> = match get_app_fn(&struct_ty_whnf) {
1171            Expr::Const(name, lvls) if *name == ind_val.common.name => lvls.clone(),
1172            _ => {
1173                return Err(KernelError::Other(format!(
1174                    "invalid projection: expected an element of {}, but the operand has type {}",
1175                    ind_val.common.name, struct_ty_whnf
1176                )))
1177            }
1178        };
1179        let level_params = &ind_val.common.level_params;
1180        let mut cur_ty = if level_params.is_empty() || levels.is_empty() {
1181            ctor_ty
1182        } else {
1183            instantiate_type_lparams(&ctor_ty, level_params, &levels)
1184        };
1185        let struct_args: Vec<Expr> = get_app_args(&struct_ty_whnf).into_iter().cloned().collect();
1186        let Some(params) = proj_type_params(ind_val, &struct_args) else {
1187            return Err(KernelError::Other(format!(
1188                "invalid projection: {} expects {} parameters and {} indices, \
1189                 but the operand type {} supplies {} arguments",
1190                ind_val.common.name,
1191                ind_val.num_params,
1192                ind_val.num_indices,
1193                struct_ty_whnf,
1194                struct_args.len()
1195            )));
1196        };
1197        for param in params {
1198            match cur_ty {
1199                Expr::Pi(_, _, _, body) => {
1200                    cur_ty = instantiate(&body, param);
1201                }
1202                _ => {
1203                    return Err(KernelError::Other(format!(
1204                        "invalid projection: constructor {} telescope ended while \
1205                         instantiating the parameters of {}",
1206                        ctor_val.common.name, ind_val.common.name
1207                    )))
1208                }
1209            }
1210        }
1211        // No data out of a proof (Lean's `infer_proj`; see `proj_prop`): of
1212        // a proposition, the projected field and every earlier field a
1213        // later field's type depends on must be propositions themselves.
1214        // The sort is that of `S ps is`: read past the indices too.
1215        let struct_is_prop = self.proj_struct_is_prop(ind_val, &levels, &struct_args)?;
1216        for j in 0..idx {
1217            match cur_ty {
1218                Expr::Pi(_, _, dom, body) => {
1219                    if struct_is_prop && has_loose_bvars(&body) {
1220                        self.ensure_prop_field(ind_val, j, &dom)?;
1221                    }
1222                    let field_val = Expr::Proj(
1223                        ind_val.common.name.clone(),
1224                        j,
1225                        Node::new(struct_expr.clone()),
1226                    );
1227                    cur_ty = instantiate(&body, &field_val);
1228                }
1229                _ => {
1230                    return Err(KernelError::Other(format!(
1231                        "invalid projection: constructor {} telescope ended at field {} \
1232                         while computing field {} of {}",
1233                        ctor_val.common.name, j, idx, ind_val.common.name
1234                    )))
1235                }
1236            }
1237        }
1238        match cur_ty {
1239            Expr::Pi(_, _, dom, _) => {
1240                if struct_is_prop {
1241                    self.ensure_prop_field(ind_val, idx, &dom)?;
1242                }
1243                Ok((*dom).clone())
1244            }
1245            _ => Err(KernelError::Other(format!(
1246                "invalid projection: constructor {} telescope has no field {} for {}",
1247                ctor_val.common.name, idx, ind_val.common.name
1248            ))),
1249        }
1250    }
1251    /// Check if an expression is a proposition (has type Prop).
1252    pub fn is_prop(&mut self, expr: &Expr) -> bool {
1253        if let Ok(ty) = self.infer_type(expr) {
1254            let ty_whnf = self.whnf(&ty);
1255            matches!(ty_whnf, Expr::Sort(l) if l.is_zero())
1256        } else {
1257            false
1258        }
1259    }
1260    /// Check if an expression is a proof (its type is a proposition).
1261    pub fn is_proof(&mut self, expr: &Expr) -> bool {
1262        if let Ok(ty) = self.infer_type(expr) {
1263            self.is_prop(&ty)
1264        } else {
1265            false
1266        }
1267    }
1268    /// Check if an expression is a type (has type Sort u for some u).
1269    pub fn is_type(&mut self, expr: &Expr) -> bool {
1270        if let Ok(ty) = self.infer_type(expr) {
1271            let ty_whnf = self.whnf(&ty);
1272            ty_whnf.is_sort()
1273        } else {
1274            false
1275        }
1276    }
1277    /// Get the universe level of a type expression.
1278    #[allow(clippy::result_large_err)]
1279    pub fn get_level(&mut self, expr: &Expr) -> Result<Level, KernelError> {
1280        let ty = self.infer_type(expr)?;
1281        let ty_whnf = self.whnf(&ty);
1282        match ty_whnf {
1283            Expr::Sort(l) => Ok(l),
1284            _ => Err(KernelError::NotASort(ty_whnf)),
1285        }
1286    }
1287    /// Try to unfold a constant definition.
1288    pub fn unfold_definition(&mut self, expr: &Expr) -> Option<Expr> {
1289        let head = get_app_fn(expr);
1290        if let Expr::Const(name, levels) = head {
1291            if let Some(ci) = self.env.find(name) {
1292                if let Some(val) = ci.value() {
1293                    let hint = ci.reducibility_hint();
1294                    if hint.should_unfold() {
1295                        let val_inst = if ci.level_params().is_empty() || levels.is_empty() {
1296                            val.clone()
1297                        } else {
1298                            instantiate_type_lparams(val, ci.level_params(), levels)
1299                        };
1300                        let args: Vec<Expr> = get_app_args(expr).into_iter().cloned().collect();
1301                        return Some(mk_app(val_inst, &args));
1302                    }
1303                }
1304            }
1305        }
1306        None
1307    }
1308}
1309impl<'env> TypeChecker<'env> {
1310    /// Infer the type of a chain of applications at once.
1311    #[allow(clippy::result_large_err)]
1312    pub fn infer_app_chain(
1313        &mut self,
1314        f: &Expr,
1315        args: &[Expr],
1316    ) -> Result<Expr, crate::error::KernelError> {
1317        let mut ty = self.infer_type(f)?;
1318        for arg in args {
1319            let whnf = self.whnf(&ty);
1320            match whnf {
1321                Expr::Pi(_, _, dom, cod) => {
1322                    if self.check_mode {
1323                        let arg_ty = self.infer_type(arg)?;
1324                        if !self.is_def_eq(&arg_ty, &dom) {
1325                            return Err(crate::error::KernelError::TypeMismatch {
1326                                expected: (*dom).clone(),
1327                                got: arg_ty,
1328                                context: "application argument".to_string(),
1329                            });
1330                        }
1331                    }
1332                    ty = instantiate(&cod, arg);
1333                }
1334                other => return Err(crate::error::KernelError::NotAFunction(other)),
1335            }
1336        }
1337        Ok(ty)
1338    }
1339    /// Telescope a type, instantiating leading Pi binders with fresh free variables.
1340    pub fn telescope_type(&mut self, ty: &Expr, max_pis: usize) -> (Vec<LocalDecl>, Expr) {
1341        let mut fvars = Vec::new();
1342        let mut current = ty.clone();
1343        for _ in 0..max_pis {
1344            let whnf = self.whnf(&current);
1345            match whnf {
1346                Expr::Pi(bi, name, dom, cod) => {
1347                    let fvar_id = self.fresh_fvar(name.clone(), (*dom).clone());
1348                    let decl = LocalDecl {
1349                        fvar: fvar_id,
1350                        name,
1351                        ty: (*dom).clone(),
1352                        val: None,
1353                    };
1354                    let body = instantiate(&cod, &Expr::FVar(fvar_id));
1355                    fvars.push(decl);
1356                    current = body;
1357                    let _ = bi;
1358                }
1359                _ => break,
1360            }
1361        }
1362        (fvars, current)
1363    }
1364    /// Close a type over a list of free variables into Pi types.
1365    pub fn close_type_over_fvars(&mut self, fvars: &[LocalDecl], ty: Expr) -> Expr {
1366        let mut result = ty;
1367        for decl in fvars.iter().rev() {
1368            result = abstract_expr(&result, decl.fvar);
1369            result = Expr::Pi(
1370                crate::BinderInfo::Default,
1371                decl.name.clone(),
1372                Node::new(decl.ty.clone()),
1373                Node::new(result),
1374            );
1375        }
1376        result
1377    }
1378    /// Close a term over free variables into lambdas.
1379    pub fn close_term_over_fvars(&mut self, fvars: &[LocalDecl], term: Expr) -> Expr {
1380        let mut result = term;
1381        for decl in fvars.iter().rev() {
1382            result = abstract_expr(&result, decl.fvar);
1383            result = Expr::Lam(
1384                crate::BinderInfo::Default,
1385                decl.name.clone(),
1386                Node::new(decl.ty.clone()),
1387                Node::new(result),
1388            );
1389        }
1390        result
1391    }
1392    /// Check that `expr` has type `expected`.
1393    #[allow(clippy::result_large_err)]
1394    pub fn check(&mut self, expr: &Expr, expected: &Expr) -> Result<(), crate::error::KernelError> {
1395        let inferred = self.infer_type(expr)?;
1396        if self.is_def_eq(&inferred, expected) {
1397            Ok(())
1398        } else {
1399            Err(crate::error::KernelError::TypeMismatch {
1400                expected: expected.clone(),
1401                got: inferred,
1402                context: format!("check({:?})", expr),
1403            })
1404        }
1405    }
1406    /// Try to check; return true if the type matches.
1407    pub fn try_check(&mut self, expr: &Expr, expected: &Expr) -> bool {
1408        if let Ok(inferred) = self.infer_type(expr) {
1409            self.is_def_eq(&inferred, expected)
1410        } else {
1411            false
1412        }
1413    }
1414    /// Get the number of Pi binders in the WHNF of a type.
1415    pub fn count_pi_binders(&mut self, ty: &Expr) -> usize {
1416        let mut count = 0;
1417        let mut current = ty.clone();
1418        loop {
1419            let whnf = self.whnf(&current);
1420            if let Expr::Pi(_, _, _, cod) = whnf {
1421                count += 1;
1422                current = (*cod).clone();
1423            } else {
1424                break;
1425            }
1426        }
1427        count
1428    }
1429    /// Verify a declaration's type and optional value.
1430    #[allow(clippy::result_large_err)]
1431    pub fn verify_declaration(
1432        &mut self,
1433        name: &Name,
1434        ty: &Expr,
1435        val: Option<&Expr>,
1436    ) -> Result<(), crate::error::KernelError> {
1437        self.ensure_sort(ty)?;
1438        if let Some(v) = val {
1439            let v_ty = self.infer_type(v)?;
1440            if !self.is_def_eq(&v_ty, ty) {
1441                return Err(crate::error::KernelError::TypeMismatch {
1442                    expected: ty.clone(),
1443                    got: v_ty,
1444                    context: format!("verifying declaration {}", name),
1445                });
1446            }
1447        }
1448        Ok(())
1449    }
1450    /// Normalize an expression (full normal form).
1451    pub fn normalize(&mut self, expr: &Expr) -> Expr {
1452        let whnf = self.whnf(expr);
1453        match &whnf {
1454            Expr::App(f, a) => {
1455                let f_norm = self.normalize(f);
1456                let a_norm = self.normalize(a);
1457                Expr::App(Node::new(f_norm), Node::new(a_norm))
1458            }
1459            Expr::Lam(bi, name, ty, body) => {
1460                let ty_norm = self.normalize(ty);
1461                let body_norm = self.normalize(body);
1462                Expr::Lam(*bi, name.clone(), Node::new(ty_norm), Node::new(body_norm))
1463            }
1464            Expr::Pi(bi, name, ty, body) => {
1465                let ty_norm = self.normalize(ty);
1466                let body_norm = self.normalize(body);
1467                Expr::Pi(*bi, name.clone(), Node::new(ty_norm), Node::new(body_norm))
1468            }
1469            Expr::Let(name, ty, val, body) => {
1470                let ty_norm = self.normalize(ty);
1471                let val_norm = self.normalize(val);
1472                let body_norm = self.normalize(body);
1473                Expr::Let(
1474                    name.clone(),
1475                    Node::new(ty_norm),
1476                    Node::new(val_norm),
1477                    Node::new(body_norm),
1478                )
1479            }
1480            Expr::Proj(sname, idx, inner) => {
1481                let inner_norm = self.normalize(inner);
1482                Expr::Proj(sname.clone(), *idx, Node::new(inner_norm))
1483            }
1484            _ => whnf,
1485        }
1486    }
1487    /// Return the arity (number of Pi binders) in the type of a constant.
1488    pub fn constant_arity(&mut self, name: &Name) -> Option<usize> {
1489        let ty = if let Some(ci) = self.env.find(name) {
1490            ci.ty().clone()
1491        } else {
1492            self.env.get(name)?.ty().clone()
1493        };
1494        Some(self.count_pi_binders(&ty))
1495    }
1496    /// Check if two universe levels are definitionally equal.
1497    ///
1498    /// Delegates to the complete `level::is_equivalent` decision procedure
1499    /// (previously this was an essentially-syntactic stub that only recognised
1500    /// literally-equal or both-zero levels — a trap for callers expecting real
1501    /// universe defeq).
1502    pub fn is_level_eq(&self, l1: &Level, l2: &Level) -> bool {
1503        crate::level::is_equivalent(l1, l2)
1504    }
1505}
1506/// A simple key-value store backed by a sorted Vec for small maps.
1507#[allow(dead_code)]
1508pub struct SmallMap<K: Ord + Clone, V: Clone> {
1509    entries: Vec<(K, V)>,
1510}
1511#[allow(dead_code)]
1512impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1513    /// Creates a new empty small map.
1514    pub fn new() -> Self {
1515        Self {
1516            entries: Vec::new(),
1517        }
1518    }
1519    /// Inserts or replaces the value for `key`.
1520    pub fn insert(&mut self, key: K, val: V) {
1521        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1522            Ok(i) => self.entries[i].1 = val,
1523            Err(i) => self.entries.insert(i, (key, val)),
1524        }
1525    }
1526    /// Returns the value for `key`, or `None`.
1527    pub fn get(&self, key: &K) -> Option<&V> {
1528        self.entries
1529            .binary_search_by_key(&key, |(k, _)| k)
1530            .ok()
1531            .map(|i| &self.entries[i].1)
1532    }
1533    /// Returns the number of entries.
1534    pub fn len(&self) -> usize {
1535        self.entries.len()
1536    }
1537    /// Returns `true` if empty.
1538    pub fn is_empty(&self) -> bool {
1539        self.entries.is_empty()
1540    }
1541    /// Returns all keys.
1542    pub fn keys(&self) -> Vec<&K> {
1543        self.entries.iter().map(|(k, _)| k).collect()
1544    }
1545    /// Returns all values.
1546    pub fn values(&self) -> Vec<&V> {
1547        self.entries.iter().map(|(_, v)| v).collect()
1548    }
1549}
1550/// A versioned record that stores a history of values.
1551#[allow(dead_code)]
1552pub struct VersionedRecord<T: Clone> {
1553    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
1554    earlier: Vec<T>,
1555    /// The latest value (version `earlier.len()`), declared last so it drops last.
1556    current: T,
1557}
1558#[allow(dead_code)]
1559impl<T: Clone> VersionedRecord<T> {
1560    /// Creates a new record with an initial value.
1561    pub fn new(initial: T) -> Self {
1562        Self {
1563            earlier: Vec::new(),
1564            current: initial,
1565        }
1566    }
1567    /// Updates the record with a new version.
1568    pub fn update(&mut self, val: T) {
1569        let previous = std::mem::replace(&mut self.current, val);
1570        self.earlier.push(previous);
1571    }
1572    /// Returns the current (latest) value.
1573    pub fn current(&self) -> &T {
1574        &self.current
1575    }
1576    /// Returns the value at version `n` (0-indexed), or `None`.
1577    pub fn at_version(&self, n: usize) -> Option<&T> {
1578        match self.earlier.get(n) {
1579            Some(value) => Some(value),
1580            None if n == self.earlier.len() => Some(&self.current),
1581            None => None,
1582        }
1583    }
1584    /// Returns the version number of the current value.
1585    pub fn version(&self) -> usize {
1586        self.earlier.len()
1587    }
1588    /// Returns `true` if more than one version exists.
1589    pub fn has_history(&self) -> bool {
1590        !self.earlier.is_empty()
1591    }
1592}
1593/// A simple LRU-style type inference cache.
1594///
1595/// In practice, this is a bounded list of recent (expr → type) pairs.
1596#[allow(dead_code)]
1597pub struct InferCache {
1598    entries: Vec<InferCacheEntry>,
1599    capacity: usize,
1600}
1601impl InferCache {
1602    /// Create a new cache with a given capacity.
1603    #[allow(dead_code)]
1604    pub fn new(capacity: usize) -> Self {
1605        Self {
1606            entries: Vec::with_capacity(capacity),
1607            capacity,
1608        }
1609    }
1610    /// Look up a cached type for an expression.
1611    #[allow(dead_code)]
1612    pub fn get(&self, expr: &Expr) -> Option<&Expr> {
1613        self.entries
1614            .iter()
1615            .rev()
1616            .find(|e| &e.expr == expr)
1617            .map(|e| &e.ty)
1618    }
1619    /// Insert a new (expr, type) pair.
1620    #[allow(dead_code)]
1621    pub fn insert(&mut self, expr: Expr, ty: Expr) {
1622        if self.entries.len() >= self.capacity {
1623            self.entries.remove(0);
1624        }
1625        self.entries.push(InferCacheEntry { expr, ty });
1626    }
1627    /// Clear the cache.
1628    #[allow(dead_code)]
1629    pub fn clear(&mut self) {
1630        self.entries.clear();
1631    }
1632    /// Number of cached entries.
1633    #[allow(dead_code)]
1634    pub fn len(&self) -> usize {
1635        self.entries.len()
1636    }
1637    /// Whether the cache is empty.
1638    #[allow(dead_code)]
1639    pub fn is_empty(&self) -> bool {
1640        self.entries.is_empty()
1641    }
1642}
1643/// A fixed-size sliding window that computes a running sum.
1644#[allow(dead_code)]
1645pub struct SlidingSum {
1646    window: Vec<f64>,
1647    capacity: usize,
1648    pos: usize,
1649    sum: f64,
1650    count: usize,
1651}
1652#[allow(dead_code)]
1653impl SlidingSum {
1654    /// Creates a sliding sum with the given window size.
1655    pub fn new(capacity: usize) -> Self {
1656        Self {
1657            window: vec![0.0; capacity],
1658            capacity,
1659            pos: 0,
1660            sum: 0.0,
1661            count: 0,
1662        }
1663    }
1664    /// Adds a value to the window, removing the oldest if full.
1665    pub fn push(&mut self, val: f64) {
1666        let oldest = self.window[self.pos];
1667        self.sum -= oldest;
1668        self.sum += val;
1669        self.window[self.pos] = val;
1670        self.pos = (self.pos + 1) % self.capacity;
1671        if self.count < self.capacity {
1672            self.count += 1;
1673        }
1674    }
1675    /// Returns the current window sum.
1676    pub fn sum(&self) -> f64 {
1677        self.sum
1678    }
1679    /// Returns the window mean, or `None` if empty.
1680    pub fn mean(&self) -> Option<f64> {
1681        if self.count == 0 {
1682            None
1683        } else {
1684            Some(self.sum / self.count as f64)
1685        }
1686    }
1687    /// Returns the current window size (number of valid elements).
1688    pub fn count(&self) -> usize {
1689        self.count
1690    }
1691}
1692/// Local declaration (free variable with type).
1693#[derive(Clone, Debug)]
1694pub struct LocalDecl {
1695    /// Free variable ID
1696    pub fvar: FVarId,
1697    /// Name (for pretty-printing)
1698    pub name: Name,
1699    /// Type of this variable
1700    pub ty: Expr,
1701    /// Optional value (for let-bound variables)
1702    pub val: Option<Expr>,
1703}
1704/// A pool of reusable string buffers.
1705#[allow(dead_code)]
1706pub struct StringPool {
1707    free: Vec<String>,
1708}
1709#[allow(dead_code)]
1710impl StringPool {
1711    /// Creates a new empty string pool.
1712    pub fn new() -> Self {
1713        Self { free: Vec::new() }
1714    }
1715    /// Takes a string from the pool (may be empty).
1716    pub fn take(&mut self) -> String {
1717        self.free.pop().unwrap_or_default()
1718    }
1719    /// Returns a string to the pool.
1720    pub fn give(&mut self, mut s: String) {
1721        s.clear();
1722        self.free.push(s);
1723    }
1724    /// Returns the number of free strings in the pool.
1725    pub fn free_count(&self) -> usize {
1726        self.free.len()
1727    }
1728}
1729/// A simple decision tree node for rule dispatching.
1730#[allow(dead_code)]
1731#[allow(missing_docs)]
1732pub enum DecisionNode {
1733    /// A leaf with an action string.
1734    Leaf(String),
1735    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
1736    Branch {
1737        key: String,
1738        val: String,
1739        yes_branch: Box<DecisionNode>,
1740        no_branch: Box<DecisionNode>,
1741    },
1742}
1743#[allow(dead_code)]
1744impl DecisionNode {
1745    /// Evaluates the decision tree with the given context.
1746    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
1747        match self {
1748            DecisionNode::Leaf(action) => action.as_str(),
1749            DecisionNode::Branch {
1750                key,
1751                val,
1752                yes_branch,
1753                no_branch,
1754            } => {
1755                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
1756                if actual == val.as_str() {
1757                    yes_branch.evaluate(ctx)
1758                } else {
1759                    no_branch.evaluate(ctx)
1760                }
1761            }
1762        }
1763    }
1764    /// Returns the depth of the decision tree.
1765    pub fn depth(&self) -> usize {
1766        match self {
1767            DecisionNode::Leaf(_) => 0,
1768            DecisionNode::Branch {
1769                yes_branch,
1770                no_branch,
1771                ..
1772            } => 1 + yes_branch.depth().max(no_branch.depth()),
1773        }
1774    }
1775}
1776/// A generic counter that tracks min/max/sum for statistical summaries.
1777#[allow(dead_code)]
1778pub struct StatSummary {
1779    count: u64,
1780    sum: f64,
1781    min: f64,
1782    max: f64,
1783}
1784#[allow(dead_code)]
1785impl StatSummary {
1786    /// Creates an empty summary.
1787    pub fn new() -> Self {
1788        Self {
1789            count: 0,
1790            sum: 0.0,
1791            min: f64::INFINITY,
1792            max: f64::NEG_INFINITY,
1793        }
1794    }
1795    /// Records a sample.
1796    pub fn record(&mut self, val: f64) {
1797        self.count += 1;
1798        self.sum += val;
1799        if val < self.min {
1800            self.min = val;
1801        }
1802        if val > self.max {
1803            self.max = val;
1804        }
1805    }
1806    /// Returns the mean, or `None` if no samples.
1807    pub fn mean(&self) -> Option<f64> {
1808        if self.count == 0 {
1809            None
1810        } else {
1811            Some(self.sum / self.count as f64)
1812        }
1813    }
1814    /// Returns the minimum, or `None` if no samples.
1815    pub fn min(&self) -> Option<f64> {
1816        if self.count == 0 {
1817            None
1818        } else {
1819            Some(self.min)
1820        }
1821    }
1822    /// Returns the maximum, or `None` if no samples.
1823    pub fn max(&self) -> Option<f64> {
1824        if self.count == 0 {
1825            None
1826        } else {
1827            Some(self.max)
1828        }
1829    }
1830    /// Returns the count of recorded samples.
1831    pub fn count(&self) -> u64 {
1832        self.count
1833    }
1834}
1835/// A label set for a graph node.
1836#[allow(dead_code)]
1837pub struct LabelSet {
1838    labels: Vec<String>,
1839}
1840#[allow(dead_code)]
1841impl LabelSet {
1842    /// Creates a new empty label set.
1843    pub fn new() -> Self {
1844        Self { labels: Vec::new() }
1845    }
1846    /// Adds a label (deduplicates).
1847    pub fn add(&mut self, label: impl Into<String>) {
1848        let s = label.into();
1849        if !self.labels.contains(&s) {
1850            self.labels.push(s);
1851        }
1852    }
1853    /// Returns `true` if `label` is present.
1854    pub fn has(&self, label: &str) -> bool {
1855        self.labels.iter().any(|l| l == label)
1856    }
1857    /// Returns the count of labels.
1858    pub fn count(&self) -> usize {
1859        self.labels.len()
1860    }
1861    /// Returns all labels.
1862    pub fn all(&self) -> &[String] {
1863        &self.labels
1864    }
1865}
1866/// A reusable scratch buffer for path computations.
1867#[allow(dead_code)]
1868pub struct PathBuf {
1869    components: Vec<String>,
1870}
1871#[allow(dead_code)]
1872impl PathBuf {
1873    /// Creates a new empty path buffer.
1874    pub fn new() -> Self {
1875        Self {
1876            components: Vec::new(),
1877        }
1878    }
1879    /// Pushes a component.
1880    pub fn push(&mut self, comp: impl Into<String>) {
1881        self.components.push(comp.into());
1882    }
1883    /// Pops the last component.
1884    pub fn pop(&mut self) {
1885        self.components.pop();
1886    }
1887    /// Returns the current path as a `/`-separated string.
1888    pub fn as_str(&self) -> String {
1889        self.components.join("/")
1890    }
1891    /// Returns the depth of the path.
1892    pub fn depth(&self) -> usize {
1893        self.components.len()
1894    }
1895    /// Clears the path.
1896    pub fn clear(&mut self) {
1897        self.components.clear();
1898    }
1899}
1900/// A type-erased function pointer with arity tracking.
1901#[allow(dead_code)]
1902pub struct RawFnPtr {
1903    /// The raw function pointer (stored as usize for type erasure).
1904    ptr: usize,
1905    arity: usize,
1906    name: String,
1907}
1908#[allow(dead_code)]
1909impl RawFnPtr {
1910    /// Creates a new raw function pointer descriptor.
1911    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1912        Self {
1913            ptr,
1914            arity,
1915            name: name.into(),
1916        }
1917    }
1918    /// Returns the arity.
1919    pub fn arity(&self) -> usize {
1920        self.arity
1921    }
1922    /// Returns the name.
1923    pub fn name(&self) -> &str {
1924        &self.name
1925    }
1926    /// Returns the raw pointer value.
1927    pub fn raw(&self) -> usize {
1928        self.ptr
1929    }
1930}
1931/// A tagged union for representing a simple two-case discriminated union.
1932#[allow(dead_code)]
1933pub enum Either2<A, B> {
1934    /// The first alternative.
1935    First(A),
1936    /// The second alternative.
1937    Second(B),
1938}
1939#[allow(dead_code)]
1940impl<A, B> Either2<A, B> {
1941    /// Returns `true` if this is the first alternative.
1942    pub fn is_first(&self) -> bool {
1943        matches!(self, Either2::First(_))
1944    }
1945    /// Returns `true` if this is the second alternative.
1946    pub fn is_second(&self) -> bool {
1947        matches!(self, Either2::Second(_))
1948    }
1949    /// Returns the first value if present.
1950    pub fn first(self) -> Option<A> {
1951        match self {
1952            Either2::First(a) => Some(a),
1953            _ => None,
1954        }
1955    }
1956    /// Returns the second value if present.
1957    pub fn second(self) -> Option<B> {
1958        match self {
1959            Either2::Second(b) => Some(b),
1960            _ => None,
1961        }
1962    }
1963    /// Maps over the first alternative.
1964    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
1965        match self {
1966            Either2::First(a) => Either2::First(f(a)),
1967            Either2::Second(b) => Either2::Second(b),
1968        }
1969    }
1970}