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