Skip to main content

oxilean_kernel/def_eq/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::equiv_manager::EquivManager;
7use crate::expr_util::{get_app_args, get_app_fn, has_loose_bvar};
8use crate::instantiate::instantiate_type_lparams;
9use crate::level;
10use crate::reduce::{Reducer, ReducibilityHint, TransparencyMode};
11use crate::subst::instantiate;
12use crate::{Environment, Expr, Level};
13use std::collections::HashMap;
14
15/// A tagged union for representing a simple two-case discriminated union.
16#[allow(dead_code)]
17pub enum Either2<A, B> {
18    /// The first alternative.
19    First(A),
20    /// The second alternative.
21    Second(B),
22}
23#[allow(dead_code)]
24impl<A, B> Either2<A, B> {
25    /// Returns `true` if this is the first alternative.
26    pub fn is_first(&self) -> bool {
27        matches!(self, Either2::First(_))
28    }
29    /// Returns `true` if this is the second alternative.
30    pub fn is_second(&self) -> bool {
31        matches!(self, Either2::Second(_))
32    }
33    /// Returns the first value if present.
34    pub fn first(self) -> Option<A> {
35        match self {
36            Either2::First(a) => Some(a),
37            _ => None,
38        }
39    }
40    /// Returns the second value if present.
41    pub fn second(self) -> Option<B> {
42        match self {
43            Either2::Second(b) => Some(b),
44            _ => None,
45        }
46    }
47    /// Maps over the first alternative.
48    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
49        match self {
50            Either2::First(a) => Either2::First(f(a)),
51            Either2::Second(b) => Either2::Second(b),
52        }
53    }
54}
55/// A mutable reference stack for tracking the current "focus" in a tree traversal.
56#[allow(dead_code)]
57pub struct FocusStack<T> {
58    items: Vec<T>,
59}
60#[allow(dead_code)]
61impl<T> FocusStack<T> {
62    /// Creates an empty focus stack.
63    pub fn new() -> Self {
64        Self { items: Vec::new() }
65    }
66    /// Focuses on `item`.
67    pub fn focus(&mut self, item: T) {
68        self.items.push(item);
69    }
70    /// Blurs (pops) the current focus.
71    pub fn blur(&mut self) -> Option<T> {
72        self.items.pop()
73    }
74    /// Returns the current focus, or `None`.
75    pub fn current(&self) -> Option<&T> {
76        self.items.last()
77    }
78    /// Returns the focus depth.
79    pub fn depth(&self) -> usize {
80        self.items.len()
81    }
82    /// Returns `true` if there is no current focus.
83    pub fn is_empty(&self) -> bool {
84        self.items.is_empty()
85    }
86}
87/// A simple ordered index mapping names to integer IDs.
88#[allow(dead_code)]
89#[derive(Debug, Clone, Default)]
90pub struct NameIndex {
91    names: Vec<String>,
92    index: std::collections::HashMap<String, usize>,
93}
94impl NameIndex {
95    /// Create a new empty name index.
96    #[allow(dead_code)]
97    pub fn new() -> Self {
98        Self::default()
99    }
100    /// Insert a name, returning its ID.
101    /// If the name is already present, returns the existing ID.
102    #[allow(dead_code)]
103    pub fn insert(&mut self, name: impl Into<String>) -> usize {
104        let name = name.into();
105        if let Some(&id) = self.index.get(&name) {
106            return id;
107        }
108        let id = self.names.len();
109        self.index.insert(name.clone(), id);
110        self.names.push(name);
111        id
112    }
113    /// Get the ID for a name, if it exists.
114    #[allow(dead_code)]
115    pub fn get_id(&self, name: &str) -> Option<usize> {
116        self.index.get(name).copied()
117    }
118    /// Get the name for an ID.
119    #[allow(dead_code)]
120    pub fn get_name(&self, id: usize) -> Option<&str> {
121        self.names.get(id).map(|s| s.as_str())
122    }
123    /// Get the number of names in the index.
124    #[allow(dead_code)]
125    pub fn len(&self) -> usize {
126        self.names.len()
127    }
128    /// Check if the index is empty.
129    #[allow(dead_code)]
130    pub fn is_empty(&self) -> bool {
131        self.names.is_empty()
132    }
133    /// Get all names in insertion order.
134    #[allow(dead_code)]
135    pub fn all_names(&self) -> &[String] {
136        &self.names
137    }
138}
139/// A versioned record that stores a history of values.
140#[allow(dead_code)]
141pub struct VersionedRecord<T: Clone> {
142    history: Vec<T>,
143}
144#[allow(dead_code)]
145impl<T: Clone> VersionedRecord<T> {
146    /// Creates a new record with an initial value.
147    pub fn new(initial: T) -> Self {
148        Self {
149            history: vec![initial],
150        }
151    }
152    /// Updates the record with a new version.
153    pub fn update(&mut self, val: T) {
154        self.history.push(val);
155    }
156    /// Returns the current (latest) value.
157    pub fn current(&self) -> &T {
158        self.history
159            .last()
160            .expect("VersionedRecord history is always non-empty after construction")
161    }
162    /// Returns the value at version `n` (0-indexed), or `None`.
163    pub fn at_version(&self, n: usize) -> Option<&T> {
164        self.history.get(n)
165    }
166    /// Returns the version number of the current value.
167    pub fn version(&self) -> usize {
168        self.history.len() - 1
169    }
170    /// Returns `true` if more than one version exists.
171    pub fn has_history(&self) -> bool {
172        self.history.len() > 1
173    }
174}
175/// A type-erased function pointer with arity tracking.
176#[allow(dead_code)]
177pub struct RawFnPtr {
178    /// The raw function pointer (stored as usize for type erasure).
179    ptr: usize,
180    arity: usize,
181    name: String,
182}
183#[allow(dead_code)]
184impl RawFnPtr {
185    /// Creates a new raw function pointer descriptor.
186    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
187        Self {
188            ptr,
189            arity,
190            name: name.into(),
191        }
192    }
193    /// Returns the arity.
194    pub fn arity(&self) -> usize {
195        self.arity
196    }
197    /// Returns the name.
198    pub fn name(&self) -> &str {
199        &self.name
200    }
201    /// Returns the raw pointer value.
202    pub fn raw(&self) -> usize {
203        self.ptr
204    }
205}
206/// A batch checker that can check multiple pairs efficiently.
207#[allow(dead_code)]
208pub struct BatchDefEqChecker<'env> {
209    checker: DefEqChecker<'env>,
210}
211impl<'env> BatchDefEqChecker<'env> {
212    /// Create a new batch checker.
213    #[allow(dead_code)]
214    pub fn new(env: &'env Environment) -> Self {
215        Self {
216            checker: DefEqChecker::new(env),
217        }
218    }
219    /// Check definitional equality for a single pair.
220    #[allow(dead_code)]
221    pub fn check(&mut self, t: &Expr, s: &Expr) -> bool {
222        self.checker.is_def_eq(t, s)
223    }
224    /// Check all pairs in a list; returns true iff all pairs are def-equal.
225    #[allow(dead_code)]
226    pub fn check_all(&mut self, pairs: &[(Expr, Expr)]) -> bool {
227        pairs.iter().all(|(t, s)| self.checker.is_def_eq(t, s))
228    }
229    /// Check any pair in a list; returns true iff at least one pair is def-equal.
230    #[allow(dead_code)]
231    pub fn check_any(&mut self, pairs: &[(Expr, Expr)]) -> bool {
232        pairs.iter().any(|(t, s)| self.checker.is_def_eq(t, s))
233    }
234    /// Count how many pairs in the list are definitionally equal.
235    #[allow(dead_code)]
236    pub fn count_equal(&mut self, pairs: &[(Expr, Expr)]) -> usize {
237        pairs
238            .iter()
239            .filter(|(t, s)| self.checker.is_def_eq(t, s))
240            .count()
241    }
242    /// Reset the checker (clears caches).
243    #[allow(dead_code)]
244    pub fn reset(&mut self) {
245        self.checker.cache.clear();
246        self.checker.equiv_manager.clear();
247    }
248}
249/// A generic counter that tracks min/max/sum for statistical summaries.
250#[allow(dead_code)]
251pub struct StatSummary {
252    count: u64,
253    sum: f64,
254    min: f64,
255    max: f64,
256}
257#[allow(dead_code)]
258impl StatSummary {
259    /// Creates an empty summary.
260    pub fn new() -> Self {
261        Self {
262            count: 0,
263            sum: 0.0,
264            min: f64::INFINITY,
265            max: f64::NEG_INFINITY,
266        }
267    }
268    /// Records a sample.
269    pub fn record(&mut self, val: f64) {
270        self.count += 1;
271        self.sum += val;
272        if val < self.min {
273            self.min = val;
274        }
275        if val > self.max {
276            self.max = val;
277        }
278    }
279    /// Returns the mean, or `None` if no samples.
280    pub fn mean(&self) -> Option<f64> {
281        if self.count == 0 {
282            None
283        } else {
284            Some(self.sum / self.count as f64)
285        }
286    }
287    /// Returns the minimum, or `None` if no samples.
288    pub fn min(&self) -> Option<f64> {
289        if self.count == 0 {
290            None
291        } else {
292            Some(self.min)
293        }
294    }
295    /// Returns the maximum, or `None` if no samples.
296    pub fn max(&self) -> Option<f64> {
297        if self.count == 0 {
298            None
299        } else {
300            Some(self.max)
301        }
302    }
303    /// Returns the count of recorded samples.
304    pub fn count(&self) -> u64 {
305        self.count
306    }
307}
308/// A pair of `StatSummary` values tracking before/after a transformation.
309#[allow(dead_code)]
310pub struct TransformStat {
311    before: StatSummary,
312    after: StatSummary,
313}
314#[allow(dead_code)]
315impl TransformStat {
316    /// Creates a new transform stat recorder.
317    pub fn new() -> Self {
318        Self {
319            before: StatSummary::new(),
320            after: StatSummary::new(),
321        }
322    }
323    /// Records a before value.
324    pub fn record_before(&mut self, v: f64) {
325        self.before.record(v);
326    }
327    /// Records an after value.
328    pub fn record_after(&mut self, v: f64) {
329        self.after.record(v);
330    }
331    /// Returns the mean reduction ratio (after/before).
332    pub fn mean_ratio(&self) -> Option<f64> {
333        let b = self.before.mean()?;
334        let a = self.after.mean()?;
335        if b.abs() < f64::EPSILON {
336            return None;
337        }
338        Some(a / b)
339    }
340}
341/// A write-once cell.
342#[allow(dead_code)]
343pub struct WriteOnce<T> {
344    value: std::cell::Cell<Option<T>>,
345}
346#[allow(dead_code)]
347impl<T: Copy> WriteOnce<T> {
348    /// Creates an empty write-once cell.
349    pub fn new() -> Self {
350        Self {
351            value: std::cell::Cell::new(None),
352        }
353    }
354    /// Writes a value.  Returns `false` if already written.
355    pub fn write(&self, val: T) -> bool {
356        if self.value.get().is_some() {
357            return false;
358        }
359        self.value.set(Some(val));
360        true
361    }
362    /// Returns the value if written.
363    pub fn read(&self) -> Option<T> {
364        self.value.get()
365    }
366    /// Returns `true` if the value has been written.
367    pub fn is_written(&self) -> bool {
368        self.value.get().is_some()
369    }
370}
371/// A hierarchical configuration tree.
372#[allow(dead_code)]
373pub struct ConfigNode {
374    key: String,
375    value: Option<String>,
376    children: Vec<ConfigNode>,
377}
378#[allow(dead_code)]
379impl ConfigNode {
380    /// Creates a leaf config node with a value.
381    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
382        Self {
383            key: key.into(),
384            value: Some(value.into()),
385            children: Vec::new(),
386        }
387    }
388    /// Creates a section node with children.
389    pub fn section(key: impl Into<String>) -> Self {
390        Self {
391            key: key.into(),
392            value: None,
393            children: Vec::new(),
394        }
395    }
396    /// Adds a child node.
397    pub fn add_child(&mut self, child: ConfigNode) {
398        self.children.push(child);
399    }
400    /// Returns the key.
401    pub fn key(&self) -> &str {
402        &self.key
403    }
404    /// Returns the value, or `None` for section nodes.
405    pub fn value(&self) -> Option<&str> {
406        self.value.as_deref()
407    }
408    /// Returns the number of children.
409    pub fn num_children(&self) -> usize {
410        self.children.len()
411    }
412    /// Looks up a dot-separated path.
413    pub fn lookup(&self, path: &str) -> Option<&str> {
414        let mut parts = path.splitn(2, '.');
415        let head = parts.next()?;
416        let tail = parts.next();
417        if head != self.key {
418            return None;
419        }
420        match tail {
421            None => self.value.as_deref(),
422            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
423        }
424    }
425    fn lookup_relative(&self, path: &str) -> Option<&str> {
426        let mut parts = path.splitn(2, '.');
427        let head = parts.next()?;
428        let tail = parts.next();
429        if head != self.key {
430            return None;
431        }
432        match tail {
433            None => self.value.as_deref(),
434            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
435        }
436    }
437}
438/// Statistics for a DefEq check run.
439#[allow(dead_code)]
440#[derive(Debug, Clone, Default)]
441pub struct DefEqStats {
442    /// Number of cache hits.
443    pub cache_hits: u64,
444    /// Number of cache misses.
445    pub cache_misses: u64,
446    /// Number of reduction steps taken.
447    pub reduction_steps: u64,
448    /// Number of successful delta reductions.
449    pub delta_reductions: u64,
450    /// Number of beta reductions.
451    pub beta_reductions: u64,
452    /// Number of eta expansions tried.
453    pub eta_attempts: u64,
454    /// Number of equiv-manager hits.
455    pub equiv_hits: u64,
456}
457impl DefEqStats {
458    /// Compute the cache hit rate in [0, 1].
459    #[allow(dead_code)]
460    pub fn cache_hit_rate(&self) -> f64 {
461        let total = self.cache_hits + self.cache_misses;
462        if total == 0 {
463            1.0
464        } else {
465            self.cache_hits as f64 / total as f64
466        }
467    }
468    /// Compute total number of cache accesses.
469    #[allow(dead_code)]
470    pub fn total_cache_accesses(&self) -> u64 {
471        self.cache_hits + self.cache_misses
472    }
473    /// Compute total number of reductions across all kinds.
474    #[allow(dead_code)]
475    pub fn total_reductions(&self) -> u64 {
476        self.reduction_steps + self.delta_reductions + self.beta_reductions
477    }
478}
479/// A reusable scratch buffer for path computations.
480#[allow(dead_code)]
481pub struct PathBuf {
482    components: Vec<String>,
483}
484#[allow(dead_code)]
485impl PathBuf {
486    /// Creates a new empty path buffer.
487    pub fn new() -> Self {
488        Self {
489            components: Vec::new(),
490        }
491    }
492    /// Pushes a component.
493    pub fn push(&mut self, comp: impl Into<String>) {
494        self.components.push(comp.into());
495    }
496    /// Pops the last component.
497    pub fn pop(&mut self) {
498        self.components.pop();
499    }
500    /// Returns the current path as a `/`-separated string.
501    pub fn as_str(&self) -> String {
502        self.components.join("/")
503    }
504    /// Returns the depth of the path.
505    pub fn depth(&self) -> usize {
506        self.components.len()
507    }
508    /// Clears the path.
509    pub fn clear(&mut self) {
510        self.components.clear();
511    }
512}
513/// A simple decision tree node for rule dispatching.
514#[allow(dead_code)]
515#[allow(missing_docs)]
516pub enum DecisionNode {
517    /// A leaf with an action string.
518    Leaf(String),
519    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
520    Branch {
521        key: String,
522        val: String,
523        yes_branch: Box<DecisionNode>,
524        no_branch: Box<DecisionNode>,
525    },
526}
527#[allow(dead_code)]
528impl DecisionNode {
529    /// Evaluates the decision tree with the given context.
530    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
531        match self {
532            DecisionNode::Leaf(action) => action.as_str(),
533            DecisionNode::Branch {
534                key,
535                val,
536                yes_branch,
537                no_branch,
538            } => {
539                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
540                if actual == val.as_str() {
541                    yes_branch.evaluate(ctx)
542                } else {
543                    no_branch.evaluate(ctx)
544                }
545            }
546        }
547    }
548    /// Returns the depth of the decision tree.
549    pub fn depth(&self) -> usize {
550        match self {
551            DecisionNode::Leaf(_) => 0,
552            DecisionNode::Branch {
553                yes_branch,
554                no_branch,
555                ..
556            } => 1 + yes_branch.depth().max(no_branch.depth()),
557        }
558    }
559}
560/// A sparse vector: stores only non-default elements.
561#[allow(dead_code)]
562pub struct SparseVec<T: Default + Clone + PartialEq> {
563    entries: std::collections::HashMap<usize, T>,
564    default_: T,
565    logical_len: usize,
566}
567#[allow(dead_code)]
568impl<T: Default + Clone + PartialEq> SparseVec<T> {
569    /// Creates a new sparse vector with logical length `len`.
570    pub fn new(len: usize) -> Self {
571        Self {
572            entries: std::collections::HashMap::new(),
573            default_: T::default(),
574            logical_len: len,
575        }
576    }
577    /// Sets element at `idx`.
578    pub fn set(&mut self, idx: usize, val: T) {
579        if val == self.default_ {
580            self.entries.remove(&idx);
581        } else {
582            self.entries.insert(idx, val);
583        }
584    }
585    /// Gets element at `idx`.
586    pub fn get(&self, idx: usize) -> &T {
587        self.entries.get(&idx).unwrap_or(&self.default_)
588    }
589    /// Returns the logical length.
590    pub fn len(&self) -> usize {
591        self.logical_len
592    }
593    /// Returns whether the collection is empty.
594    pub fn is_empty(&self) -> bool {
595        self.len() == 0
596    }
597    /// Returns the number of non-default elements.
598    pub fn nnz(&self) -> usize {
599        self.entries.len()
600    }
601}
602/// A counter that can measure elapsed time between snapshots.
603#[allow(dead_code)]
604pub struct Stopwatch {
605    start: std::time::Instant,
606    splits: Vec<f64>,
607}
608#[allow(dead_code)]
609impl Stopwatch {
610    /// Creates and starts a new stopwatch.
611    pub fn start() -> Self {
612        Self {
613            start: std::time::Instant::now(),
614            splits: Vec::new(),
615        }
616    }
617    /// Records a split time (elapsed since start).
618    pub fn split(&mut self) {
619        self.splits.push(self.elapsed_ms());
620    }
621    /// Returns total elapsed milliseconds since start.
622    pub fn elapsed_ms(&self) -> f64 {
623        self.start.elapsed().as_secs_f64() * 1000.0
624    }
625    /// Returns all recorded split times.
626    pub fn splits(&self) -> &[f64] {
627        &self.splits
628    }
629    /// Returns the number of splits.
630    pub fn num_splits(&self) -> usize {
631        self.splits.len()
632    }
633}
634/// A label set for a graph node.
635#[allow(dead_code)]
636pub struct LabelSet {
637    labels: Vec<String>,
638}
639#[allow(dead_code)]
640impl LabelSet {
641    /// Creates a new empty label set.
642    pub fn new() -> Self {
643        Self { labels: Vec::new() }
644    }
645    /// Adds a label (deduplicates).
646    pub fn add(&mut self, label: impl Into<String>) {
647        let s = label.into();
648        if !self.labels.contains(&s) {
649            self.labels.push(s);
650        }
651    }
652    /// Returns `true` if `label` is present.
653    pub fn has(&self, label: &str) -> bool {
654        self.labels.iter().any(|l| l == label)
655    }
656    /// Returns the count of labels.
657    pub fn count(&self) -> usize {
658        self.labels.len()
659    }
660    /// Returns all labels.
661    pub fn all(&self) -> &[String] {
662        &self.labels
663    }
664}
665/// A pool of reusable string buffers.
666#[allow(dead_code)]
667pub struct StringPool {
668    free: Vec<String>,
669}
670#[allow(dead_code)]
671impl StringPool {
672    /// Creates a new empty string pool.
673    pub fn new() -> Self {
674        Self { free: Vec::new() }
675    }
676    /// Takes a string from the pool (may be empty).
677    pub fn take(&mut self) -> String {
678        self.free.pop().unwrap_or_default()
679    }
680    /// Returns a string to the pool.
681    pub fn give(&mut self, mut s: String) {
682        s.clear();
683        self.free.push(s);
684    }
685    /// Returns the number of free strings in the pool.
686    pub fn free_count(&self) -> usize {
687        self.free.len()
688    }
689}
690/// Status of a lazy delta reduction step.
691#[derive(Debug, PartialEq, Eq)]
692pub enum ReductionStatus {
693    /// Continue: reduced one or both sides.
694    Continue(Expr, Expr),
695    /// Definitely equal.
696    Equal,
697    /// Definitely not equal (both stuck).
698    Stuck,
699    /// Unknown: need more reduction.
700    Unknown,
701}
702/// A simple stack-based calculator for arithmetic expressions.
703#[allow(dead_code)]
704pub struct StackCalc {
705    stack: Vec<i64>,
706}
707#[allow(dead_code)]
708impl StackCalc {
709    /// Creates a new empty calculator.
710    pub fn new() -> Self {
711        Self { stack: Vec::new() }
712    }
713    /// Pushes an integer literal.
714    pub fn push(&mut self, n: i64) {
715        self.stack.push(n);
716    }
717    /// Adds the top two values.  Panics if fewer than two values.
718    pub fn add(&mut self) {
719        let b = self
720            .stack
721            .pop()
722            .expect("stack must have at least two values for add");
723        let a = self
724            .stack
725            .pop()
726            .expect("stack must have at least two values for add");
727        self.stack.push(a + b);
728    }
729    /// Subtracts top from second.
730    pub fn sub(&mut self) {
731        let b = self
732            .stack
733            .pop()
734            .expect("stack must have at least two values for sub");
735        let a = self
736            .stack
737            .pop()
738            .expect("stack must have at least two values for sub");
739        self.stack.push(a - b);
740    }
741    /// Multiplies the top two values.
742    pub fn mul(&mut self) {
743        let b = self
744            .stack
745            .pop()
746            .expect("stack must have at least two values for mul");
747        let a = self
748            .stack
749            .pop()
750            .expect("stack must have at least two values for mul");
751        self.stack.push(a * b);
752    }
753    /// Peeks the top value.
754    pub fn peek(&self) -> Option<i64> {
755        self.stack.last().copied()
756    }
757    /// Returns the stack depth.
758    pub fn depth(&self) -> usize {
759        self.stack.len()
760    }
761}
762/// Configuration for the definitional equality checker.
763#[allow(dead_code)]
764#[derive(Debug, Clone)]
765pub struct DefEqConfig {
766    /// Maximum number of reduction steps before giving up.
767    pub max_steps: u32,
768    /// Whether to use proof irrelevance (Props are equal).
769    pub proof_irrelevance: bool,
770    /// Whether to check eta-expansion.
771    pub eta: bool,
772    /// Whether to try lazy delta reduction.
773    pub lazy_delta: bool,
774    /// Transparency mode for unfolding.
775    pub transparency: TransparencyMode,
776}
777impl DefEqConfig {
778    /// Create a config for checking definitional equality with full unfolding.
779    #[allow(dead_code)]
780    pub fn full_transparency() -> Self {
781        Self {
782            transparency: TransparencyMode::All,
783            ..Self::default()
784        }
785    }
786    /// Create a config for opaque checking (no unfolding).
787    #[allow(dead_code)]
788    pub fn opaque() -> Self {
789        Self {
790            lazy_delta: false,
791            transparency: TransparencyMode::None,
792            ..Self::default()
793        }
794    }
795    /// Create a config with proof irrelevance disabled.
796    #[allow(dead_code)]
797    pub fn no_proof_irrelevance() -> Self {
798        Self {
799            proof_irrelevance: false,
800            ..Self::default()
801        }
802    }
803}
804/// A dependency closure builder (transitive closure via BFS).
805#[allow(dead_code)]
806pub struct TransitiveClosure {
807    adj: Vec<Vec<usize>>,
808    n: usize,
809}
810#[allow(dead_code)]
811impl TransitiveClosure {
812    /// Creates a transitive closure builder for `n` nodes.
813    pub fn new(n: usize) -> Self {
814        Self {
815            adj: vec![Vec::new(); n],
816            n,
817        }
818    }
819    /// Adds a direct edge.
820    pub fn add_edge(&mut self, from: usize, to: usize) {
821        if from < self.n {
822            self.adj[from].push(to);
823        }
824    }
825    /// Computes all nodes reachable from `start` (including `start`).
826    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
827        let mut visited = vec![false; self.n];
828        let mut queue = std::collections::VecDeque::new();
829        queue.push_back(start);
830        while let Some(node) = queue.pop_front() {
831            if node >= self.n || visited[node] {
832                continue;
833            }
834            visited[node] = true;
835            for &next in &self.adj[node] {
836                queue.push_back(next);
837            }
838        }
839        (0..self.n).filter(|&i| visited[i]).collect()
840    }
841    /// Returns `true` if `from` can transitively reach `to`.
842    pub fn can_reach(&self, from: usize, to: usize) -> bool {
843        self.reachable_from(from).contains(&to)
844    }
845}
846/// Simple trie for efficient string prefix lookup.
847#[allow(dead_code)]
848#[derive(Debug, Clone, Default)]
849pub struct StringTrie {
850    pub(super) children: std::collections::HashMap<char, StringTrie>,
851    is_end: bool,
852    pub(super) value: Option<String>,
853}
854impl StringTrie {
855    /// Create a new empty trie.
856    #[allow(dead_code)]
857    pub fn new() -> Self {
858        Self::default()
859    }
860    /// Insert a string into the trie.
861    #[allow(dead_code)]
862    pub fn insert(&mut self, s: &str) {
863        let mut node = self;
864        for c in s.chars() {
865            node = node.children.entry(c).or_default();
866        }
867        node.is_end = true;
868        node.value = Some(s.to_string());
869    }
870    /// Check if a string is in the trie.
871    #[allow(dead_code)]
872    pub fn contains(&self, s: &str) -> bool {
873        let mut node = self;
874        for c in s.chars() {
875            match node.children.get(&c) {
876                Some(next) => node = next,
877                None => return false,
878            }
879        }
880        node.is_end
881    }
882    /// Find all strings with a given prefix.
883    #[allow(dead_code)]
884    pub fn starts_with(&self, prefix: &str) -> Vec<String> {
885        let mut node = self;
886        for c in prefix.chars() {
887            match node.children.get(&c) {
888                Some(next) => node = next,
889                None => return vec![],
890            }
891        }
892        let mut results = Vec::new();
893        collect_strings(node, &mut results);
894        results
895    }
896    /// Get the number of strings in the trie.
897    #[allow(dead_code)]
898    pub fn len(&self) -> usize {
899        let mut count = if self.is_end { 1 } else { 0 };
900        for child in self.children.values() {
901            count += child.len();
902        }
903        count
904    }
905    /// Check if the trie is empty.
906    #[allow(dead_code)]
907    pub fn is_empty(&self) -> bool {
908        self.len() == 0
909    }
910}
911/// A window iterator that yields overlapping windows of size `n`.
912#[allow(dead_code)]
913pub struct WindowIterator<'a, T> {
914    pub(super) data: &'a [T],
915    pub(super) pos: usize,
916    pub(super) window: usize,
917}
918#[allow(dead_code)]
919impl<'a, T> WindowIterator<'a, T> {
920    /// Creates a new window iterator.
921    pub fn new(data: &'a [T], window: usize) -> Self {
922        Self {
923            data,
924            pos: 0,
925            window,
926        }
927    }
928}
929/// A token bucket rate limiter.
930#[allow(dead_code)]
931pub struct TokenBucket {
932    capacity: u64,
933    tokens: u64,
934    refill_per_ms: u64,
935    last_refill: std::time::Instant,
936}
937#[allow(dead_code)]
938impl TokenBucket {
939    /// Creates a new token bucket.
940    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
941        Self {
942            capacity,
943            tokens: capacity,
944            refill_per_ms,
945            last_refill: std::time::Instant::now(),
946        }
947    }
948    /// Attempts to consume `n` tokens.  Returns `true` on success.
949    pub fn try_consume(&mut self, n: u64) -> bool {
950        self.refill();
951        if self.tokens >= n {
952            self.tokens -= n;
953            true
954        } else {
955            false
956        }
957    }
958    fn refill(&mut self) {
959        let now = std::time::Instant::now();
960        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
961        if elapsed_ms > 0 {
962            let new_tokens = elapsed_ms * self.refill_per_ms;
963            self.tokens = (self.tokens + new_tokens).min(self.capacity);
964            self.last_refill = now;
965        }
966    }
967    /// Returns the number of currently available tokens.
968    pub fn available(&self) -> u64 {
969        self.tokens
970    }
971    /// Returns the bucket capacity.
972    pub fn capacity(&self) -> u64 {
973        self.capacity
974    }
975}
976/// A set of rewrite rules.
977#[allow(dead_code)]
978pub struct RewriteRuleSet {
979    rules: Vec<RewriteRule>,
980}
981#[allow(dead_code)]
982impl RewriteRuleSet {
983    /// Creates an empty rule set.
984    pub fn new() -> Self {
985        Self { rules: Vec::new() }
986    }
987    /// Adds a rule.
988    pub fn add(&mut self, rule: RewriteRule) {
989        self.rules.push(rule);
990    }
991    /// Returns the number of rules.
992    pub fn len(&self) -> usize {
993        self.rules.len()
994    }
995    /// Returns `true` if the set is empty.
996    pub fn is_empty(&self) -> bool {
997        self.rules.is_empty()
998    }
999    /// Returns all conditional rules.
1000    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
1001        self.rules.iter().filter(|r| r.conditional).collect()
1002    }
1003    /// Returns all unconditional rules.
1004    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
1005        self.rules.iter().filter(|r| !r.conditional).collect()
1006    }
1007    /// Looks up a rule by name.
1008    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
1009        self.rules.iter().find(|r| r.name == name)
1010    }
1011}
1012/// A simple directed acyclic graph.
1013#[allow(dead_code)]
1014pub struct SimpleDag {
1015    /// `edges[i]` is the list of direct successors of node `i`.
1016    edges: Vec<Vec<usize>>,
1017}
1018#[allow(dead_code)]
1019impl SimpleDag {
1020    /// Creates a DAG with `n` nodes and no edges.
1021    pub fn new(n: usize) -> Self {
1022        Self {
1023            edges: vec![Vec::new(); n],
1024        }
1025    }
1026    /// Adds an edge from `from` to `to`.
1027    pub fn add_edge(&mut self, from: usize, to: usize) {
1028        if from < self.edges.len() {
1029            self.edges[from].push(to);
1030        }
1031    }
1032    /// Returns the successors of `node`.
1033    pub fn successors(&self, node: usize) -> &[usize] {
1034        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
1035    }
1036    /// Returns `true` if `from` can reach `to` via DFS.
1037    pub fn can_reach(&self, from: usize, to: usize) -> bool {
1038        let mut visited = vec![false; self.edges.len()];
1039        self.dfs(from, to, &mut visited)
1040    }
1041    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
1042        if cur == target {
1043            return true;
1044        }
1045        if cur >= visited.len() || visited[cur] {
1046            return false;
1047        }
1048        visited[cur] = true;
1049        for &next in self.successors(cur) {
1050            if self.dfs(next, target, visited) {
1051                return true;
1052            }
1053        }
1054        false
1055    }
1056    /// Returns the topological order of nodes, or `None` if a cycle is detected.
1057    pub fn topological_sort(&self) -> Option<Vec<usize>> {
1058        let n = self.edges.len();
1059        let mut in_degree = vec![0usize; n];
1060        for succs in &self.edges {
1061            for &s in succs {
1062                if s < n {
1063                    in_degree[s] += 1;
1064                }
1065            }
1066        }
1067        let mut queue: std::collections::VecDeque<usize> =
1068            (0..n).filter(|&i| in_degree[i] == 0).collect();
1069        let mut order = Vec::new();
1070        while let Some(node) = queue.pop_front() {
1071            order.push(node);
1072            for &s in self.successors(node) {
1073                if s < n {
1074                    in_degree[s] -= 1;
1075                    if in_degree[s] == 0 {
1076                        queue.push_back(s);
1077                    }
1078                }
1079            }
1080        }
1081        if order.len() == n {
1082            Some(order)
1083        } else {
1084            None
1085        }
1086    }
1087    /// Returns the number of nodes.
1088    pub fn num_nodes(&self) -> usize {
1089        self.edges.len()
1090    }
1091}
1092/// A flat list of substitution pairs `(from, to)`.
1093#[allow(dead_code)]
1094pub struct FlatSubstitution {
1095    pairs: Vec<(String, String)>,
1096}
1097#[allow(dead_code)]
1098impl FlatSubstitution {
1099    /// Creates an empty substitution.
1100    pub fn new() -> Self {
1101        Self { pairs: Vec::new() }
1102    }
1103    /// Adds a pair.
1104    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
1105        self.pairs.push((from.into(), to.into()));
1106    }
1107    /// Applies all substitutions to `s` (leftmost-first order).
1108    pub fn apply(&self, s: &str) -> String {
1109        let mut result = s.to_string();
1110        for (from, to) in &self.pairs {
1111            result = result.replace(from.as_str(), to.as_str());
1112        }
1113        result
1114    }
1115    /// Returns the number of pairs.
1116    pub fn len(&self) -> usize {
1117        self.pairs.len()
1118    }
1119    /// Returns `true` if empty.
1120    pub fn is_empty(&self) -> bool {
1121        self.pairs.is_empty()
1122    }
1123}
1124/// Represents a rewrite rule `lhs → rhs`.
1125#[allow(dead_code)]
1126#[allow(missing_docs)]
1127pub struct RewriteRule {
1128    /// The name of the rule.
1129    pub name: String,
1130    /// A string representation of the LHS pattern.
1131    pub lhs: String,
1132    /// A string representation of the RHS.
1133    pub rhs: String,
1134    /// Whether this is a conditional rule (has side conditions).
1135    pub conditional: bool,
1136}
1137#[allow(dead_code)]
1138impl RewriteRule {
1139    /// Creates an unconditional rewrite rule.
1140    pub fn unconditional(
1141        name: impl Into<String>,
1142        lhs: impl Into<String>,
1143        rhs: impl Into<String>,
1144    ) -> Self {
1145        Self {
1146            name: name.into(),
1147            lhs: lhs.into(),
1148            rhs: rhs.into(),
1149            conditional: false,
1150        }
1151    }
1152    /// Creates a conditional rewrite rule.
1153    pub fn conditional(
1154        name: impl Into<String>,
1155        lhs: impl Into<String>,
1156        rhs: impl Into<String>,
1157    ) -> Self {
1158        Self {
1159            name: name.into(),
1160            lhs: lhs.into(),
1161            rhs: rhs.into(),
1162            conditional: true,
1163        }
1164    }
1165    /// Returns a textual representation.
1166    pub fn display(&self) -> String {
1167        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
1168    }
1169}
1170/// A non-empty list (at least one element guaranteed).
1171#[allow(dead_code)]
1172pub struct NonEmptyVec<T> {
1173    head: T,
1174    tail: Vec<T>,
1175}
1176#[allow(dead_code)]
1177impl<T> NonEmptyVec<T> {
1178    /// Creates a non-empty vec with a single element.
1179    pub fn singleton(val: T) -> Self {
1180        Self {
1181            head: val,
1182            tail: Vec::new(),
1183        }
1184    }
1185    /// Pushes an element.
1186    pub fn push(&mut self, val: T) {
1187        self.tail.push(val);
1188    }
1189    /// Returns a reference to the first element.
1190    pub fn first(&self) -> &T {
1191        &self.head
1192    }
1193    /// Returns a reference to the last element.
1194    pub fn last(&self) -> &T {
1195        self.tail.last().unwrap_or(&self.head)
1196    }
1197    /// Returns the number of elements.
1198    pub fn len(&self) -> usize {
1199        1 + self.tail.len()
1200    }
1201    /// Returns whether the collection is empty.
1202    pub fn is_empty(&self) -> bool {
1203        self.len() == 0
1204    }
1205    /// Returns all elements as a Vec.
1206    pub fn to_vec(&self) -> Vec<&T> {
1207        let mut v = vec![&self.head];
1208        v.extend(self.tail.iter());
1209        v
1210    }
1211}
1212/// A fixed-size sliding window that computes a running sum.
1213#[allow(dead_code)]
1214pub struct SlidingSum {
1215    window: Vec<f64>,
1216    capacity: usize,
1217    pos: usize,
1218    sum: f64,
1219    count: usize,
1220}
1221#[allow(dead_code)]
1222impl SlidingSum {
1223    /// Creates a sliding sum with the given window size.
1224    pub fn new(capacity: usize) -> Self {
1225        Self {
1226            window: vec![0.0; capacity],
1227            capacity,
1228            pos: 0,
1229            sum: 0.0,
1230            count: 0,
1231        }
1232    }
1233    /// Adds a value to the window, removing the oldest if full.
1234    pub fn push(&mut self, val: f64) {
1235        let oldest = self.window[self.pos];
1236        self.sum -= oldest;
1237        self.sum += val;
1238        self.window[self.pos] = val;
1239        self.pos = (self.pos + 1) % self.capacity;
1240        if self.count < self.capacity {
1241            self.count += 1;
1242        }
1243    }
1244    /// Returns the current window sum.
1245    pub fn sum(&self) -> f64 {
1246        self.sum
1247    }
1248    /// Returns the window mean, or `None` if empty.
1249    pub fn mean(&self) -> Option<f64> {
1250        if self.count == 0 {
1251            None
1252        } else {
1253            Some(self.sum / self.count as f64)
1254        }
1255    }
1256    /// Returns the current window size (number of valid elements).
1257    pub fn count(&self) -> usize {
1258        self.count
1259    }
1260}
1261/// Definitional equality checker with full optimizations.
1262pub struct DefEqChecker<'env> {
1263    env: &'env Environment,
1264    reducer: Reducer,
1265    cache: HashMap<(Expr, Expr), bool>,
1266    equiv_manager: EquivManager,
1267    proof_irrelevance: bool,
1268}
1269impl<'env> DefEqChecker<'env> {
1270    /// Create a new definitional equality checker.
1271    pub fn new(env: &'env Environment) -> Self {
1272        Self {
1273            env,
1274            reducer: Reducer::new(),
1275            cache: HashMap::new(),
1276            equiv_manager: EquivManager::new(),
1277            proof_irrelevance: true,
1278        }
1279    }
1280    /// Disable proof irrelevance for this checker.
1281    pub fn set_proof_irrelevance(&mut self, enabled: bool) {
1282        self.proof_irrelevance = enabled;
1283    }
1284    /// Set the transparency mode for this checker.
1285    pub fn set_transparency(&mut self, mode: TransparencyMode) {
1286        self.reducer.set_transparency(mode);
1287        self.cache.clear();
1288        self.equiv_manager.clear();
1289    }
1290    /// Check if two expressions are definitionally equal.
1291    pub fn is_def_eq(&mut self, t: &Expr, s: &Expr) -> bool {
1292        if t == s {
1293            return true;
1294        }
1295        if self.equiv_manager.is_equiv(t, s) {
1296            return true;
1297        }
1298        if self.equiv_manager.is_failure(t, s) {
1299            return false;
1300        }
1301        let key = (t.clone(), s.clone());
1302        if let Some(&result) = self.cache.get(&key) {
1303            return result;
1304        }
1305        let result = self.is_def_eq_core(t, s);
1306        self.cache.insert(key, result);
1307        if result {
1308            self.equiv_manager.add_equiv(t, s);
1309        } else {
1310            self.equiv_manager.add_failure(t, s);
1311        }
1312        result
1313    }
1314    fn is_def_eq_core(&mut self, t: &Expr, s: &Expr) -> bool {
1315        let t_whnf = self.reducer.whnf_env(t, self.env);
1316        let s_whnf = self.reducer.whnf_env(s, self.env);
1317        if t_whnf == s_whnf {
1318            return true;
1319        }
1320        if self.is_proof_irrelevant_eq(&t_whnf, &s_whnf) {
1321            return true;
1322        }
1323        match (&t_whnf, &s_whnf) {
1324            (Expr::Sort(l1), Expr::Sort(l2)) => level::is_equivalent(l1, l2),
1325            (Expr::BVar(i1), Expr::BVar(i2)) => i1 == i2,
1326            (Expr::FVar(id1), Expr::FVar(id2)) => id1 == id2,
1327            (Expr::Const(n1, ls1), Expr::Const(n2, ls2)) => {
1328                n1 == n2
1329                    && ls1.len() == ls2.len()
1330                    && ls1
1331                        .iter()
1332                        .zip(ls2.iter())
1333                        .all(|(l1, l2)| level::is_equivalent(l1, l2))
1334            }
1335            (Expr::App(f1, a1), Expr::App(f2, a2)) => {
1336                if self.is_def_eq_app(&t_whnf, &s_whnf) {
1337                    return true;
1338                }
1339                self.is_def_eq(f1, f2) && self.is_def_eq(a1, a2)
1340            }
1341            (Expr::Lam(_, _, ty1, b1), Expr::Lam(_, _, ty2, b2)) => {
1342                self.is_def_eq(ty1, ty2) && self.is_def_eq(b1, b2)
1343            }
1344            (Expr::Pi(_, _, ty1, b1), Expr::Pi(_, _, ty2, b2)) => {
1345                self.is_def_eq(ty1, ty2) && self.is_def_eq(b1, b2)
1346            }
1347            (Expr::Let(_, ty1, v1, b1), Expr::Let(_, ty2, v2, b2)) => {
1348                self.is_def_eq(ty1, ty2) && self.is_def_eq(v1, v2) && self.is_def_eq(b1, b2)
1349            }
1350            (Expr::Lit(l1), Expr::Lit(l2)) => l1 == l2,
1351            (Expr::Proj(n1, i1, e1), Expr::Proj(n2, i2, e2)) => {
1352                n1 == n2 && i1 == i2 && self.is_def_eq(e1, e2)
1353            }
1354            (Expr::Lam(_, _, _, _), _) => self.try_eta_lhs(&t_whnf, &s_whnf),
1355            (_, Expr::Lam(_, _, _, _)) => self.try_eta_rhs(&t_whnf, &s_whnf),
1356            _ => self.try_lazy_delta(&t_whnf, &s_whnf),
1357        }
1358    }
1359    /// Try lazy delta reduction: unfold one side at a time.
1360    ///
1361    /// When comparing `f a1 ... an` with `g b1 ... bm` where f and g
1362    /// are both definitions, unfold the one with lower height first.
1363    fn try_lazy_delta(&mut self, t: &Expr, s: &Expr) -> bool {
1364        let t_head = get_app_fn(t);
1365        let s_head = get_app_fn(s);
1366        let t_hint = self.get_hint(t_head);
1367        let s_hint = self.get_hint(s_head);
1368        match (t_hint, s_hint) {
1369            (Some(th), Some(sh)) => {
1370                if th.height() <= sh.height() {
1371                    if let Some(t_unfolded) = self.unfold_definition(t) {
1372                        let t_whnf = self.reducer.whnf_env(&t_unfolded, self.env);
1373                        return self.is_def_eq(&t_whnf, s);
1374                    }
1375                }
1376                if let Some(s_unfolded) = self.unfold_definition(s) {
1377                    let s_whnf = self.reducer.whnf_env(&s_unfolded, self.env);
1378                    return self.is_def_eq(t, &s_whnf);
1379                }
1380                false
1381            }
1382            (Some(_), None) => {
1383                if let Some(t_unfolded) = self.unfold_definition(t) {
1384                    let t_whnf = self.reducer.whnf_env(&t_unfolded, self.env);
1385                    return self.is_def_eq(&t_whnf, s);
1386                }
1387                false
1388            }
1389            (None, Some(_)) => {
1390                if let Some(s_unfolded) = self.unfold_definition(s) {
1391                    let s_whnf = self.reducer.whnf_env(&s_unfolded, self.env);
1392                    return self.is_def_eq(t, &s_whnf);
1393                }
1394                false
1395            }
1396            (None, None) => false,
1397        }
1398    }
1399    /// Get the reducibility hint for an expression head.
1400    fn get_hint(&self, head: &Expr) -> Option<ReducibilityHint> {
1401        if let Expr::Const(name, _) = head {
1402            if let Some(ci) = self.env.find(name) {
1403                let hint = ci.reducibility_hint();
1404                if hint.should_unfold() {
1405                    return Some(hint);
1406                }
1407            }
1408        }
1409        None
1410    }
1411    /// Try to unfold a definition at the head of an expression.
1412    fn unfold_definition(&self, expr: &Expr) -> Option<Expr> {
1413        let head = get_app_fn(expr);
1414        if let Expr::Const(name, levels) = head {
1415            if let Some(ci) = self.env.find(name) {
1416                if let Some(val) = ci.value() {
1417                    let val_inst = if ci.level_params().is_empty() || levels.is_empty() {
1418                        val.clone()
1419                    } else {
1420                        crate::instantiate::instantiate_type_lparams(val, ci.level_params(), levels)
1421                    };
1422                    let args: Vec<Expr> = get_app_args(expr).into_iter().cloned().collect();
1423                    return Some(crate::expr_util::mk_app(val_inst, &args));
1424                }
1425            }
1426        }
1427        None
1428    }
1429    /// Try structural application equality.
1430    ///
1431    /// Compare `f a1 ... an` with `g b1 ... bn` by comparing
1432    /// the heads and all arguments.
1433    fn is_def_eq_app(&mut self, t: &Expr, s: &Expr) -> bool {
1434        let t_head = get_app_fn(t);
1435        let s_head = get_app_fn(s);
1436        let t_args = get_app_args(t);
1437        let s_args = get_app_args(s);
1438        if t_args.len() != s_args.len() {
1439            return false;
1440        }
1441        if !self.is_def_eq(t_head, s_head) {
1442            return false;
1443        }
1444        t_args
1445            .iter()
1446            .zip(s_args.iter())
1447            .all(|(a, b)| self.is_def_eq(a, b))
1448    }
1449    /// Quickly infer the type of a term without full elaboration.
1450    ///
1451    /// Handles the common structural cases needed for proof irrelevance:
1452    /// constants, applications of Pi-typed functions, Sort, and literals.
1453    /// Returns `None` for cases that require a full type checker (e.g. BVar, FVar).
1454    fn quick_infer_type(&mut self, expr: &Expr) -> Option<Expr> {
1455        match expr {
1456            Expr::Sort(l) => Some(Expr::Sort(crate::level::Level::succ(l.clone()))),
1457            Expr::Lit(crate::Literal::Nat(_)) => Some(Expr::Const(crate::Name::str("Nat"), vec![])),
1458            Expr::Lit(crate::Literal::Str(_)) => {
1459                Some(Expr::Const(crate::Name::str("String"), vec![]))
1460            }
1461            Expr::Const(name, levels) => {
1462                let ci = self.env.find(name)?;
1463                let raw_ty = ci.ty().clone();
1464                let params = ci.level_params().to_vec();
1465                if params.is_empty() || levels.is_empty() {
1466                    Some(raw_ty)
1467                } else {
1468                    Some(crate::instantiate::instantiate_type_lparams(
1469                        &raw_ty, &params, levels,
1470                    ))
1471                }
1472            }
1473            Expr::App(f, a) => {
1474                let f_ty = self.quick_infer_type(f)?;
1475                let f_ty_whnf = self.reducer.whnf_env(&f_ty, self.env);
1476                if let Expr::Pi(_, _, _, body) = f_ty_whnf {
1477                    Some(crate::subst::instantiate(&body, a))
1478                } else {
1479                    None
1480                }
1481            }
1482            Expr::Lam(bi, name, dom, body) => {
1483                let body_ty = self.quick_infer_type(body)?;
1484                Some(Expr::Pi(*bi, name.clone(), dom.clone(), Box::new(body_ty)))
1485            }
1486            Expr::Pi(_, _, dom, cod) => {
1487                let dom_ty = self.quick_infer_type(dom)?;
1488                let cod_ty = self.quick_infer_type(cod)?;
1489                let dom_whnf = self.reducer.whnf_env(&dom_ty, self.env);
1490                let cod_whnf = self.reducer.whnf_env(&cod_ty, self.env);
1491                match (dom_whnf, cod_whnf) {
1492                    (Expr::Sort(l1), Expr::Sort(l2)) => {
1493                        Some(Expr::Sort(crate::level::Level::imax(l1, l2)))
1494                    }
1495                    _ => None,
1496                }
1497            }
1498            Expr::Let(_, _ty, val, body) => {
1499                let body_subst = crate::subst::instantiate(body, val);
1500                self.quick_infer_type(&body_subst)
1501            }
1502            _ => None,
1503        }
1504    }
1505    /// Check proof irrelevance: two proofs of the same Prop are definitionally equal.
1506    ///
1507    /// Two terms `t` and `s` are proof-irrelevantly equal when:
1508    /// - Both have types that reduce to `Sort 0` (i.e., they live in `Prop`)
1509    /// - Their types are definitionally equal (they prove the same proposition)
1510    fn is_proof_irrelevant_eq(&mut self, t: &Expr, s: &Expr) -> bool {
1511        if !self.proof_irrelevance {
1512            return false;
1513        }
1514        let ty_t = match self.quick_infer_type(t) {
1515            Some(ty) => ty,
1516            None => return false,
1517        };
1518        let ty_ty_t = match self.quick_infer_type(&ty_t) {
1519            Some(ty) => ty,
1520            None => return false,
1521        };
1522        let ty_ty_t_whnf = self.reducer.whnf_env(&ty_ty_t, self.env);
1523        if !matches!(& ty_ty_t_whnf, Expr::Sort(l) if l.is_zero()) {
1524            return false;
1525        }
1526        let ty_s = match self.quick_infer_type(s) {
1527            Some(ty) => ty,
1528            None => return false,
1529        };
1530        let ty_ty_s = match self.quick_infer_type(&ty_s) {
1531            Some(ty) => ty,
1532            None => return false,
1533        };
1534        let ty_ty_s_whnf = self.reducer.whnf_env(&ty_ty_s, self.env);
1535        if !matches!(& ty_ty_s_whnf, Expr::Sort(l) if l.is_zero()) {
1536            return false;
1537        }
1538        let ty_t_whnf = self.reducer.whnf_env(&ty_t, self.env);
1539        let ty_s_whnf = self.reducer.whnf_env(&ty_s, self.env);
1540        self.is_def_eq(&ty_t_whnf, &ty_s_whnf)
1541    }
1542    /// Try eta-expansion for left-side lambda.
1543    ///
1544    /// `λ x. f x` =?= `g` when `f` doesn't use `x`, then `f =?= g`
1545    fn try_eta_lhs(&mut self, t: &Expr, s: &Expr) -> bool {
1546        if let Expr::Lam(_, _, _, body) = t {
1547            if let Expr::App(f, a) = body.as_ref() {
1548                if let Expr::BVar(0) = **a {
1549                    if !has_loose_bvar(f, 0) {
1550                        let f_shifted =
1551                            crate::subst::instantiate(f, &Expr::FVar(crate::FVarId(u64::MAX)));
1552                        return self.is_def_eq(&f_shifted, s);
1553                    }
1554                }
1555            }
1556        }
1557        false
1558    }
1559    /// Try eta-expansion for right-side lambda.
1560    fn try_eta_rhs(&mut self, t: &Expr, s: &Expr) -> bool {
1561        if let Expr::Lam(_, _, _, body) = s {
1562            if let Expr::App(f, a) = body.as_ref() {
1563                if let Expr::BVar(0) = **a {
1564                    if !has_loose_bvar(f, 0) {
1565                        let f_shifted =
1566                            crate::subst::instantiate(f, &Expr::FVar(crate::FVarId(u64::MAX)));
1567                        return self.is_def_eq(t, &f_shifted);
1568                    }
1569                }
1570            }
1571        }
1572        false
1573    }
1574}
1575/// A simple key-value store backed by a sorted Vec for small maps.
1576#[allow(dead_code)]
1577pub struct SmallMap<K: Ord + Clone, V: Clone> {
1578    entries: Vec<(K, V)>,
1579}
1580#[allow(dead_code)]
1581impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1582    /// Creates a new empty small map.
1583    pub fn new() -> Self {
1584        Self {
1585            entries: Vec::new(),
1586        }
1587    }
1588    /// Inserts or replaces the value for `key`.
1589    pub fn insert(&mut self, key: K, val: V) {
1590        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1591            Ok(i) => self.entries[i].1 = val,
1592            Err(i) => self.entries.insert(i, (key, val)),
1593        }
1594    }
1595    /// Returns the value for `key`, or `None`.
1596    pub fn get(&self, key: &K) -> Option<&V> {
1597        self.entries
1598            .binary_search_by_key(&key, |(k, _)| k)
1599            .ok()
1600            .map(|i| &self.entries[i].1)
1601    }
1602    /// Returns the number of entries.
1603    pub fn len(&self) -> usize {
1604        self.entries.len()
1605    }
1606    /// Returns `true` if empty.
1607    pub fn is_empty(&self) -> bool {
1608        self.entries.is_empty()
1609    }
1610    /// Returns all keys.
1611    pub fn keys(&self) -> Vec<&K> {
1612        self.entries.iter().map(|(k, _)| k).collect()
1613    }
1614    /// Returns all values.
1615    pub fn values(&self) -> Vec<&V> {
1616        self.entries.iter().map(|(_, v)| v).collect()
1617    }
1618}