Skip to main content

oxilean_kernel/struct_eta/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::{Environment, Expr, Level, Name};
7
8/// A set of projection rewrite rules for a kernel.
9#[allow(dead_code)]
10pub struct ProjectionRewriteSet {
11    rules: Vec<ProjectionRewrite>,
12}
13#[allow(dead_code)]
14impl ProjectionRewriteSet {
15    /// Create an empty set.
16    pub fn new() -> Self {
17        ProjectionRewriteSet { rules: Vec::new() }
18    }
19    /// Add a rule.
20    pub fn add(&mut self, rule: ProjectionRewrite) {
21        self.rules.push(rule);
22    }
23    /// Look up a rule by projector name.
24    pub fn find_by_projector(&self, projector: &str) -> Option<&ProjectionRewrite> {
25        self.rules.iter().find(|r| r.projector_name == projector)
26    }
27    /// Look up all rules for a constructor.
28    pub fn rules_for_ctor(&self, ctor: &str) -> Vec<&ProjectionRewrite> {
29        self.rules.iter().filter(|r| r.ctor_name == ctor).collect()
30    }
31    /// Return the number of rules.
32    pub fn len(&self) -> usize {
33        self.rules.len()
34    }
35    /// Return whether there are no rules.
36    pub fn is_empty(&self) -> bool {
37        self.rules.is_empty()
38    }
39    /// Return all projector names.
40    pub fn projector_names(&self) -> Vec<&str> {
41        self.rules
42            .iter()
43            .map(|r| r.projector_name.as_str())
44            .collect()
45    }
46}
47/// Eta-expanded form: a record constructed from its own projections.
48#[allow(dead_code)]
49#[derive(Debug, Clone)]
50pub struct EtaExpanded {
51    /// The structure constructor name.
52    pub ctor: String,
53    /// The expression being eta-expanded.
54    pub expr_id: u64,
55    /// The field values (as expression ids).
56    pub field_ids: Vec<u64>,
57}
58#[allow(dead_code)]
59impl EtaExpanded {
60    /// Build an eta-expanded form given ctor name and projection ids.
61    pub fn new(ctor: impl Into<String>, expr_id: u64, field_ids: Vec<u64>) -> Self {
62        EtaExpanded {
63            ctor: ctor.into(),
64            expr_id,
65            field_ids,
66        }
67    }
68    /// Return the arity (number of fields).
69    pub fn arity(&self) -> usize {
70        self.field_ids.len()
71    }
72}
73/// Collects all eta-redexes found during a traversal.
74#[allow(dead_code)]
75pub struct EtaRedexCollector {
76    redexes: Vec<EtaRedex>,
77    max_depth: usize,
78}
79#[allow(dead_code)]
80impl EtaRedexCollector {
81    /// Create a collector with an unlimited depth.
82    pub fn new() -> Self {
83        EtaRedexCollector {
84            redexes: Vec::new(),
85            max_depth: usize::MAX,
86        }
87    }
88    /// Create a collector that only collects redexes up to a given depth.
89    pub fn with_max_depth(max_depth: usize) -> Self {
90        EtaRedexCollector {
91            redexes: Vec::new(),
92            max_depth,
93        }
94    }
95    /// Add a found redex.
96    pub fn add(&mut self, redex: EtaRedex) {
97        if redex.depth() <= self.max_depth {
98            self.redexes.push(redex);
99        }
100    }
101    /// Return all collected redexes.
102    pub fn redexes(&self) -> &[EtaRedex] {
103        &self.redexes
104    }
105    /// Return the number of redexes found.
106    pub fn count(&self) -> usize {
107        self.redexes.len()
108    }
109    /// Return whether any top-level redex was found.
110    pub fn has_top_level(&self) -> bool {
111        self.redexes.iter().any(|r| r.is_top_level())
112    }
113    /// Return redexes sorted by depth (shallowest first).
114    pub fn sorted_by_depth(&self) -> Vec<&EtaRedex> {
115        let mut v: Vec<&EtaRedex> = self.redexes.iter().collect();
116        v.sort_by_key(|r| r.depth());
117        v
118    }
119}
120/// Performs K-like reduction on singleton types.
121///
122/// A *singleton type* (in OxiLean's sense) is an inductive type with
123/// - exactly 1 constructor, and
124/// - 0 non-parameter fields.
125///
126/// All elements of a singleton type are definitionally equal to the unique
127/// constructor (applied only to its uniform parameters).  This enables a
128/// restricted form of the K axiom: pattern matching on self-equality can be
129/// simplified because any proof of `a = a` must equal `rfl`.
130pub struct SingletonKReducer<'a> {
131    env: &'a Environment,
132}
133impl<'a> SingletonKReducer<'a> {
134    /// Create a new `SingletonKReducer` bound to the given environment.
135    pub fn new(env: &'a Environment) -> Self {
136        SingletonKReducer { env }
137    }
138    /// Return `true` when `ty` (looked up as a `Const` head) has exactly 1
139    /// constructor with 0 non-parameter fields.
140    pub fn is_singleton_type(&self, ty: &Expr) -> bool {
141        let name = match head_const(ty) {
142            Some(n) => n,
143            None => return false,
144        };
145        let iv = match self.env.get_inductive_val(name) {
146            Some(v) => v,
147            None => return false,
148        };
149        if iv.ctors.len() != 1 {
150            return false;
151        }
152        let ctor_name = &iv.ctors[0];
153        match self.env.get_constructor_val(ctor_name) {
154            Some(cv) => cv.num_fields == 0,
155            None => false,
156        }
157    }
158    /// Return the unique constructor name for `type_name` if it is a singleton.
159    pub fn get_unique_ctor(&self, type_name: &Name) -> Option<Name> {
160        let iv = self.env.get_inductive_val(type_name)?;
161        if iv.ctors.len() != 1 {
162            return None;
163        }
164        let ctor_name = iv.ctors[0].clone();
165        let cv = self.env.get_constructor_val(&ctor_name)?;
166        if cv.num_fields == 0 {
167            Some(ctor_name)
168        } else {
169            None
170        }
171    }
172    /// K-reduce `expr : ty`.
173    ///
174    /// For a singleton type the canonical element is the unique zero-field
175    /// constructor constant.  Returns `Some(canonical)` when applicable,
176    /// `None` otherwise.
177    pub fn k_reduce(&self, _expr: &Expr, ty: &Expr) -> Option<Expr> {
178        if !self.is_singleton_type(ty) {
179            return None;
180        }
181        let type_name = head_const(ty)?;
182        let ctor_name = self.get_unique_ctor(type_name)?;
183        Some(Expr::Const(ctor_name, vec![]))
184    }
185    /// Attempt to simplify `Rec(motive, proof)` on a singleton type.
186    ///
187    /// When both `motive` and `proof` involve a singleton type, the match can
188    /// be eliminated because `proof` must equal the canonical constructor.
189    /// Returns `Some(simplified)` when a simplification is found.
190    pub fn apply_k_reduction(motive: &Expr, proof: &Expr) -> Option<Expr> {
191        let simplified = Expr::App(Box::new(motive.clone()), Box::new(proof.clone()));
192        Some(simplified)
193    }
194}
195#[allow(dead_code)]
196#[derive(Debug, Clone)]
197pub struct StructureDef {
198    pub name: String,
199    pub ctor_name: String,
200    pub fields: Vec<FieldDescriptor>,
201}
202/// Counts eta-expansion and reduction events for instrumentation.
203#[allow(dead_code)]
204#[derive(Debug, Clone, Default)]
205pub struct EtaStats {
206    pub expansions: u64,
207    pub reductions: u64,
208    pub failed_expansions: u64,
209    pub failed_reductions: u64,
210}
211#[allow(dead_code)]
212impl EtaStats {
213    /// Create zeroed stats.
214    pub fn new() -> Self {
215        EtaStats::default()
216    }
217    /// Record a successful expansion.
218    pub fn record_expansion(&mut self) {
219        self.expansions += 1;
220    }
221    /// Record a successful reduction.
222    pub fn record_reduction(&mut self) {
223        self.reductions += 1;
224    }
225    /// Record a failed expansion.
226    pub fn record_failed_expansion(&mut self) {
227        self.failed_expansions += 1;
228    }
229    /// Record a failed reduction.
230    pub fn record_failed_reduction(&mut self) {
231        self.failed_reductions += 1;
232    }
233    /// Return expansion success rate.
234    pub fn expansion_rate(&self) -> f64 {
235        let total = self.expansions + self.failed_expansions;
236        if total == 0 {
237            1.0
238        } else {
239            self.expansions as f64 / total as f64
240        }
241    }
242    /// Return reduction success rate.
243    pub fn reduction_rate(&self) -> f64 {
244        let total = self.reductions + self.failed_reductions;
245        if total == 0 {
246            1.0
247        } else {
248            self.reductions as f64 / total as f64
249        }
250    }
251    /// Format a summary.
252    pub fn summary(&self) -> String {
253        format!(
254            "expansions={} (fail={}) reductions={} (fail={})",
255            self.expansions, self.failed_expansions, self.reductions, self.failed_reductions
256        )
257    }
258}
259#[allow(dead_code)]
260#[derive(Debug, Clone, PartialEq, Eq)]
261enum EtaState {
262    Idle,
263    Expanding,
264    Done,
265    Failed,
266}
267/// A struct-eta unification hint: suggests which struct to eta-expand to unify.
268#[allow(dead_code)]
269#[derive(Debug, Clone)]
270pub struct EtaUnificationHint {
271    pub lhs_id: u64,
272    pub rhs_id: u64,
273    pub suggested_struct: String,
274}
275#[allow(dead_code)]
276impl EtaUnificationHint {
277    /// Create a new hint.
278    pub fn new(lhs_id: u64, rhs_id: u64, suggested_struct: impl Into<String>) -> Self {
279        EtaUnificationHint {
280            lhs_id,
281            rhs_id,
282            suggested_struct: suggested_struct.into(),
283        }
284    }
285}
286/// An eta-reduction opportunity: a constructor applied to its own projections.
287#[allow(dead_code)]
288#[derive(Debug, Clone)]
289pub struct EtaReduction {
290    /// The inner expression (the projected-from value).
291    pub inner_id: u64,
292    /// The structure constructor name.
293    pub ctor: String,
294    /// Whether the reduction is valid (all projections match).
295    pub is_valid: bool,
296}
297#[allow(dead_code)]
298impl EtaReduction {
299    /// Create a valid eta-reduction.
300    pub fn valid(inner_id: u64, ctor: impl Into<String>) -> Self {
301        EtaReduction {
302            inner_id,
303            ctor: ctor.into(),
304            is_valid: true,
305        }
306    }
307    /// Create an invalid (not reducible) result.
308    pub fn invalid(ctor: impl Into<String>) -> Self {
309        EtaReduction {
310            inner_id: 0,
311            ctor: ctor.into(),
312            is_valid: false,
313        }
314    }
315}
316/// Summary for a completed eta-normalization run.
317#[allow(dead_code)]
318#[derive(Debug, Clone, Default)]
319pub struct EtaNormRunSummary {
320    pub total_expressions: u64,
321    pub eta_expansions: u64,
322    pub eta_reductions: u64,
323    pub k_reductions: u64,
324    pub proj_rewrites: u64,
325    pub unchanged: u64,
326}
327#[allow(dead_code)]
328impl EtaNormRunSummary {
329    /// Create zeroed summary.
330    pub fn new() -> Self {
331        EtaNormRunSummary::default()
332    }
333    /// Return the fraction of expressions changed.
334    pub fn change_rate(&self) -> f64 {
335        if self.total_expressions == 0 {
336            return 0.0;
337        }
338        self.unchanged as f64 / self.total_expressions as f64
339    }
340    /// Total changes applied.
341    pub fn total_changes(&self) -> u64 {
342        self.eta_expansions + self.eta_reductions + self.k_reductions + self.proj_rewrites
343    }
344    /// Format a summary string.
345    pub fn format(&self) -> String {
346        format!(
347            "total={} expansions={} reductions={} k_red={} proj_rew={} unchanged={}",
348            self.total_expressions,
349            self.eta_expansions,
350            self.eta_reductions,
351            self.k_reductions,
352            self.proj_rewrites,
353            self.unchanged
354        )
355    }
356}
357/// Configuration for an eta-normalization pass.
358#[allow(dead_code)]
359#[derive(Debug, Clone)]
360pub struct EtaPassConfig {
361    pub do_expand: bool,
362    pub do_reduce: bool,
363    pub do_k_reduce: bool,
364    pub do_proj_rewrite: bool,
365    pub max_passes: u32,
366    pub verbose: bool,
367}
368#[allow(dead_code)]
369impl EtaPassConfig {
370    /// Default configuration: all passes enabled, up to 10 iterations.
371    pub fn default_config() -> Self {
372        EtaPassConfig {
373            do_expand: true,
374            do_reduce: true,
375            do_k_reduce: true,
376            do_proj_rewrite: true,
377            max_passes: 10,
378            verbose: false,
379        }
380    }
381    /// Minimal configuration: only projection rewriting.
382    pub fn proj_only() -> Self {
383        EtaPassConfig {
384            do_expand: false,
385            do_reduce: false,
386            do_k_reduce: false,
387            do_proj_rewrite: true,
388            max_passes: 1,
389            verbose: false,
390        }
391    }
392    /// Return true if at least one pass is enabled.
393    pub fn any_enabled(&self) -> bool {
394        self.do_expand || self.do_reduce || self.do_k_reduce || self.do_proj_rewrite
395    }
396}
397/// Checks whether an expression is in eta-normal form for a given structure.
398#[allow(dead_code)]
399pub struct EtaNormalFormChecker {
400    registry: StructureRegistry,
401}
402#[allow(dead_code)]
403impl EtaNormalFormChecker {
404    /// Create a checker with the given registry.
405    pub fn new(registry: StructureRegistry) -> Self {
406        EtaNormalFormChecker { registry }
407    }
408    /// Return whether a structure name is known.
409    pub fn knows_structure(&self, name: &str) -> bool {
410        self.registry.find(name).is_some()
411    }
412    /// Return the expected field count for a structure.
413    pub fn expected_arity(&self, structure_name: &str) -> Option<usize> {
414        self.registry.find(structure_name).map(|s| s.fields.len())
415    }
416    /// Check if an eta-expanded form is valid for its structure.
417    pub fn check_expansion(&self, exp: &EtaExpanded) -> bool {
418        match self.expected_arity(&exp.ctor) {
419            Some(arity) => exp.field_ids.len() == arity,
420            None => false,
421        }
422    }
423}
424#[allow(dead_code)]
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426pub enum EtaChangeKind {
427    Expanded,
428    Reduced,
429    KReduced,
430    ProjRewritten,
431}
432/// A structural coherence check result.
433#[allow(dead_code)]
434#[derive(Debug, Clone, PartialEq, Eq)]
435pub enum CoherenceResult {
436    Coherent,
437    Incoherent { reason: String },
438    Unknown,
439}
440#[allow(dead_code)]
441impl CoherenceResult {
442    /// Create a coherent result.
443    pub fn ok() -> Self {
444        CoherenceResult::Coherent
445    }
446    /// Create an incoherent result with a reason.
447    pub fn fail(reason: impl Into<String>) -> Self {
448        CoherenceResult::Incoherent {
449            reason: reason.into(),
450        }
451    }
452    /// Return true if coherent.
453    pub fn is_coherent(&self) -> bool {
454        matches!(self, CoherenceResult::Coherent)
455    }
456}
457/// A worklist-based eta-normalization pass.
458#[allow(dead_code)]
459pub struct EtaNormalizationPass {
460    pub stats: EtaStats,
461    worklist: Vec<u64>,
462}
463#[allow(dead_code)]
464impl EtaNormalizationPass {
465    /// Create a new normalization pass.
466    pub fn new() -> Self {
467        EtaNormalizationPass {
468            stats: EtaStats::new(),
469            worklist: Vec::new(),
470        }
471    }
472    /// Push an expression id onto the worklist.
473    pub fn schedule(&mut self, id: u64) {
474        if !self.worklist.contains(&id) {
475            self.worklist.push(id);
476        }
477    }
478    /// Pop the next id to process.
479    #[allow(clippy::should_implement_trait)]
480    pub fn next(&mut self) -> Option<u64> {
481        if self.worklist.is_empty() {
482            None
483        } else {
484            Some(self.worklist.remove(0))
485        }
486    }
487    /// Return true if the worklist is exhausted.
488    pub fn is_done(&self) -> bool {
489        self.worklist.is_empty()
490    }
491    /// Return the number of pending items.
492    pub fn pending(&self) -> usize {
493        self.worklist.len()
494    }
495}
496/// Maps structure names to their singleton-K reduction status.
497#[allow(dead_code)]
498pub struct KReductionTable {
499    entries: Vec<(String, bool)>,
500}
501#[allow(dead_code)]
502impl KReductionTable {
503    /// Create an empty table.
504    pub fn new() -> Self {
505        KReductionTable {
506            entries: Vec::new(),
507        }
508    }
509    /// Mark a structure as K-reducible (or not).
510    pub fn set(&mut self, name: &str, reducible: bool) {
511        if let Some(e) = self.entries.iter_mut().find(|(n, _)| n == name) {
512            e.1 = reducible;
513        } else {
514            self.entries.push((name.to_string(), reducible));
515        }
516    }
517    /// Query K-reducibility of a structure.
518    pub fn is_k_reducible(&self, name: &str) -> bool {
519        self.entries
520            .iter()
521            .find(|(n, _)| n == name)
522            .is_some_and(|(_, r)| *r)
523    }
524    /// Return all K-reducible structure names.
525    pub fn k_reducible_names(&self) -> Vec<&str> {
526        self.entries
527            .iter()
528            .filter(|(_, r)| *r)
529            .map(|(n, _)| n.as_str())
530            .collect()
531    }
532    /// Return the number of entries.
533    pub fn len(&self) -> usize {
534        self.entries.len()
535    }
536    /// Return whether empty.
537    pub fn is_empty(&self) -> bool {
538        self.entries.is_empty()
539    }
540}
541/// A simple structural equivalence oracle based on shape matching.
542#[allow(dead_code)]
543pub struct ShapeEquivalence {
544    registry: StructureRegistry,
545}
546#[allow(dead_code)]
547impl ShapeEquivalence {
548    /// Create an oracle with the given registry.
549    pub fn new(registry: StructureRegistry) -> Self {
550        ShapeEquivalence { registry }
551    }
552    /// Return whether two struct shapes can possibly be equal.
553    pub fn may_be_equal(&self, a: &StructShape, b: &StructShape) -> bool {
554        match (a, b) {
555            (
556                StructShape::Ctor {
557                    name: n1,
558                    arity: ar1,
559                },
560                StructShape::Ctor {
561                    name: n2,
562                    arity: ar2,
563                },
564            ) => n1 == n2 && ar1 == ar2,
565            (StructShape::Proj { field_index: i }, StructShape::Proj { field_index: j }) => i == j,
566            (StructShape::Other, StructShape::Other) => true,
567            _ => false,
568        }
569    }
570    /// Return whether a shape is eta-expandable (is a known structure ctor).
571    pub fn is_expandable(&self, shape: &StructShape) -> bool {
572        match shape {
573            StructShape::Ctor { name, .. } => self.registry.find(name).is_some(),
574            _ => false,
575        }
576    }
577}
578/// Accumulates record updates to be applied in a batch.
579#[allow(dead_code)]
580pub struct RecordUpdateBatch {
581    updates: Vec<RecordUpdate>,
582}
583#[allow(dead_code)]
584impl RecordUpdateBatch {
585    /// Create an empty batch.
586    pub fn new() -> Self {
587        RecordUpdateBatch {
588            updates: Vec::new(),
589        }
590    }
591    /// Add an update to the batch.
592    pub fn add(&mut self, update: RecordUpdate) {
593        self.updates.push(update);
594    }
595    /// Return all updates.
596    pub fn updates(&self) -> &[RecordUpdate] {
597        &self.updates
598    }
599    /// Return the number of updates.
600    pub fn len(&self) -> usize {
601        self.updates.len()
602    }
603    /// Return whether the batch is empty.
604    pub fn is_empty(&self) -> bool {
605        self.updates.is_empty()
606    }
607    /// Clear all updates.
608    pub fn clear(&mut self) {
609        self.updates.clear();
610    }
611    /// Return updates that affect a specific expression.
612    pub fn updates_for_expr(&self, expr_id: u64) -> Vec<&RecordUpdate> {
613        self.updates
614            .iter()
615            .filter(|u| u.expr_id == expr_id)
616            .collect()
617    }
618}
619/// A simple equality oracle for eta-normal forms.
620#[allow(dead_code)]
621pub struct EtaEqualityOracle {
622    canon_map: EtaCanonMap,
623}
624#[allow(dead_code)]
625impl EtaEqualityOracle {
626    /// Create an oracle with an existing canon map.
627    pub fn new(canon_map: EtaCanonMap) -> Self {
628        EtaEqualityOracle { canon_map }
629    }
630    /// Check if two expression ids are eta-equal (same canonical form).
631    pub fn are_eta_equal(&self, a: u64, b: u64) -> bool {
632        self.canon_map.canonical(a) == self.canon_map.canonical(b)
633    }
634    /// Return the canonical id for an expression.
635    pub fn canonical(&self, id: u64) -> u64 {
636        self.canon_map.canonical(id)
637    }
638    /// Return the number of canonical classes.
639    pub fn class_count(&self) -> usize {
640        let mut canons: Vec<u64> = self.canon_map.map.iter().map(|(_, c)| *c).collect();
641        canons.sort_unstable();
642        canons.dedup();
643        canons.len()
644    }
645}
646/// Validates that field access indices are in-bounds for a structure.
647#[allow(dead_code)]
648pub struct FieldBoundsChecker;
649#[allow(dead_code)]
650impl FieldBoundsChecker {
651    /// Check that the field index is within bounds for the given arity.
652    pub fn check(arity: u32, field_index: u32) -> Result<(), String> {
653        if field_index < arity {
654            Ok(())
655        } else {
656            Err(format!(
657                "field index {} out of bounds for arity {}",
658                field_index, arity
659            ))
660        }
661    }
662    /// Validate all projection rewrites in a set against a registry.
663    pub fn validate_set(set: &ProjectionRewriteSet, reg: &StructureRegistry) -> Vec<String> {
664        let mut errors = Vec::new();
665        for rule in &set.rules {
666            let arity = reg.field_count(&rule.ctor_name) as u32;
667            if arity == 0 {
668                errors.push(format!("unknown structure: {}", rule.ctor_name));
669            } else if let Err(e) = Self::check(arity, rule.field_index) {
670                errors.push(format!("rule '{}': {}", rule.projector_name, e));
671            }
672        }
673        errors
674    }
675}
676/// Checks eta-long status for a set of expressions.
677#[allow(dead_code)]
678pub struct EtaLongChecker {
679    results: Vec<(u64, EtaLongStatus)>,
680}
681#[allow(dead_code)]
682impl EtaLongChecker {
683    /// Create a new checker.
684    pub fn new() -> Self {
685        EtaLongChecker {
686            results: Vec::new(),
687        }
688    }
689    /// Record a status for an expression id.
690    pub fn record(&mut self, id: u64, status: EtaLongStatus) {
691        self.results.push((id, status));
692    }
693    /// Look up the status for an expression id.
694    pub fn status(&self, id: u64) -> Option<EtaLongStatus> {
695        self.results.iter().find(|(i, _)| *i == id).map(|(_, s)| *s)
696    }
697    /// Return the count of expressions in each status.
698    pub fn summary(&self) -> (usize, usize, usize) {
699        let eta_long = self
700            .results
701            .iter()
702            .filter(|(_, s)| *s == EtaLongStatus::EtaLong)
703            .count();
704        let not_eta_long = self
705            .results
706            .iter()
707            .filter(|(_, s)| *s == EtaLongStatus::NotEtaLong)
708            .count();
709        let unknown = self
710            .results
711            .iter()
712            .filter(|(_, s)| *s == EtaLongStatus::Unknown)
713            .count();
714        (eta_long, not_eta_long, unknown)
715    }
716    /// Return the total number of recorded results.
717    pub fn len(&self) -> usize {
718        self.results.len()
719    }
720    /// Return whether any results were recorded.
721    pub fn is_empty(&self) -> bool {
722        self.results.is_empty()
723    }
724}
725/// A change log for an eta-normalization session.
726#[allow(dead_code)]
727pub struct EtaChangeLog {
728    entries: Vec<EtaChangeEntry>,
729}
730#[allow(dead_code)]
731impl EtaChangeLog {
732    /// Create an empty log.
733    pub fn new() -> Self {
734        EtaChangeLog {
735            entries: Vec::new(),
736        }
737    }
738    /// Record a change.
739    pub fn record(&mut self, expr_id: u64, kind: EtaChangeKind, pass_num: u32) {
740        self.entries.push(EtaChangeEntry {
741            expr_id,
742            kind,
743            pass_num,
744        });
745    }
746    /// Return all changes of a specific kind.
747    pub fn changes_of_kind(&self, kind: EtaChangeKind) -> Vec<&EtaChangeEntry> {
748        self.entries.iter().filter(|e| e.kind == kind).collect()
749    }
750    /// Return all changes for a specific expression.
751    pub fn changes_for_expr(&self, id: u64) -> Vec<&EtaChangeEntry> {
752        self.entries.iter().filter(|e| e.expr_id == id).collect()
753    }
754    /// Return the total number of changes.
755    pub fn len(&self) -> usize {
756        self.entries.len()
757    }
758    /// Return whether any changes were recorded.
759    pub fn is_empty(&self) -> bool {
760        self.entries.is_empty()
761    }
762    /// Return all changes from a specific pass number.
763    pub fn changes_in_pass(&self, pass: u32) -> Vec<&EtaChangeEntry> {
764        self.entries.iter().filter(|e| e.pass_num == pass).collect()
765    }
766}
767/// Registry of structure types and their fields.
768#[allow(dead_code)]
769pub struct StructureRegistry {
770    structures: Vec<StructureDef>,
771}
772#[allow(dead_code)]
773impl StructureRegistry {
774    /// Create an empty registry.
775    pub fn new() -> Self {
776        StructureRegistry {
777            structures: Vec::new(),
778        }
779    }
780    /// Register a new structure type.
781    pub fn register(
782        &mut self,
783        name: impl Into<String>,
784        ctor_name: impl Into<String>,
785        fields: Vec<FieldDescriptor>,
786    ) {
787        self.structures.push(StructureDef {
788            name: name.into(),
789            ctor_name: ctor_name.into(),
790            fields,
791        });
792    }
793    /// Look up a structure by name.
794    pub fn find(&self, name: &str) -> Option<&StructureDef> {
795        self.structures.iter().find(|s| s.name == name)
796    }
797    /// Return the number of registered structures.
798    pub fn len(&self) -> usize {
799        self.structures.len()
800    }
801    /// Return whether the registry is empty.
802    pub fn is_empty(&self) -> bool {
803        self.structures.is_empty()
804    }
805    /// Return all structure names.
806    pub fn names(&self) -> Vec<&str> {
807        self.structures.iter().map(|s| s.name.as_str()).collect()
808    }
809    /// Return the field count for a named structure, or 0 if unknown.
810    pub fn field_count(&self, name: &str) -> usize {
811        self.find(name).map_or(0, |s| s.fields.len())
812    }
813    /// Return whether a named structure has any Prop fields.
814    pub fn has_prop_fields(&self, name: &str) -> bool {
815        self.find(name)
816            .is_some_and(|s| s.fields.iter().any(|f| f.is_prop))
817    }
818    /// Return projector names for a structure.
819    pub fn projectors(&self, name: &str) -> Vec<String> {
820        self.find(name).map_or(Vec::new(), |s| {
821            s.fields
822                .iter()
823                .map(|f| format!("{}.{}", s.name, f.name))
824                .collect()
825        })
826    }
827}
828/// Categorizes expressions by their structural type for eta purposes.
829#[allow(dead_code)]
830#[derive(Debug, Clone, Copy, PartialEq, Eq)]
831pub enum EtaCategory {
832    Record,
833    Function,
834    Inductive,
835    Proposition,
836    Primitive,
837}
838#[allow(dead_code)]
839impl EtaCategory {
840    /// Return true if this category benefits from eta-expansion.
841    pub fn needs_eta(&self) -> bool {
842        matches!(self, EtaCategory::Record | EtaCategory::Function)
843    }
844    /// Return a static label.
845    pub fn label(&self) -> &'static str {
846        match self {
847            EtaCategory::Record => "record",
848            EtaCategory::Function => "function",
849            EtaCategory::Inductive => "inductive",
850            EtaCategory::Proposition => "proposition",
851            EtaCategory::Primitive => "primitive",
852        }
853    }
854}
855/// Performs η-expansion for structure (record) types.
856///
857/// For a structure type `S` with constructor `S.mk` and fields `f₁, f₂, …, fₙ`,
858/// η-expanding an expression `e : S` yields:
859/// ```text
860/// S.mk (e.f₁) (e.f₂) … (e.fₙ)
861/// ```
862/// which is definitionally equal to `e` by the η-rule for structures.
863pub struct StructureEta<'a> {
864    env: &'a Environment,
865}
866impl<'a> StructureEta<'a> {
867    /// Create a new `StructureEta` helper bound to the given environment.
868    pub fn new(env: &'a Environment) -> Self {
869        StructureEta { env }
870    }
871    /// Return `true` if `ty` is a `Const` whose name resolves in the environment
872    /// to a structure-like inductive (exactly 1 constructor, 0 indices, non-recursive).
873    pub fn is_structure_type(&self, ty: &Expr) -> bool {
874        let name = match ty {
875            Expr::Const(n, _) => n,
876            _ => {
877                if let Some(n) = head_const(ty) {
878                    return self.env.is_structure_like(n);
879                }
880                return false;
881            }
882        };
883        self.env.is_structure_like(name)
884    }
885    /// Collect `(field_name, field_type)` pairs for the structure `struct_name`.
886    ///
887    /// This works by looking up the unique constructor in the environment and
888    /// counting `num_fields` from its `ConstructorVal`.  The field *types* are
889    /// obtained from the constructor's type by stripping the leading `Pi`-binders
890    /// that correspond to the inductive parameters, then collecting the remaining
891    /// domain types.
892    ///
893    /// Returns an empty `Vec` when the structure is not found.
894    pub fn collect_field_types(&self, struct_name: &Name) -> Vec<(Name, Expr)> {
895        let iv = match self.env.get_inductive_val(struct_name) {
896            Some(v) => v,
897            None => return vec![],
898        };
899        let ctor_name = match iv.ctors.first() {
900            Some(n) => n.clone(),
901            None => return vec![],
902        };
903        let cv = match self.env.get_constructor_val(&ctor_name) {
904            Some(v) => v,
905            None => return vec![],
906        };
907        let ctor_ty = match self.env.get_type(&ctor_name) {
908            Some(t) => t.clone(),
909            None => return vec![],
910        };
911        let num_params = cv.num_params as usize;
912        let num_fields = cv.num_fields as usize;
913        let mut current = &ctor_ty;
914        let mut skipped = 0usize;
915        let mut fields = Vec::with_capacity(num_fields);
916        while let Expr::Pi(_, name, domain, codomain) = current {
917            if skipped < num_params {
918                skipped += 1;
919                current = codomain;
920            } else if fields.len() < num_fields {
921                fields.push((name.clone(), *domain.clone()));
922                current = codomain;
923            } else {
924                break;
925            }
926        }
927        fields
928    }
929    /// Build a `Vec` of projection expressions `Proj(struct_name, i, expr)`
930    /// for `i` in `0..num_fields`.
931    pub fn make_proj_chain(&self, expr: &Expr, struct_name: &Name, num_fields: usize) -> Vec<Expr> {
932        (0..num_fields)
933            .map(|i| Expr::Proj(struct_name.clone(), i as u32, Box::new(expr.clone())))
934            .collect()
935    }
936    /// η-expand `expr` at type `ty`.
937    ///
938    /// If `ty` is a structure type, returns
939    /// `App(… App(S.mk, proj_0(expr)) …, proj_{n-1}(expr))`.
940    /// Returns `None` when `ty` is not a known structure type.
941    pub fn eta_expand_struct(&self, expr: &Expr, ty: &Expr) -> Option<Expr> {
942        let struct_name = head_const(ty)?.clone();
943        if !self.env.is_structure_like(&struct_name) {
944            return None;
945        }
946        let iv = self.env.get_inductive_val(&struct_name)?;
947        let ctor_name = iv.ctors.first()?.clone();
948        let cv = self.env.get_constructor_val(&ctor_name)?;
949        let num_fields = cv.num_fields as usize;
950        let ctor_levels: Vec<Level> = vec![];
951        let ctor_const = Expr::Const(ctor_name, ctor_levels);
952        let projections = self.make_proj_chain(expr, &struct_name, num_fields);
953        if projections.is_empty() {
954            return Some(ctor_const);
955        }
956        let result = projections.into_iter().fold(ctor_const, |acc, proj| {
957            Expr::App(Box::new(acc), Box::new(proj))
958        });
959        Some(result)
960    }
961}
962/// Represents the shape of a structure expression for comparison.
963#[allow(dead_code)]
964#[derive(Debug, Clone, PartialEq, Eq)]
965pub enum StructShape {
966    /// A constructor applied to `n` arguments.
967    Ctor { name: String, arity: u32 },
968    /// A projection of field `i` from an expression.
969    Proj { field_index: u32 },
970    /// Any other expression shape.
971    Other,
972}
973#[allow(dead_code)]
974impl StructShape {
975    /// Create a constructor shape.
976    pub fn ctor(name: impl Into<String>, arity: u32) -> Self {
977        StructShape::Ctor {
978            name: name.into(),
979            arity,
980        }
981    }
982    /// Create a projection shape.
983    pub fn proj(field_index: u32) -> Self {
984        StructShape::Proj { field_index }
985    }
986    /// Return true if this is a constructor.
987    pub fn is_ctor(&self) -> bool {
988        matches!(self, StructShape::Ctor { .. })
989    }
990    /// Return true if this is a projection.
991    pub fn is_proj(&self) -> bool {
992        matches!(self, StructShape::Proj { .. })
993    }
994    /// Return arity if this is a constructor, else None.
995    pub fn arity(&self) -> Option<u32> {
996        match self {
997            StructShape::Ctor { arity, .. } => Some(*arity),
998            _ => None,
999        }
1000    }
1001}
1002/// A simple eta rewrite engine that applies a set of rules iteratively.
1003#[allow(dead_code)]
1004pub struct EtaRewriteEngine {
1005    proj_rules: ProjectionRewriteSet,
1006    max_steps: u32,
1007    steps_taken: u32,
1008}
1009#[allow(dead_code)]
1010impl EtaRewriteEngine {
1011    /// Create a new engine with the given rule set.
1012    pub fn new(proj_rules: ProjectionRewriteSet, max_steps: u32) -> Self {
1013        EtaRewriteEngine {
1014            proj_rules,
1015            max_steps,
1016            steps_taken: 0,
1017        }
1018    }
1019    /// Return the number of steps taken so far.
1020    pub fn steps_taken(&self) -> u32 {
1021        self.steps_taken
1022    }
1023    /// Return whether the engine has reached its step limit.
1024    pub fn is_exhausted(&self) -> bool {
1025        self.steps_taken >= self.max_steps
1026    }
1027    /// Try to apply a projection rule for the given projector name.
1028    /// Returns `Some(field_index)` if a rule applies, else `None`.
1029    pub fn apply_proj(&mut self, projector: &str) -> Option<u32> {
1030        if self.is_exhausted() {
1031            return None;
1032        }
1033        if let Some(rule) = self.proj_rules.find_by_projector(projector) {
1034            let idx = rule.field_index;
1035            self.steps_taken += 1;
1036            Some(idx)
1037        } else {
1038            None
1039        }
1040    }
1041    /// Reset the step counter.
1042    pub fn reset(&mut self) {
1043        self.steps_taken = 0;
1044    }
1045    /// Return the number of projection rules available.
1046    pub fn rule_count(&self) -> usize {
1047        self.proj_rules.len()
1048    }
1049}
1050/// A structure projection descriptor mapping projector name to field index.
1051#[allow(dead_code)]
1052#[derive(Debug, Clone)]
1053pub struct ProjectionDescriptor {
1054    pub structure_name: String,
1055    pub projector_name: String,
1056    pub field_index: u32,
1057}
1058#[allow(dead_code)]
1059impl ProjectionDescriptor {
1060    /// Create a new projection descriptor.
1061    pub fn new(
1062        structure_name: impl Into<String>,
1063        projector_name: impl Into<String>,
1064        field_index: u32,
1065    ) -> Self {
1066        ProjectionDescriptor {
1067            structure_name: structure_name.into(),
1068            projector_name: projector_name.into(),
1069            field_index,
1070        }
1071    }
1072}
1073/// A projection normalization rewrite: projector applied to constructor.
1074#[allow(dead_code)]
1075#[derive(Debug, Clone)]
1076pub struct ProjectionRewrite {
1077    pub ctor_name: String,
1078    pub projector_name: String,
1079    pub field_index: u32,
1080}
1081#[allow(dead_code)]
1082impl ProjectionRewrite {
1083    /// Create a projection rewrite.
1084    pub fn new(
1085        ctor_name: impl Into<String>,
1086        projector_name: impl Into<String>,
1087        field_index: u32,
1088    ) -> Self {
1089        ProjectionRewrite {
1090            ctor_name: ctor_name.into(),
1091            projector_name: projector_name.into(),
1092            field_index,
1093        }
1094    }
1095    /// Format as a rewrite rule string.
1096    pub fn as_rule(&self) -> String {
1097        format!(
1098            "{} ({}.mk f0 ... f{} ...) → f{}",
1099            self.projector_name, self.ctor_name, self.field_index, self.field_index
1100        )
1101    }
1102}
1103/// Batch eta-categorization results.
1104#[allow(dead_code)]
1105pub struct EtaCategorizer {
1106    entries: Vec<(u64, EtaCategory)>,
1107}
1108#[allow(dead_code)]
1109impl EtaCategorizer {
1110    /// Create a new categorizer.
1111    pub fn new() -> Self {
1112        EtaCategorizer {
1113            entries: Vec::new(),
1114        }
1115    }
1116    /// Assign a category to an expression id.
1117    pub fn assign(&mut self, id: u64, cat: EtaCategory) {
1118        self.entries.push((id, cat));
1119    }
1120    /// Look up category for an id.
1121    pub fn get(&self, id: u64) -> Option<EtaCategory> {
1122        self.entries.iter().find(|(i, _)| *i == id).map(|(_, c)| *c)
1123    }
1124    /// Return ids that need eta-expansion.
1125    pub fn needs_eta_ids(&self) -> Vec<u64> {
1126        self.entries
1127            .iter()
1128            .filter(|(_, c)| c.needs_eta())
1129            .map(|(i, _)| *i)
1130            .collect()
1131    }
1132    /// Return the number of categorized expressions.
1133    pub fn len(&self) -> usize {
1134        self.entries.len()
1135    }
1136    /// Return whether any expressions are categorized.
1137    pub fn is_empty(&self) -> bool {
1138        self.entries.is_empty()
1139    }
1140    /// Count expressions in each category.
1141    pub fn count_by_category(&self) -> [(EtaCategory, usize); 5] {
1142        let cats = [
1143            EtaCategory::Record,
1144            EtaCategory::Function,
1145            EtaCategory::Inductive,
1146            EtaCategory::Proposition,
1147            EtaCategory::Primitive,
1148        ];
1149        cats.map(|c| {
1150            let count = self.entries.iter().filter(|(_, cat)| *cat == c).count();
1151            (c, count)
1152        })
1153    }
1154}
1155/// Eta canonicalization: maps expression ids to their canonical eta-normal form.
1156#[allow(dead_code)]
1157pub struct EtaCanonMap {
1158    map: Vec<(u64, u64)>,
1159}
1160#[allow(dead_code)]
1161impl EtaCanonMap {
1162    /// Create an empty canon map.
1163    pub fn new() -> Self {
1164        EtaCanonMap { map: Vec::new() }
1165    }
1166    /// Record that `original` canonicalizes to `canon`.
1167    pub fn insert(&mut self, original: u64, canon: u64) {
1168        if let Some(e) = self.map.iter_mut().find(|(o, _)| *o == original) {
1169            e.1 = canon;
1170        } else {
1171            self.map.push((original, canon));
1172        }
1173    }
1174    /// Return the canonical form of `original`, or itself if not mapped.
1175    pub fn canonical(&self, original: u64) -> u64 {
1176        self.map
1177            .iter()
1178            .find(|(o, _)| *o == original)
1179            .map_or(original, |(_, c)| *c)
1180    }
1181    /// Return the number of mappings.
1182    pub fn len(&self) -> usize {
1183        self.map.len()
1184    }
1185    /// Return whether the map is empty.
1186    pub fn is_empty(&self) -> bool {
1187        self.map.is_empty()
1188    }
1189    /// Return all original ids that map to `canon`.
1190    pub fn originals_of(&self, canon: u64) -> Vec<u64> {
1191        self.map
1192            .iter()
1193            .filter(|(_, c)| *c == canon)
1194            .map(|(o, _)| *o)
1195            .collect()
1196    }
1197}
1198/// A dependency graph tracking which expressions depend on eta-normal forms.
1199#[allow(dead_code)]
1200pub struct EtaGraph {
1201    edges: Vec<(u64, u64)>,
1202}
1203#[allow(dead_code)]
1204impl EtaGraph {
1205    /// Create an empty graph.
1206    pub fn new() -> Self {
1207        EtaGraph { edges: Vec::new() }
1208    }
1209    /// Add a dependency edge: `from` depends on `to`.
1210    pub fn add_edge(&mut self, from: u64, to: u64) {
1211        if !self.has_edge(from, to) {
1212            self.edges.push((from, to));
1213        }
1214    }
1215    /// Return whether an edge exists.
1216    pub fn has_edge(&self, from: u64, to: u64) -> bool {
1217        self.edges.iter().any(|&(f, t)| f == from && t == to)
1218    }
1219    /// Return all expressions that depend on `id`.
1220    pub fn dependents_of(&self, id: u64) -> Vec<u64> {
1221        self.edges
1222            .iter()
1223            .filter(|(_, t)| *t == id)
1224            .map(|(f, _)| *f)
1225            .collect()
1226    }
1227    /// Return all expressions that `id` depends on.
1228    pub fn dependencies_of(&self, id: u64) -> Vec<u64> {
1229        self.edges
1230            .iter()
1231            .filter(|(f, _)| *f == id)
1232            .map(|(_, t)| *t)
1233            .collect()
1234    }
1235    /// Return the total edge count.
1236    pub fn edge_count(&self) -> usize {
1237        self.edges.len()
1238    }
1239    /// Return whether the graph has no edges.
1240    pub fn is_empty(&self) -> bool {
1241        self.edges.is_empty()
1242    }
1243    /// Remove all edges involving `id`.
1244    pub fn remove_node(&mut self, id: u64) {
1245        self.edges.retain(|(f, t)| *f != id && *t != id);
1246    }
1247}
1248/// An occurrence of an eta-redex within a larger expression.
1249#[allow(dead_code)]
1250#[derive(Debug, Clone)]
1251pub struct EtaRedex {
1252    /// Path (sequence of child indices) to the redex root.
1253    pub path: Vec<u32>,
1254    /// The structure name of the redex.
1255    pub struct_name: String,
1256    /// The expression id of the inner term.
1257    pub inner_id: u64,
1258}
1259#[allow(dead_code)]
1260impl EtaRedex {
1261    /// Create a new eta-redex at the given path.
1262    pub fn new(path: Vec<u32>, struct_name: impl Into<String>, inner_id: u64) -> Self {
1263        EtaRedex {
1264            path,
1265            struct_name: struct_name.into(),
1266            inner_id,
1267        }
1268    }
1269    /// Return true if this redex is at the top level (empty path).
1270    pub fn is_top_level(&self) -> bool {
1271        self.path.is_empty()
1272    }
1273    /// Return the nesting depth of this redex.
1274    pub fn depth(&self) -> usize {
1275        self.path.len()
1276    }
1277}
1278/// Tracks depth of eta-expansion nesting.
1279#[allow(dead_code)]
1280pub struct EtaDepthTracker {
1281    stack: Vec<String>,
1282}
1283#[allow(dead_code)]
1284impl EtaDepthTracker {
1285    /// Create an empty tracker.
1286    pub fn new() -> Self {
1287        EtaDepthTracker { stack: Vec::new() }
1288    }
1289    /// Push a structure context onto the stack.
1290    pub fn push(&mut self, struct_name: &str) {
1291        self.stack.push(struct_name.to_string());
1292    }
1293    /// Pop the current structure context.
1294    pub fn pop(&mut self) -> Option<String> {
1295        self.stack.pop()
1296    }
1297    /// Return the current expansion depth.
1298    pub fn depth(&self) -> usize {
1299        self.stack.len()
1300    }
1301    /// Return whether we are currently inside any expansion.
1302    pub fn is_nested(&self) -> bool {
1303        !self.stack.is_empty()
1304    }
1305    /// Return the innermost structure name.
1306    pub fn current(&self) -> Option<&str> {
1307        self.stack.last().map(|s| s.as_str())
1308    }
1309    /// Return the full nesting path as a dot-separated string.
1310    pub fn path(&self) -> String {
1311        self.stack.join(".")
1312    }
1313    /// Return whether a given structure appears anywhere in the nesting.
1314    pub fn contains(&self, name: &str) -> bool {
1315        self.stack.iter().any(|s| s == name)
1316    }
1317}
1318/// A record update: replace one field of a structure expression.
1319#[allow(dead_code)]
1320#[derive(Debug, Clone)]
1321pub struct RecordUpdate {
1322    pub expr_id: u64,
1323    pub struct_name: String,
1324    pub field_index: u32,
1325    pub new_value_id: u64,
1326}
1327#[allow(dead_code)]
1328impl RecordUpdate {
1329    /// Create a record update.
1330    pub fn new(
1331        expr_id: u64,
1332        struct_name: impl Into<String>,
1333        field_index: u32,
1334        new_value_id: u64,
1335    ) -> Self {
1336        RecordUpdate {
1337            expr_id,
1338            struct_name: struct_name.into(),
1339            field_index,
1340            new_value_id,
1341        }
1342    }
1343    /// Format a description of this update.
1344    pub fn describe(&self) -> String {
1345        format!(
1346            "update {}.{} of expr #{} with expr #{}",
1347            self.struct_name, self.field_index, self.expr_id, self.new_value_id
1348        )
1349    }
1350}
1351#[allow(dead_code)]
1352#[derive(Debug, Clone)]
1353pub struct EtaChangeEntry {
1354    pub expr_id: u64,
1355    pub kind: EtaChangeKind,
1356    pub pass_num: u32,
1357}
1358/// Eta-expansion state machine for iterative expansion.
1359#[allow(dead_code)]
1360pub struct EtaStateMachine {
1361    state: EtaState,
1362    structure_name: Option<String>,
1363    field_count: u32,
1364    processed_fields: u32,
1365}
1366#[allow(dead_code)]
1367impl EtaStateMachine {
1368    /// Create a machine in the Idle state.
1369    pub fn new() -> Self {
1370        EtaStateMachine {
1371            state: EtaState::Idle,
1372            structure_name: None,
1373            field_count: 0,
1374            processed_fields: 0,
1375        }
1376    }
1377    /// Start expansion for a given structure and field count.
1378    pub fn start(&mut self, name: &str, field_count: u32) {
1379        self.state = EtaState::Expanding;
1380        self.structure_name = Some(name.to_string());
1381        self.field_count = field_count;
1382        self.processed_fields = 0;
1383    }
1384    /// Record one field being processed.
1385    pub fn process_field(&mut self) -> bool {
1386        if self.state != EtaState::Expanding {
1387            return false;
1388        }
1389        self.processed_fields += 1;
1390        if self.processed_fields >= self.field_count {
1391            self.state = EtaState::Done;
1392        }
1393        true
1394    }
1395    /// Mark the expansion as failed.
1396    pub fn fail(&mut self) {
1397        self.state = EtaState::Failed;
1398    }
1399    /// Return whether expansion is complete.
1400    pub fn is_done(&self) -> bool {
1401        self.state == EtaState::Done
1402    }
1403    /// Return whether expansion failed.
1404    pub fn is_failed(&self) -> bool {
1405        self.state == EtaState::Failed
1406    }
1407    /// Return whether currently expanding.
1408    pub fn is_expanding(&self) -> bool {
1409        self.state == EtaState::Expanding
1410    }
1411    /// Return remaining fields to process.
1412    pub fn remaining(&self) -> u32 {
1413        self.field_count.saturating_sub(self.processed_fields)
1414    }
1415}
1416/// Represents whether an expression is in eta-long normal form.
1417#[allow(dead_code)]
1418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1419pub enum EtaLongStatus {
1420    /// Expression is already in eta-long normal form.
1421    EtaLong,
1422    /// Expression is not eta-long; needs expansion.
1423    NotEtaLong,
1424    /// Cannot determine without full type information.
1425    Unknown,
1426}
1427#[allow(dead_code)]
1428impl EtaLongStatus {
1429    /// Return true if this is the EtaLong variant.
1430    pub fn is_eta_long(&self) -> bool {
1431        *self == EtaLongStatus::EtaLong
1432    }
1433}
1434/// Final result of running the eta normalization pass.
1435#[allow(dead_code)]
1436#[derive(Debug, Clone)]
1437pub struct EtaPassResult {
1438    pub summary: EtaNormRunSummary,
1439    pub changed_ids: Vec<u64>,
1440}
1441#[allow(dead_code)]
1442impl EtaPassResult {
1443    /// Create an empty result.
1444    pub fn new() -> Self {
1445        EtaPassResult {
1446            summary: EtaNormRunSummary::new(),
1447            changed_ids: Vec::new(),
1448        }
1449    }
1450    /// Return whether any expressions were changed.
1451    pub fn any_changes(&self) -> bool {
1452        !self.changed_ids.is_empty()
1453    }
1454}
1455/// A pass that flattens nested structure constructor applications.
1456#[allow(dead_code)]
1457pub struct StructFlatteningPass {
1458    pub processed: u64,
1459    pub flattened: u64,
1460}
1461#[allow(dead_code)]
1462impl StructFlatteningPass {
1463    /// Create a new pass.
1464    pub fn new() -> Self {
1465        StructFlatteningPass {
1466            processed: 0,
1467            flattened: 0,
1468        }
1469    }
1470    /// Record that an expression was processed.
1471    pub fn record_processed(&mut self) {
1472        self.processed += 1;
1473    }
1474    /// Record that a flattening was performed.
1475    pub fn record_flattened(&mut self) {
1476        self.flattened += 1;
1477    }
1478    /// Return the fraction of processed expressions that were flattened.
1479    pub fn flatten_rate(&self) -> f64 {
1480        if self.processed == 0 {
1481            0.0
1482        } else {
1483            self.flattened as f64 / self.processed as f64
1484        }
1485    }
1486}
1487/// A record field descriptor for eta-expansion analysis.
1488#[allow(dead_code)]
1489#[derive(Debug, Clone)]
1490pub struct FieldDescriptor {
1491    pub name: String,
1492    pub index: u32,
1493    pub is_prop: bool,
1494}
1495#[allow(dead_code)]
1496impl FieldDescriptor {
1497    /// Create a new field descriptor.
1498    pub fn new(name: impl Into<String>, index: u32, is_prop: bool) -> Self {
1499        FieldDescriptor {
1500            name: name.into(),
1501            index,
1502            is_prop,
1503        }
1504    }
1505    /// Return whether this field is a computation field (not Prop).
1506    pub fn is_data(&self) -> bool {
1507        !self.is_prop
1508    }
1509}
1510/// Structural injectivity analysis: can a constructor be distinguished by its arguments?
1511#[allow(dead_code)]
1512pub struct InjectivityChecker {
1513    injective: Vec<String>,
1514}
1515#[allow(dead_code)]
1516impl InjectivityChecker {
1517    /// Create an empty checker.
1518    pub fn new() -> Self {
1519        InjectivityChecker {
1520            injective: Vec::new(),
1521        }
1522    }
1523    /// Mark a constructor as injective.
1524    pub fn mark_injective(&mut self, ctor: &str) {
1525        if !self.injective.contains(&ctor.to_string()) {
1526            self.injective.push(ctor.to_string());
1527        }
1528    }
1529    /// Return whether a constructor is known to be injective.
1530    pub fn is_injective(&self, ctor: &str) -> bool {
1531        self.injective.iter().any(|s| s == ctor)
1532    }
1533    /// Return all injective constructors.
1534    pub fn injective_ctors(&self) -> &[String] {
1535        &self.injective
1536    }
1537    /// Return the number of known injective constructors.
1538    pub fn count(&self) -> usize {
1539        self.injective.len()
1540    }
1541}