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