Skip to main content

oxilean_kernel/subst/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::node_memo::NodeMemo;
6use crate::Node;
7use crate::{Expr, FVarId};
8use std::collections::HashMap;
9use std::rc::Rc;
10
11use super::types::{
12    ConfigNode, DecisionNode, Either2, FlatSubstitution, FocusStack, LabelSet, NonEmptyVec,
13    PathBuf, RewriteRule, RewriteRuleSet, SimpleDag, SlidingSum, SmallMap, SparseVec, StackCalc,
14    StatSummary, Stopwatch, StringPool, SubstStats, Substitution, TokenBucket, TransformStat,
15    TransitiveClosure, VersionedRecord, WindowIterator, WriteOnce,
16};
17
18/// Instantiate BVar(0) with arg in body, shifting down all other loose BVars.
19///
20/// This is the core operation for β-reduction: `(λx.body) arg → body[arg/x]`.
21///
22/// `arg` is expressed in the context *outside* the redex. When the
23/// substitution site sits under `depth` additional binders inside `body`,
24/// every loose bound variable of `arg` must be lifted by `depth` so it keeps
25/// referring to the same outer binder (otherwise it would be captured by the
26/// inner binders — the classic de Bruijn capture bug, exposed by open-term
27/// reduction such as iota under a `Pi` body during def-eq).
28///
29/// # Sharing
30///
31/// The result shares with `body` every subterm the substitution does not
32/// reach, and keeps what `body` shares:
33///
34/// * a subterm with no loose bound variable at or above the current depth
35///   (`range <= depth`, read off the [`Node`] in O(1)) is returned as the
36///   SAME allocation, not copied;
37/// * a subterm that IS rebuilt and has several owners is rebuilt once per
38///   depth it is reached at — the result is remembered for the walk
39///   (the kernel-private `node_memo` module) and the second path to it gets
40///   the same allocation;
41/// * `arg` goes in as one allocation per binder depth, however many times
42///   the variable occurs there.
43///
44/// So substituting into a term of `n` distinct nodes builds at most `n` new
45/// ones per depth, whatever the term's size as a tree.
46///
47/// # Fuel
48///
49/// One unit per subterm the walk reaches, whether it rebuilds it, shares it
50/// unchanged or finds it in the memo (see [`crate::fuel`]). Every node the
51/// walk allocates is therefore charged.
52pub fn instantiate(body: &Expr, arg: &Expr) -> Expr {
53    if body.range() == 0 {
54        // Closed: no variable to replace and none to shift down.
55        return body.clone();
56    }
57    let mut walk = Instantiate {
58        args: Args::Terms(std::slice::from_ref(&arg)),
59        rebuilt: NodeMemo::new(),
60        lifted: Vec::new(),
61    };
62    walk.expr(body, 0)
63}
64
65/// Instantiate the `args.len()` outermost loose bound variables of `body`
66/// at once: `BVar(0)` with the LAST argument, `BVar(1)` with the one before
67/// it, and so on, shifting every other loose variable down by `args.len()`.
68///
69/// This is what `(λx₁ … xₙ. body) a₁ … aₙ` reduces to — `body` being what
70/// stands under the `n` binders and `args` being `[a₁, …, aₙ]` — and equal to
71/// `n` nested calls of [`instantiate`], in one walk of `body` instead of `n`.
72/// Sharing and fuel are as documented on [`instantiate`]. An argument that
73/// needs no lifting goes into the result as the allocation the caller holds.
74pub(crate) fn instantiate_rev_nodes(body: &Expr, args: &[&Node]) -> Expr {
75    if args.is_empty() || body.range() == 0 {
76        return body.clone();
77    }
78    let mut walk = Instantiate {
79        args: Args::Nodes(args),
80        rebuilt: NodeMemo::new(),
81        lifted: Vec::new(),
82    };
83    walk.expr(body, 0)
84}
85
86/// The arguments of one substitution, outermost binder first.
87enum Args<'arg> {
88    /// Terms the caller holds by reference.
89    Terms(&'arg [&'arg Expr]),
90    /// Subterms the caller holds as shared allocations.
91    Nodes(&'arg [&'arg Node]),
92}
93
94impl Args<'_> {
95    fn len(&self) -> usize {
96        match self {
97            Args::Terms(terms) => terms.len(),
98            Args::Nodes(nodes) => nodes.len(),
99        }
100    }
101
102    /// Argument `index` as it must appear under `depth` binders, as an
103    /// allocation: the caller's own when it needs no lifting.
104    fn lifted(&self, index: usize, depth: u32) -> Node {
105        match self {
106            Args::Terms(terms) => {
107                Node::new(crate::expr_util::lift_loose_bvars(terms[index], depth, 0))
108            }
109            Args::Nodes(nodes) => {
110                let node = nodes[index];
111                if depth == 0 || node.range() == 0 {
112                    node.clone()
113                } else {
114                    Node::new(crate::expr_util::lift_loose_bvars(node, depth, 0))
115                }
116            }
117        }
118    }
119
120    /// Argument `index` as it must appear under `depth` binders, as a term.
121    fn lifted_term(&self, index: usize, depth: u32) -> Expr {
122        let term: &Expr = match self {
123            Args::Terms(terms) => terms[index],
124            Args::Nodes(nodes) => nodes[index],
125        };
126        crate::expr_util::lift_loose_bvars(term, depth, 0)
127    }
128}
129
130/// One substitution: the arguments, and what the walk has built so far.
131struct Instantiate<'term, 'arg> {
132    /// What replaces the outermost loose variables of the body: the last
133    /// argument replaces `BVar(0)`.
134    args: Args<'arg>,
135    /// The rebuilt form of each shared subterm of the body the walk has
136    /// finished, by subterm and depth.
137    rebuilt: NodeMemo<'term, Node>,
138    /// Each argument lifted to each binder depth its variable has been met
139    /// at, by (depth, argument). Almost always a handful of entries, hence
140    /// a list.
141    lifted: Vec<(u32, usize, Node)>,
142}
143
144impl<'term> Instantiate<'term, '_> {
145    /// Which argument replaces `BVar(index)` under `depth` binders, if it is
146    /// one of the substituted variables. (`index >= depth` is the caller's.)
147    fn arg_index(&self, index: u32, depth: u32) -> Option<usize> {
148        let count = self.args.len();
149        // `usize` is at least 32 bits wide on every supported target.
150        let offset = (index - depth) as usize;
151        (offset < count).then(|| count - 1 - offset)
152    }
153
154    /// How far a loose variable that is NOT substituted moves down.
155    fn shift(&self) -> u32 {
156        // More arguments than a `u32` can count cannot be reached: each one
157        // is a binder of the body. Saturating keeps the arithmetic total.
158        u32::try_from(self.args.len()).unwrap_or(u32::MAX)
159    }
160
161    /// The substitution applied to a child edge at binder depth `depth`.
162    fn node(&mut self, node: &'term Node, depth: u32) -> Node {
163        crate::fuel::charge(1);
164        if node.range() <= depth {
165            // No loose bound variable at or above `depth`: untouched.
166            return node.clone();
167        }
168        if let Expr::BVar(index) = &**node {
169            // `range > depth` for a variable means `index >= depth`: one of
170            // the substituted variables, or one bound further out.
171            return match self.arg_index(*index, depth) {
172                Some(which) => self.arg_at(which, depth),
173                None => Node::new(Expr::BVar(index.saturating_sub(self.shift()))),
174            };
175        }
176        let shared = node.is_shared();
177        if shared {
178            if let Some(done) = self.rebuilt.get(node, depth) {
179                return done.clone();
180            }
181        }
182        let result = Node::new(self.rebuild(node, depth));
183        if shared {
184            self.rebuilt.insert(node, depth, result.clone());
185        }
186        result
187    }
188
189    /// Argument `which` as it must appear under `depth` binders: lifted once
190    /// per depth and shared by every occurrence of its variable there.
191    fn arg_at(&mut self, which: usize, depth: u32) -> Node {
192        if let Some((_, _, lifted)) = self
193            .lifted
194            .iter()
195            .find(|(d, w, _)| *d == depth && *w == which)
196        {
197            return lifted.clone();
198        }
199        let lifted = self.args.lifted(which, depth);
200        self.lifted.push((depth, which, lifted.clone()));
201        lifted
202    }
203
204    /// The substitution applied to the body itself.
205    fn expr(&mut self, expr: &'term Expr, depth: u32) -> Expr {
206        crate::fuel::charge(1);
207        self.rebuild(expr, depth)
208    }
209
210    /// Rebuild `expr`, which has a loose bound variable at or above `depth`.
211    fn rebuild(&mut self, expr: &'term Expr, depth: u32) -> Expr {
212        match expr {
213            Expr::BVar(n) => {
214                if *n < depth {
215                    return expr.clone();
216                }
217                match self.arg_index(*n, depth) {
218                    Some(which) => self.args.lifted_term(which, depth),
219                    None => Expr::BVar(n.saturating_sub(self.shift())),
220                }
221            }
222            Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
223            Expr::App(f, a) => Expr::App(self.node(f, depth), self.node(a, depth)),
224            Expr::Lam(bi, name, ty, body) => Expr::Lam(
225                *bi,
226                name.clone(),
227                self.node(ty, depth),
228                self.node(body, depth + 1),
229            ),
230            Expr::Pi(bi, name, ty, body) => Expr::Pi(
231                *bi,
232                name.clone(),
233                self.node(ty, depth),
234                self.node(body, depth + 1),
235            ),
236            Expr::Let(name, ty, val, body) => Expr::Let(
237                name.clone(),
238                self.node(ty, depth),
239                self.node(val, depth),
240                self.node(body, depth + 1),
241            ),
242            Expr::Proj(name, idx, e) => Expr::Proj(name.clone(), *idx, self.node(e, depth)),
243        }
244    }
245}
246
247/// Replace FVar with BVar(0), shifting up loose (unbound) BVars.
248///
249/// This is the inverse of instantiation, used when forming binders.
250/// Bound variables (BVars pointing at binders *inside* `expr`) are left
251/// unchanged; only loose BVars — which refer to binders outside `expr`
252/// and therefore move one binder further away when the new binder is
253/// wrapped around the result — are shifted by one.
254///
255/// # Sharing
256///
257/// A subterm the abstraction leaves as it is — it holds neither the
258/// variable nor a loose bound variable to shift — is the SAME allocation in
259/// the result, and a shared subterm that does change is rebuilt once per
260/// depth (as in [`instantiate`]). A subterm with no free variable at all and
261/// no loose bound variable at or above the current depth is passed over
262/// without being visited (both facts are read off the [`Node`]).
263///
264/// # Fuel
265///
266/// One unit per subterm the walk reaches (see [`crate::fuel`]).
267pub fn abstract_expr(expr: &Expr, fvar: FVarId) -> Expr {
268    let mut walk = Abstract {
269        fvar,
270        done: NodeMemo::new(),
271    };
272    crate::fuel::charge(1);
273    match walk.rebuild(expr, 0) {
274        Some(changed) => changed,
275        None => expr.clone(),
276    }
277}
278
279/// One [`abstract_expr`] call. `None` stands for "unchanged" throughout: the
280/// caller keeps the subterm it already holds.
281struct Abstract<'term> {
282    fvar: FVarId,
283    /// The outcome for each shared subterm the walk has finished, by subterm
284    /// and depth.
285    done: NodeMemo<'term, Option<Node>>,
286}
287
288impl<'term> Abstract<'term> {
289    /// The abstraction applied to a child edge under `depth` binders;
290    /// `None` when it leaves the subterm as it is.
291    fn node(&mut self, node: &'term Node, depth: u32) -> Option<Node> {
292        crate::fuel::charge(1);
293        if !node.has_fvar() && node.range() <= depth {
294            // Neither the variable nor a loose bound variable to shift.
295            return None;
296        }
297        if matches!(&**node, Expr::FVar(_) | Expr::BVar(_)) {
298            // A leaf is cheaper to redo than to look up.
299            return self.rebuild(node, depth).map(Node::new);
300        }
301        let shared = node.is_shared();
302        if shared {
303            if let Some(done) = self.done.get(node, depth) {
304                return done.clone();
305            }
306        }
307        let result = self.rebuild(node, depth).map(Node::new);
308        if shared {
309            self.done.insert(node, depth, result.clone());
310        }
311        result
312    }
313
314    /// Rebuild `expr` under `depth` binders; `None` when nothing in it
315    /// changes.
316    fn rebuild(&mut self, expr: &'term Expr, depth: u32) -> Option<Expr> {
317        match expr {
318            Expr::FVar(id) if *id == self.fvar => Some(Expr::BVar(depth)),
319            // Loose: bound outside `expr`; the new binder adds one level.
320            // (An index that cannot be incremented stays where it is: it is
321            // above every binder there can be, so it stays loose.)
322            Expr::BVar(n) if *n >= depth => Some(Expr::BVar(n.saturating_add(1))),
323            Expr::BVar(_) | Expr::FVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => {
324                None
325            }
326            Expr::App(f, a) => {
327                let f_new = self.node(f, depth);
328                let a_new = self.node(a, depth);
329                if f_new.is_none() && a_new.is_none() {
330                    return None;
331                }
332                Some(Expr::App(
333                    f_new.unwrap_or_else(|| f.clone()),
334                    a_new.unwrap_or_else(|| a.clone()),
335                ))
336            }
337            Expr::Lam(bi, name, ty, body) => {
338                let ty_new = self.node(ty, depth);
339                let body_new = self.node(body, depth + 1);
340                if ty_new.is_none() && body_new.is_none() {
341                    return None;
342                }
343                Some(Expr::Lam(
344                    *bi,
345                    name.clone(),
346                    ty_new.unwrap_or_else(|| ty.clone()),
347                    body_new.unwrap_or_else(|| body.clone()),
348                ))
349            }
350            Expr::Pi(bi, name, ty, body) => {
351                let ty_new = self.node(ty, depth);
352                let body_new = self.node(body, depth + 1);
353                if ty_new.is_none() && body_new.is_none() {
354                    return None;
355                }
356                Some(Expr::Pi(
357                    *bi,
358                    name.clone(),
359                    ty_new.unwrap_or_else(|| ty.clone()),
360                    body_new.unwrap_or_else(|| body.clone()),
361                ))
362            }
363            Expr::Let(name, ty, val, body) => {
364                let ty_new = self.node(ty, depth);
365                let val_new = self.node(val, depth);
366                let body_new = self.node(body, depth + 1);
367                if ty_new.is_none() && val_new.is_none() && body_new.is_none() {
368                    return None;
369                }
370                Some(Expr::Let(
371                    name.clone(),
372                    ty_new.unwrap_or_else(|| ty.clone()),
373                    val_new.unwrap_or_else(|| val.clone()),
374                    body_new.unwrap_or_else(|| body.clone()),
375                ))
376            }
377            Expr::Proj(name, idx, e) => {
378                let e_new = self.node(e, depth)?;
379                Some(Expr::Proj(name.clone(), *idx, e_new))
380            }
381        }
382    }
383}
384/// A finite mapping from free-variable identifiers to expressions.
385pub type SubstMap = HashMap<FVarId, Expr>;
386/// Simultaneously substitute `BVar(0)..BVar(k-1)` with `args[0]..args[k-1]`.
387pub fn instantiate_many(expr: &Expr, args: &[Expr]) -> Expr {
388    let k = args.len() as u32;
389    if k == 0 {
390        return expr.clone();
391    }
392    instantiate_many_at(expr, args, k, 0)
393}
394fn instantiate_many_at(expr: &Expr, args: &[Expr], k: u32, offset: u32) -> Expr {
395    // C16b: see `instantiate_at` — one fuel unit per visited (rebuilt) node.
396    crate::fuel::charge(1);
397    match expr {
398        Expr::BVar(n) => {
399            let idx = *n;
400            if idx >= offset && idx < offset + k {
401                args[(idx - offset) as usize].clone()
402            } else if idx >= offset + k {
403                Expr::BVar(idx - k)
404            } else {
405                expr.clone()
406            }
407        }
408        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
409        Expr::App(f, a) => Expr::App(
410            Node::new(instantiate_many_at(f, args, k, offset)),
411            Node::new(instantiate_many_at(a, args, k, offset)),
412        ),
413        Expr::Lam(bi, name, ty, body) => Expr::Lam(
414            *bi,
415            name.clone(),
416            Node::new(instantiate_many_at(ty, args, k, offset)),
417            Node::new(instantiate_many_at(body, args, k, offset + 1)),
418        ),
419        Expr::Pi(bi, name, ty, body) => Expr::Pi(
420            *bi,
421            name.clone(),
422            Node::new(instantiate_many_at(ty, args, k, offset)),
423            Node::new(instantiate_many_at(body, args, k, offset + 1)),
424        ),
425        Expr::Let(name, ty, val, body) => Expr::Let(
426            name.clone(),
427            Node::new(instantiate_many_at(ty, args, k, offset)),
428            Node::new(instantiate_many_at(val, args, k, offset)),
429            Node::new(instantiate_many_at(body, args, k, offset + 1)),
430        ),
431        Expr::Proj(name, idx, e) => Expr::Proj(
432            name.clone(),
433            *idx,
434            Node::new(instantiate_many_at(e, args, k, offset)),
435        ),
436    }
437}
438/// Apply a parallel substitution: replace each `FVar(id)` with the corresponding
439/// expression in `map`.
440pub fn parallel_subst(expr: &Expr, map: &SubstMap) -> Expr {
441    if map.is_empty() {
442        return expr.clone();
443    }
444    parallel_subst_impl(expr, map)
445}
446fn parallel_subst_impl(expr: &Expr, map: &SubstMap) -> Expr {
447    // C16b: see `instantiate_at` — one fuel unit per visited (rebuilt) node.
448    crate::fuel::charge(1);
449    match expr {
450        Expr::FVar(id) => {
451            if let Some(replacement) = map.get(id) {
452                replacement.clone()
453            } else {
454                expr.clone()
455            }
456        }
457        Expr::BVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
458        Expr::App(f, a) => Expr::App(
459            Node::new(parallel_subst_impl(f, map)),
460            Node::new(parallel_subst_impl(a, map)),
461        ),
462        Expr::Lam(bi, name, ty, body) => Expr::Lam(
463            *bi,
464            name.clone(),
465            Node::new(parallel_subst_impl(ty, map)),
466            Node::new(parallel_subst_impl(body, map)),
467        ),
468        Expr::Pi(bi, name, ty, body) => Expr::Pi(
469            *bi,
470            name.clone(),
471            Node::new(parallel_subst_impl(ty, map)),
472            Node::new(parallel_subst_impl(body, map)),
473        ),
474        Expr::Let(name, ty, val, body) => Expr::Let(
475            name.clone(),
476            Node::new(parallel_subst_impl(ty, map)),
477            Node::new(parallel_subst_impl(val, map)),
478            Node::new(parallel_subst_impl(body, map)),
479        ),
480        Expr::Proj(name, idx, e) => {
481            Expr::Proj(name.clone(), *idx, Node::new(parallel_subst_impl(e, map)))
482        }
483    }
484}
485/// Shift all `BVar(i)` with `i >= cutoff` up by `amount`.
486pub fn shift_bvars(expr: &Expr, amount: u32, cutoff: u32) -> Expr {
487    // C16b: see `instantiate_at` — one fuel unit per visited (rebuilt) node.
488    crate::fuel::charge(1);
489    match expr {
490        Expr::BVar(i) => {
491            if *i >= cutoff {
492                Expr::BVar(*i + amount)
493            } else {
494                expr.clone()
495            }
496        }
497        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
498        Expr::App(f, a) => Expr::App(
499            Node::new(shift_bvars(f, amount, cutoff)),
500            Node::new(shift_bvars(a, amount, cutoff)),
501        ),
502        Expr::Lam(bi, name, ty, body) => Expr::Lam(
503            *bi,
504            name.clone(),
505            Node::new(shift_bvars(ty, amount, cutoff)),
506            Node::new(shift_bvars(body, amount, cutoff + 1)),
507        ),
508        Expr::Pi(bi, name, ty, body) => Expr::Pi(
509            *bi,
510            name.clone(),
511            Node::new(shift_bvars(ty, amount, cutoff)),
512            Node::new(shift_bvars(body, amount, cutoff + 1)),
513        ),
514        Expr::Let(name, ty, val, body) => Expr::Let(
515            name.clone(),
516            Node::new(shift_bvars(ty, amount, cutoff)),
517            Node::new(shift_bvars(val, amount, cutoff)),
518            Node::new(shift_bvars(body, amount, cutoff + 1)),
519        ),
520        Expr::Proj(name, idx, e) => Expr::Proj(
521            name.clone(),
522            *idx,
523            Node::new(shift_bvars(e, amount, cutoff)),
524        ),
525    }
526}
527/// Collect the set of free `FVarId`s occurring in `expr`.
528pub fn free_vars(expr: &Expr) -> std::collections::HashSet<FVarId> {
529    let mut set = std::collections::HashSet::new();
530    free_vars_impl(expr, &mut set);
531    set
532}
533fn free_vars_impl(expr: &Expr, set: &mut std::collections::HashSet<FVarId>) {
534    match expr {
535        Expr::FVar(id) => {
536            set.insert(*id);
537        }
538        Expr::App(f, a) => {
539            free_vars_impl(f, set);
540            free_vars_impl(a, set);
541        }
542        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
543            free_vars_impl(ty, set);
544            free_vars_impl(body, set);
545        }
546        Expr::Let(_, ty, val, body) => {
547            free_vars_impl(ty, set);
548            free_vars_impl(val, set);
549            free_vars_impl(body, set);
550        }
551        Expr::Proj(_, _, e) => free_vars_impl(e, set),
552        _ => {}
553    }
554}
555/// Return `true` if `fvar` occurs free in `expr`.
556pub fn occurs_free(expr: &Expr, fvar: FVarId) -> bool {
557    match expr {
558        Expr::FVar(id) => *id == fvar,
559        Expr::App(f, a) => occurs_free(f, fvar) || occurs_free(a, fvar),
560        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
561            occurs_free(ty, fvar) || occurs_free(body, fvar)
562        }
563        Expr::Let(_, ty, val, body) => {
564            occurs_free(ty, fvar) || occurs_free(val, fvar) || occurs_free(body, fvar)
565        }
566        Expr::Proj(_, _, e) => occurs_free(e, fvar),
567        _ => false,
568    }
569}
570/// Count how many times `fvar` occurs free in `expr`.
571pub fn count_free_occurrences(expr: &Expr, fvar: FVarId) -> usize {
572    match expr {
573        Expr::FVar(id) => usize::from(*id == fvar),
574        Expr::App(f, a) => count_free_occurrences(f, fvar) + count_free_occurrences(a, fvar),
575        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
576            count_free_occurrences(ty, fvar) + count_free_occurrences(body, fvar)
577        }
578        Expr::Let(_, ty, val, body) => {
579            count_free_occurrences(ty, fvar)
580                + count_free_occurrences(val, fvar)
581                + count_free_occurrences(body, fvar)
582        }
583        Expr::Proj(_, _, e) => count_free_occurrences(e, fvar),
584        _ => 0,
585    }
586}
587/// Like [`instantiate`] but also fills in [`SubstStats`].
588pub fn instantiate_tracked(body: &Expr, arg: &Expr, stats: &mut SubstStats) -> Expr {
589    instantiate_tracked_at(body, arg, 0, stats)
590}
591fn instantiate_tracked_at(expr: &Expr, arg: &Expr, depth: u32, stats: &mut SubstStats) -> Expr {
592    // C16b: see `instantiate_at` — one fuel unit per visited (rebuilt) node.
593    crate::fuel::charge(1);
594    stats.nodes_visited += 1;
595    match expr {
596        Expr::BVar(n) => {
597            if *n == depth {
598                stats.bvar_hits += 1;
599                // Same capture-avoidance as `instantiate_at`: lift the loose
600                // bound variables of `arg` past the binders it is inserted
601                // under.
602                crate::expr_util::lift_loose_bvars(arg, depth, 0)
603            } else if *n > depth {
604                stats.bvar_misses += 1;
605                Expr::BVar(*n - 1)
606            } else {
607                expr.clone()
608            }
609        }
610        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
611        Expr::App(f, a) => Expr::App(
612            Node::new(instantiate_tracked_at(f, arg, depth, stats)),
613            Node::new(instantiate_tracked_at(a, arg, depth, stats)),
614        ),
615        Expr::Lam(bi, name, ty, body) => Expr::Lam(
616            *bi,
617            name.clone(),
618            Node::new(instantiate_tracked_at(ty, arg, depth, stats)),
619            Node::new(instantiate_tracked_at(body, arg, depth + 1, stats)),
620        ),
621        Expr::Pi(bi, name, ty, body) => Expr::Pi(
622            *bi,
623            name.clone(),
624            Node::new(instantiate_tracked_at(ty, arg, depth, stats)),
625            Node::new(instantiate_tracked_at(body, arg, depth + 1, stats)),
626        ),
627        Expr::Let(name, ty, val, body) => Expr::Let(
628            name.clone(),
629            Node::new(instantiate_tracked_at(ty, arg, depth, stats)),
630            Node::new(instantiate_tracked_at(val, arg, depth, stats)),
631            Node::new(instantiate_tracked_at(body, arg, depth + 1, stats)),
632        ),
633        Expr::Proj(name, idx, e) => Expr::Proj(
634            name.clone(),
635            *idx,
636            Node::new(instantiate_tracked_at(e, arg, depth, stats)),
637        ),
638    }
639}
640/// Build a `SubstMap` from parallel slices of ids and replacements.
641pub fn build_subst_map(fvars: &[FVarId], replacements: &[Expr]) -> SubstMap {
642    assert_eq!(fvars.len(), replacements.len());
643    fvars
644        .iter()
645        .zip(replacements.iter())
646        .map(|(id, expr)| (*id, expr.clone()))
647        .collect()
648}
649/// Apply beta-reduction once at the top level if possible.
650pub fn try_beta_reduce(expr: &Expr) -> Option<Expr> {
651    match expr {
652        Expr::App(f, a) => {
653            if let Expr::Lam(_, _, _, body) = f.as_ref() {
654                Some(instantiate(body, a))
655            } else {
656                None
657            }
658        }
659        _ => None,
660    }
661}
662/// Repeatedly apply top-level beta reduction until no more is possible.
663pub fn beta_reduce_head(expr: Expr) -> Expr {
664    let mut curr = expr;
665    while let Some(reduced) = try_beta_reduce(&curr) {
666        curr = reduced;
667    }
668    curr
669}
670/// Substitute a single free variable with an expression.
671pub fn subst_fvar(expr: &Expr, fvar: FVarId, replacement: &Expr) -> Expr {
672    let mut map = SubstMap::new();
673    map.insert(fvar, replacement.clone());
674    parallel_subst(expr, &map)
675}
676/// Check whether `expr` is in weak head normal form with respect to beta.
677pub fn is_whnf_beta(expr: &Expr) -> bool {
678    match expr {
679        Expr::App(f, _) => match f.as_ref() {
680            Expr::Lam(_, _, _, _) => false,
681            other => is_whnf_beta(other),
682        },
683        _ => true,
684    }
685}
686/// Collect the indices of all loose bound variables.
687pub fn collect_loose_bvar_indices(expr: &Expr) -> Vec<u32> {
688    let mut indices = Vec::new();
689    collect_loose_bvar_impl(expr, 0, &mut indices);
690    indices.sort();
691    indices.dedup();
692    indices
693}
694fn collect_loose_bvar_impl(expr: &Expr, depth: u32, result: &mut Vec<u32>) {
695    match expr {
696        Expr::BVar(n) if *n >= depth => {
697            result.push(*n - depth);
698        }
699        Expr::BVar(_) => {}
700        Expr::App(f, a) => {
701            collect_loose_bvar_impl(f, depth, result);
702            collect_loose_bvar_impl(a, depth, result);
703        }
704        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
705            collect_loose_bvar_impl(ty, depth, result);
706            collect_loose_bvar_impl(body, depth + 1, result);
707        }
708        Expr::Let(_, ty, val, body) => {
709            collect_loose_bvar_impl(ty, depth, result);
710            collect_loose_bvar_impl(val, depth, result);
711            collect_loose_bvar_impl(body, depth + 1, result);
712        }
713        Expr::Proj(_, _, e) => collect_loose_bvar_impl(e, depth, result),
714        _ => {}
715    }
716}
717/// Apply a parallel substitution and collect profiling statistics.
718pub fn parallel_subst_tracked(expr: &Expr, map: &SubstMap, stats: &mut SubstStats) -> Expr {
719    if map.is_empty() {
720        return expr.clone();
721    }
722    parallel_subst_tracked_impl(expr, map, stats)
723}
724fn parallel_subst_tracked_impl(expr: &Expr, map: &SubstMap, stats: &mut SubstStats) -> Expr {
725    stats.nodes_visited += 1;
726    match expr {
727        Expr::FVar(id) => {
728            if let Some(r) = map.get(id) {
729                stats.fvar_hits += 1;
730                r.clone()
731            } else {
732                expr.clone()
733            }
734        }
735        Expr::BVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
736        Expr::App(f, a) => Expr::App(
737            Node::new(parallel_subst_tracked_impl(f, map, stats)),
738            Node::new(parallel_subst_tracked_impl(a, map, stats)),
739        ),
740        Expr::Lam(bi, name, ty, body) => Expr::Lam(
741            *bi,
742            name.clone(),
743            Node::new(parallel_subst_tracked_impl(ty, map, stats)),
744            Node::new(parallel_subst_tracked_impl(body, map, stats)),
745        ),
746        Expr::Pi(bi, name, ty, body) => Expr::Pi(
747            *bi,
748            name.clone(),
749            Node::new(parallel_subst_tracked_impl(ty, map, stats)),
750            Node::new(parallel_subst_tracked_impl(body, map, stats)),
751        ),
752        Expr::Let(name, ty, val, body) => Expr::Let(
753            name.clone(),
754            Node::new(parallel_subst_tracked_impl(ty, map, stats)),
755            Node::new(parallel_subst_tracked_impl(val, map, stats)),
756            Node::new(parallel_subst_tracked_impl(body, map, stats)),
757        ),
758        Expr::Proj(name, idx, e) => Expr::Proj(
759            name.clone(),
760            *idx,
761            Node::new(parallel_subst_tracked_impl(e, map, stats)),
762        ),
763    }
764}
765#[cfg(test)]
766mod extended_tests {
767    use super::*;
768    use crate::{BinderInfo, Level, Literal, Name};
769    fn fvar(id: u64) -> Expr {
770        Expr::FVar(FVarId(id))
771    }
772    fn lit(n: u64) -> Expr {
773        Expr::Lit(Literal::nat(n))
774    }
775    fn sort0() -> Expr {
776        Expr::Sort(Level::zero())
777    }
778    #[test]
779    fn test_instantiate_many_two() {
780        let app = Expr::App(Node::new(Expr::BVar(0)), Node::new(Expr::BVar(1)));
781        let result = instantiate_many(&app, &[lit(10), lit(20)]);
782        match result {
783            Expr::App(f, a) => {
784                assert_eq!(*f, lit(10));
785                assert_eq!(*a, lit(20));
786            }
787            _ => panic!("Expected App"),
788        }
789    }
790    #[test]
791    fn test_parallel_subst_single() {
792        let mut map = SubstMap::new();
793        map.insert(FVarId(1), lit(99));
794        let expr = fvar(1);
795        assert_eq!(parallel_subst(&expr, &map), lit(99));
796    }
797    #[test]
798    fn test_parallel_subst_empty() {
799        let expr = fvar(5);
800        let map = SubstMap::new();
801        assert_eq!(parallel_subst(&expr, &map), expr);
802    }
803    #[test]
804    fn test_shift_bvars_above_cutoff() {
805        let e = Expr::BVar(2);
806        let shifted = shift_bvars(&e, 3, 1);
807        assert_eq!(shifted, Expr::BVar(5));
808    }
809    #[test]
810    fn test_shift_bvars_below_cutoff() {
811        let e = Expr::BVar(0);
812        let shifted = shift_bvars(&e, 2, 1);
813        assert_eq!(shifted, Expr::BVar(0));
814    }
815    #[test]
816    fn test_free_vars_single() {
817        let set = free_vars(&fvar(7));
818        assert_eq!(set.len(), 1);
819        assert!(set.contains(&FVarId(7)));
820    }
821    #[test]
822    fn test_occurs_free_true() {
823        let expr = Expr::App(Node::new(fvar(1)), Node::new(fvar(2)));
824        assert!(occurs_free(&expr, FVarId(1)));
825        assert!(!occurs_free(&expr, FVarId(3)));
826    }
827    #[test]
828    fn test_count_free_occurrences() {
829        let expr = Expr::App(Node::new(fvar(1)), Node::new(fvar(1)));
830        assert_eq!(count_free_occurrences(&expr, FVarId(1)), 2);
831    }
832    #[test]
833    fn test_subst_stats_merge() {
834        let mut a = SubstStats {
835            bvar_hits: 3,
836            bvar_misses: 1,
837            fvar_hits: 2,
838            nodes_visited: 10,
839        };
840        let b = SubstStats {
841            bvar_hits: 1,
842            bvar_misses: 0,
843            fvar_hits: 1,
844            nodes_visited: 5,
845        };
846        a.merge(&b);
847        assert_eq!(a.bvar_hits, 4);
848        assert_eq!(a.total_substs(), 7);
849    }
850    #[test]
851    fn test_instantiate_tracked() {
852        let body = Expr::BVar(0);
853        let arg = lit(42);
854        let mut stats = SubstStats::default();
855        let result = instantiate_tracked(&body, &arg, &mut stats);
856        assert_eq!(result, lit(42));
857        assert_eq!(stats.bvar_hits, 1);
858    }
859    #[test]
860    fn test_build_subst_map() {
861        let fvars = vec![FVarId(1), FVarId(2)];
862        let replacements = vec![lit(10), lit(20)];
863        let map = build_subst_map(&fvars, &replacements);
864        assert_eq!(map.get(&FVarId(1)), Some(&lit(10)));
865    }
866    #[test]
867    fn test_try_beta_reduce_success() {
868        let lam = Expr::Lam(
869            BinderInfo::Default,
870            Name::str("x"),
871            Node::new(sort0()),
872            Node::new(Expr::BVar(0)),
873        );
874        let app = Expr::App(Node::new(lam), Node::new(lit(42)));
875        let result = try_beta_reduce(&app).expect("result should be present");
876        assert_eq!(result, lit(42));
877    }
878    #[test]
879    fn test_try_beta_reduce_fail() {
880        let app = Expr::App(Node::new(fvar(1)), Node::new(lit(42)));
881        assert!(try_beta_reduce(&app).is_none());
882    }
883    #[test]
884    fn test_beta_reduce_head() {
885        let lam = Expr::Lam(
886            BinderInfo::Default,
887            Name::str("x"),
888            Node::new(sort0()),
889            Node::new(Expr::BVar(0)),
890        );
891        let app = Expr::App(Node::new(lam), Node::new(lit(7)));
892        let result = beta_reduce_head(app);
893        assert_eq!(result, lit(7));
894    }
895    #[test]
896    fn test_subst_fvar() {
897        let expr = fvar(1);
898        let result = subst_fvar(&expr, FVarId(1), &lit(99));
899        assert_eq!(result, lit(99));
900    }
901    #[test]
902    fn test_is_whnf_beta_lam() {
903        let lam = Expr::Lam(
904            BinderInfo::Default,
905            Name::str("x"),
906            Node::new(sort0()),
907            Node::new(Expr::BVar(0)),
908        );
909        assert!(is_whnf_beta(&lam));
910    }
911    #[test]
912    fn test_is_whnf_beta_app_not_whnf() {
913        let lam = Expr::Lam(
914            BinderInfo::Default,
915            Name::str("x"),
916            Node::new(sort0()),
917            Node::new(Expr::BVar(0)),
918        );
919        let app = Expr::App(Node::new(lam), Node::new(lit(1)));
920        assert!(!is_whnf_beta(&app));
921    }
922    #[test]
923    fn test_collect_loose_bvar_indices() {
924        let e = Expr::App(Node::new(Expr::BVar(0)), Node::new(Expr::BVar(2)));
925        let indices = collect_loose_bvar_indices(&e);
926        assert!(indices.contains(&0));
927        assert!(indices.contains(&2));
928    }
929    #[test]
930    fn test_parallel_subst_tracked() {
931        let expr = Expr::App(Node::new(fvar(1)), Node::new(fvar(2)));
932        let mut map = SubstMap::new();
933        map.insert(FVarId(1), lit(10));
934        map.insert(FVarId(2), lit(20));
935        let mut stats = SubstStats::default();
936        let result = parallel_subst_tracked(&expr, &map, &mut stats);
937        assert_eq!(stats.fvar_hits, 2);
938        match result {
939            Expr::App(f, a) => {
940                assert_eq!(*f, lit(10));
941                assert_eq!(*a, lit(20));
942            }
943            _ => panic!("Expected App"),
944        }
945    }
946}
947/// Apply a list of arguments to a function expression via beta reduction.
948///
949/// Given `f` and `[a1, a2, ..., an]`, returns `((...((f a1) a2) ...) an)`
950/// after instantiating each lambda body with the corresponding argument.
951pub fn apply_args(f: &Expr, args: &[Expr]) -> Expr {
952    let mut result = f.clone();
953    for arg in args {
954        if let Expr::Lam(_, _, _, body) = result {
955            result = instantiate(&body, arg);
956        } else {
957            result = Expr::App(Node::new(result), Node::new(arg.clone()));
958        }
959    }
960    result
961}
962/// Check if an expression is a lambda abstraction.
963#[inline]
964pub fn is_lambda(e: &Expr) -> bool {
965    matches!(e, Expr::Lam(_, _, _, _))
966}
967/// Peel off `n` leading lambda binders, collecting binder info.
968///
969/// Returns `(binders, body)` where `binders` is a vector of `(name, binder_info, type)`
970/// tuples and `body` is the inner expression with `n` fewer lambdas.
971#[allow(clippy::type_complexity)]
972pub fn peel_lambdas(e: &Expr, n: usize) -> (Vec<(crate::Name, crate::BinderInfo, Node)>, &Expr) {
973    let mut binders = Vec::new();
974    let mut cur = e;
975    for _ in 0..n {
976        if let Expr::Lam(bi, _, ty, body) = cur {
977            binders.push((crate::Name::anonymous(), *bi, ty.clone()));
978            cur = body;
979        } else {
980            break;
981        }
982    }
983    (binders, cur)
984}
985/// Count the number of leading lambda binders.
986pub fn count_lambdas(e: &Expr) -> usize {
987    let mut n = 0;
988    let mut cur = e;
989    while let Expr::Lam(_, _, _, body) = cur {
990        n += 1;
991        cur = body;
992    }
993    n
994}
995/// Count the number of leading pi binders.
996pub fn count_pis(e: &Expr) -> usize {
997    let mut n = 0;
998    let mut cur = e;
999    while let Expr::Pi(_, _, _, body) = cur {
1000        n += 1;
1001        cur = body;
1002    }
1003    n
1004}
1005#[cfg(test)]
1006mod extended2_tests {
1007    use super::*;
1008    use crate::{BinderInfo, Expr, FVarId, Literal, Name};
1009    fn lit(n: u64) -> Expr {
1010        Expr::Lit(Literal::nat(n))
1011    }
1012    fn fvar(id: u64) -> Expr {
1013        Expr::FVar(FVarId(id))
1014    }
1015    #[test]
1016    fn test_apply_args_lam() {
1017        let body = Expr::BVar(0);
1018        let lam = Expr::Lam(
1019            BinderInfo::Default,
1020            Name::str("_"),
1021            Node::new(lit(0)),
1022            Node::new(body),
1023        );
1024        let result = apply_args(&lam, &[lit(42)]);
1025        assert_eq!(result, lit(42));
1026    }
1027    #[test]
1028    fn test_apply_args_non_lam() {
1029        let f = fvar(1);
1030        let result = apply_args(&f, &[lit(1), lit(2)]);
1031        match &result {
1032            Expr::App(outer, arg2) => {
1033                assert_eq!(**arg2, lit(2));
1034                match outer.as_ref() {
1035                    Expr::App(inner_f, arg1) => {
1036                        assert_eq!(**inner_f, fvar(1));
1037                        assert_eq!(**arg1, lit(1));
1038                    }
1039                    _ => panic!("Expected App"),
1040                }
1041            }
1042            _ => panic!("Expected App"),
1043        }
1044    }
1045    #[test]
1046    fn test_is_lambda() {
1047        let lam = Expr::Lam(
1048            BinderInfo::Default,
1049            Name::str("_"),
1050            Node::new(lit(0)),
1051            Node::new(lit(1)),
1052        );
1053        assert!(is_lambda(&lam));
1054        assert!(!is_lambda(&lit(0)));
1055    }
1056    #[test]
1057    fn test_count_lambdas() {
1058        let inner = lit(0);
1059        let lam1 = Expr::Lam(
1060            BinderInfo::Default,
1061            Name::str("_"),
1062            Node::new(lit(0)),
1063            Node::new(inner),
1064        );
1065        let lam2 = Expr::Lam(
1066            BinderInfo::Default,
1067            Name::str("_"),
1068            Node::new(lit(0)),
1069            Node::new(lam1),
1070        );
1071        assert_eq!(count_lambdas(&lam2), 2);
1072        assert_eq!(count_lambdas(&lit(0)), 0);
1073    }
1074    #[test]
1075    fn test_count_pis() {
1076        let inner = lit(0);
1077        let pi1 = Expr::Pi(
1078            BinderInfo::Default,
1079            Name::str("_"),
1080            Node::new(lit(0)),
1081            Node::new(inner),
1082        );
1083        let pi2 = Expr::Pi(
1084            BinderInfo::Default,
1085            Name::str("_"),
1086            Node::new(lit(0)),
1087            Node::new(pi1),
1088        );
1089        assert_eq!(count_pis(&pi2), 2);
1090        assert_eq!(count_pis(&lit(0)), 0);
1091    }
1092    #[test]
1093    fn test_peel_lambdas_two() {
1094        let body = Expr::BVar(0);
1095        let lam1 = Expr::Lam(
1096            BinderInfo::Default,
1097            Name::str("_"),
1098            Node::new(lit(0)),
1099            Node::new(body),
1100        );
1101        let lam2 = Expr::Lam(
1102            BinderInfo::Implicit,
1103            Name::str("_"),
1104            Node::new(lit(0)),
1105            Node::new(lam1),
1106        );
1107        let (binders, inner) = peel_lambdas(&lam2, 2);
1108        assert_eq!(binders.len(), 2);
1109        assert_eq!(binders[0].1, BinderInfo::Implicit);
1110        assert_eq!(binders[1].1, BinderInfo::Default);
1111        assert_eq!(*inner, Expr::BVar(0));
1112    }
1113    #[test]
1114    fn test_apply_args_empty() {
1115        let e = lit(5);
1116        let result = apply_args(&e, &[]);
1117        assert_eq!(result, e);
1118    }
1119}
1120/// Apply a substitution list (as parallel arrays) to an expression.
1121#[allow(dead_code)]
1122pub fn substitute_fvars(expr: &Expr, fvars: &[FVarId], replacements: &[Expr]) -> Expr {
1123    assert_eq!(fvars.len(), replacements.len());
1124    let mut subst = Substitution::new();
1125    for (fvar, rep) in fvars.iter().zip(replacements.iter()) {
1126        subst.insert(*fvar, rep.clone());
1127    }
1128    subst.apply(expr)
1129}
1130/// Check if an expression contains any free variable in the given set.
1131#[allow(dead_code)]
1132pub fn expr_contains_fvar(expr: &Expr, fvars: &[FVarId]) -> bool {
1133    match expr {
1134        Expr::FVar(id) => fvars.contains(id),
1135        Expr::App(f, a) => expr_contains_fvar(f, fvars) || expr_contains_fvar(a, fvars),
1136        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1137            expr_contains_fvar(ty, fvars) || expr_contains_fvar(body, fvars)
1138        }
1139        Expr::Let(_, ty, val, body) => {
1140            expr_contains_fvar(ty, fvars)
1141                || expr_contains_fvar(val, fvars)
1142                || expr_contains_fvar(body, fvars)
1143        }
1144        Expr::Proj(_, _, inner) => expr_contains_fvar(inner, fvars),
1145        _ => false,
1146    }
1147}
1148/// Replace the outermost `BVar(0)` of a body with `FVar(id)` (open a binder).
1149///
1150/// This is the "open" operation in locally nameless representation.
1151#[allow(dead_code)]
1152pub fn open_binder(body: &Expr, id: FVarId) -> Expr {
1153    open_binder_at(body, &Expr::FVar(id), 0)
1154}
1155fn open_binder_at(expr: &Expr, fvar_expr: &Expr, depth: u32) -> Expr {
1156    match expr {
1157        Expr::BVar(n) => {
1158            if *n == depth {
1159                fvar_expr.clone()
1160            } else {
1161                expr.clone()
1162            }
1163        }
1164        Expr::App(f, a) => {
1165            let f2 = open_binder_at(f, fvar_expr, depth);
1166            let a2 = open_binder_at(a, fvar_expr, depth);
1167            Expr::App(Node::new(f2), Node::new(a2))
1168        }
1169        Expr::Lam(bi, name, ty, body) => {
1170            let ty2 = open_binder_at(ty, fvar_expr, depth);
1171            let body2 = open_binder_at(body, fvar_expr, depth + 1);
1172            Expr::Lam(*bi, name.clone(), Node::new(ty2), Node::new(body2))
1173        }
1174        Expr::Pi(bi, name, ty, body) => {
1175            let ty2 = open_binder_at(ty, fvar_expr, depth);
1176            let body2 = open_binder_at(body, fvar_expr, depth + 1);
1177            Expr::Pi(*bi, name.clone(), Node::new(ty2), Node::new(body2))
1178        }
1179        Expr::Let(name, ty, val, body) => {
1180            let ty2 = open_binder_at(ty, fvar_expr, depth);
1181            let val2 = open_binder_at(val, fvar_expr, depth);
1182            let body2 = open_binder_at(body, fvar_expr, depth + 1);
1183            Expr::Let(
1184                name.clone(),
1185                Node::new(ty2),
1186                Node::new(val2),
1187                Node::new(body2),
1188            )
1189        }
1190        Expr::Proj(name, idx, inner) => {
1191            let inner2 = open_binder_at(inner, fvar_expr, depth);
1192            Expr::Proj(name.clone(), *idx, Node::new(inner2))
1193        }
1194        _ => expr.clone(),
1195    }
1196}
1197#[cfg(test)]
1198mod extended3_subst_tests {
1199    use super::*;
1200    use crate::{BinderInfo, Expr, FVarId, Literal, Name};
1201    fn lit(n: u64) -> Expr {
1202        Expr::Lit(Literal::nat(n))
1203    }
1204    fn fvar(id: u64) -> Expr {
1205        Expr::FVar(FVarId(id))
1206    }
1207    #[test]
1208    fn test_substitution_insert_and_get() {
1209        let mut s = Substitution::new();
1210        s.insert(FVarId(0), lit(42));
1211        assert_eq!(s.get(FVarId(0)), Some(&lit(42)));
1212        assert_eq!(s.get(FVarId(1)), None);
1213    }
1214    #[test]
1215    fn test_substitution_len() {
1216        let mut s = Substitution::new();
1217        assert_eq!(s.len(), 0);
1218        s.insert(FVarId(0), lit(1));
1219        assert_eq!(s.len(), 1);
1220    }
1221    #[test]
1222    fn test_substitution_apply_fvar() {
1223        let mut s = Substitution::new();
1224        s.insert(FVarId(1), lit(99));
1225        let expr = fvar(1);
1226        assert_eq!(s.apply(&expr), lit(99));
1227    }
1228    #[test]
1229    fn test_substitution_apply_no_match() {
1230        let s = Substitution::new();
1231        let expr = fvar(5);
1232        assert_eq!(s.apply(&expr), fvar(5));
1233    }
1234    #[test]
1235    fn test_substitution_remove() {
1236        let mut s = Substitution::new();
1237        s.insert(FVarId(0), lit(1));
1238        s.remove(FVarId(0));
1239        assert_eq!(s.len(), 0);
1240    }
1241    #[test]
1242    fn test_substitution_restrict() {
1243        let mut s = Substitution::new();
1244        s.insert(FVarId(0), lit(1));
1245        s.insert(FVarId(1), lit(2));
1246        let r = s.restrict(&[FVarId(0)]);
1247        assert_eq!(r.len(), 1);
1248        assert!(r.get(FVarId(0)).is_some());
1249        assert!(r.get(FVarId(1)).is_none());
1250    }
1251    #[test]
1252    fn test_substitute_fvars_parallel() {
1253        let expr = Expr::App(Node::new(fvar(0)), Node::new(fvar(1)));
1254        let result = substitute_fvars(&expr, &[FVarId(0), FVarId(1)], &[lit(10), lit(20)]);
1255        match result {
1256            Expr::App(f, a) => {
1257                assert_eq!(*f, lit(10));
1258                assert_eq!(*a, lit(20));
1259            }
1260            _ => panic!("Expected App"),
1261        }
1262    }
1263    #[test]
1264    fn test_expr_contains_fvar_true() {
1265        let expr = Expr::App(Node::new(fvar(3)), Node::new(lit(0)));
1266        assert!(expr_contains_fvar(&expr, &[FVarId(3)]));
1267    }
1268    #[test]
1269    fn test_expr_contains_fvar_false() {
1270        let expr = lit(5);
1271        assert!(!expr_contains_fvar(&expr, &[FVarId(0)]));
1272    }
1273    #[test]
1274    fn test_open_binder_simple() {
1275        let body = Expr::BVar(0);
1276        let result = open_binder(&body, FVarId(7));
1277        assert_eq!(result, fvar(7));
1278    }
1279    #[test]
1280    fn test_open_binder_nested() {
1281        let inner_body = Expr::BVar(0);
1282        let inner_lam = Expr::Lam(
1283            BinderInfo::Default,
1284            Name::str("_"),
1285            Node::new(Expr::Const(Name::str("Nat"), vec![])),
1286            Node::new(inner_body),
1287        );
1288        let outer_body = Expr::App(Node::new(inner_lam), Node::new(Expr::BVar(0)));
1289        let opened = open_binder(&outer_body, FVarId(99));
1290        match opened {
1291            Expr::App(f, arg) => {
1292                assert!(matches!(*f, Expr::Lam(_, _, _, _)));
1293                assert_eq!(*arg, fvar(99));
1294            }
1295            _ => panic!("Expected App"),
1296        }
1297    }
1298    #[test]
1299    fn test_substitution_compose() {
1300        let mut s1 = Substitution::new();
1301        s1.insert(FVarId(0), lit(10));
1302        let mut s2 = Substitution::new();
1303        s2.insert(FVarId(1), lit(20));
1304        let composed = s1.compose(&s2);
1305        assert_eq!(composed.get(FVarId(0)), Some(&lit(10)));
1306        assert_eq!(composed.get(FVarId(1)), Some(&lit(20)));
1307    }
1308    #[test]
1309    fn test_substitution_insert_replace() {
1310        let mut s = Substitution::new();
1311        s.insert(FVarId(0), lit(1));
1312        s.insert(FVarId(0), lit(2));
1313        assert_eq!(s.len(), 1);
1314        assert_eq!(s.get(FVarId(0)), Some(&lit(2)));
1315    }
1316}
1317#[cfg(test)]
1318mod tests_padding_infra {
1319    use super::*;
1320    #[test]
1321    fn test_stat_summary() {
1322        let mut ss = StatSummary::new();
1323        ss.record(10.0);
1324        ss.record(20.0);
1325        ss.record(30.0);
1326        assert_eq!(ss.count(), 3);
1327        assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1328        assert_eq!(ss.min().expect("min should succeed") as i64, 10);
1329        assert_eq!(ss.max().expect("max should succeed") as i64, 30);
1330    }
1331    #[test]
1332    fn test_transform_stat() {
1333        let mut ts = TransformStat::new();
1334        ts.record_before(100.0);
1335        ts.record_after(80.0);
1336        let ratio = ts.mean_ratio().expect("ratio should be present");
1337        assert!((ratio - 0.8).abs() < 1e-9);
1338    }
1339    #[test]
1340    fn test_small_map() {
1341        let mut m: SmallMap<u32, &str> = SmallMap::new();
1342        m.insert(3, "three");
1343        m.insert(1, "one");
1344        m.insert(2, "two");
1345        assert_eq!(m.get(&2), Some(&"two"));
1346        assert_eq!(m.len(), 3);
1347        let keys = m.keys();
1348        assert_eq!(*keys[0], 1);
1349        assert_eq!(*keys[2], 3);
1350    }
1351    #[test]
1352    fn test_label_set() {
1353        let mut ls = LabelSet::new();
1354        ls.add("foo");
1355        ls.add("bar");
1356        ls.add("foo");
1357        assert_eq!(ls.count(), 2);
1358        assert!(ls.has("bar"));
1359        assert!(!ls.has("baz"));
1360    }
1361    #[test]
1362    fn test_config_node() {
1363        let mut root = ConfigNode::section("root");
1364        let child = ConfigNode::leaf("key", "value");
1365        root.add_child(child);
1366        assert_eq!(root.num_children(), 1);
1367    }
1368    #[test]
1369    fn test_versioned_record() {
1370        let mut vr = VersionedRecord::new(0u32);
1371        vr.update(1);
1372        vr.update(2);
1373        assert_eq!(*vr.current(), 2);
1374        assert_eq!(vr.version(), 2);
1375        assert!(vr.has_history());
1376        assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
1377    }
1378    #[test]
1379    fn test_simple_dag() {
1380        let mut dag = SimpleDag::new(4);
1381        dag.add_edge(0, 1);
1382        dag.add_edge(1, 2);
1383        dag.add_edge(2, 3);
1384        assert!(dag.can_reach(0, 3));
1385        assert!(!dag.can_reach(3, 0));
1386        let order = dag.topological_sort().expect("order should be present");
1387        assert_eq!(order, vec![0, 1, 2, 3]);
1388    }
1389    #[test]
1390    fn test_focus_stack() {
1391        let mut fs: FocusStack<&str> = FocusStack::new();
1392        fs.focus("a");
1393        fs.focus("b");
1394        assert_eq!(fs.current(), Some(&"b"));
1395        assert_eq!(fs.depth(), 2);
1396        fs.blur();
1397        assert_eq!(fs.current(), Some(&"a"));
1398    }
1399}
1400#[cfg(test)]
1401mod tests_extra_iterators {
1402    use super::*;
1403    #[test]
1404    fn test_window_iterator() {
1405        let data = vec![1u32, 2, 3, 4, 5];
1406        let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
1407        assert_eq!(windows.len(), 3);
1408        assert_eq!(windows[0], &[1, 2, 3]);
1409        assert_eq!(windows[2], &[3, 4, 5]);
1410    }
1411    #[test]
1412    fn test_non_empty_vec() {
1413        let mut nev = NonEmptyVec::singleton(10u32);
1414        nev.push(20);
1415        nev.push(30);
1416        assert_eq!(nev.len(), 3);
1417        assert_eq!(*nev.first(), 10);
1418        assert_eq!(*nev.last(), 30);
1419    }
1420}
1421#[cfg(test)]
1422mod tests_padding2 {
1423    use super::*;
1424    #[test]
1425    fn test_sliding_sum() {
1426        let mut ss = SlidingSum::new(3);
1427        ss.push(1.0);
1428        ss.push(2.0);
1429        ss.push(3.0);
1430        assert!((ss.sum() - 6.0).abs() < 1e-9);
1431        ss.push(4.0);
1432        assert!((ss.sum() - 9.0).abs() < 1e-9);
1433        assert_eq!(ss.count(), 3);
1434    }
1435    #[test]
1436    fn test_path_buf() {
1437        let mut pb = PathBuf::new();
1438        pb.push("src");
1439        pb.push("main");
1440        assert_eq!(pb.as_str(), "src/main");
1441        assert_eq!(pb.depth(), 2);
1442        pb.pop();
1443        assert_eq!(pb.as_str(), "src");
1444    }
1445    #[test]
1446    fn test_string_pool() {
1447        let mut pool = StringPool::new();
1448        let s = pool.take();
1449        assert!(s.is_empty());
1450        pool.give("hello".to_string());
1451        let s2 = pool.take();
1452        assert!(s2.is_empty());
1453        assert_eq!(pool.free_count(), 0);
1454    }
1455    #[test]
1456    fn test_transitive_closure() {
1457        let mut tc = TransitiveClosure::new(4);
1458        tc.add_edge(0, 1);
1459        tc.add_edge(1, 2);
1460        tc.add_edge(2, 3);
1461        assert!(tc.can_reach(0, 3));
1462        assert!(!tc.can_reach(3, 0));
1463        let r = tc.reachable_from(0);
1464        assert_eq!(r.len(), 4);
1465    }
1466    #[test]
1467    fn test_token_bucket() {
1468        let mut tb = TokenBucket::new(100, 0);
1469        assert_eq!(tb.available(), 100);
1470        assert!(tb.try_consume(50));
1471        assert_eq!(tb.available(), 50);
1472        assert!(!tb.try_consume(60));
1473        assert_eq!(tb.capacity(), 100);
1474    }
1475    #[test]
1476    fn test_rewrite_rule_set() {
1477        let mut rrs = RewriteRuleSet::new();
1478        rrs.add(RewriteRule::unconditional(
1479            "beta",
1480            "App(Lam(x, b), v)",
1481            "b[x:=v]",
1482        ));
1483        rrs.add(RewriteRule::conditional("comm", "a + b", "b + a"));
1484        assert_eq!(rrs.len(), 2);
1485        assert_eq!(rrs.unconditional_rules().len(), 1);
1486        assert_eq!(rrs.conditional_rules().len(), 1);
1487        assert!(rrs.get("beta").is_some());
1488        let disp = rrs
1489            .get("beta")
1490            .expect("element at \'beta\' should exist")
1491            .display();
1492        assert!(disp.contains("→"));
1493    }
1494}
1495#[cfg(test)]
1496mod tests_padding3 {
1497    use super::*;
1498    #[test]
1499    fn test_decision_node() {
1500        let tree = DecisionNode::Branch {
1501            key: "x".into(),
1502            val: "1".into(),
1503            yes_branch: Box::new(DecisionNode::Leaf("yes".into())),
1504            no_branch: Box::new(DecisionNode::Leaf("no".into())),
1505        };
1506        let mut ctx = std::collections::HashMap::new();
1507        ctx.insert("x".into(), "1".into());
1508        assert_eq!(tree.evaluate(&ctx), "yes");
1509        ctx.insert("x".into(), "2".into());
1510        assert_eq!(tree.evaluate(&ctx), "no");
1511        assert_eq!(tree.depth(), 1);
1512    }
1513    #[test]
1514    fn test_flat_substitution() {
1515        let mut sub = FlatSubstitution::new();
1516        sub.add("foo", "bar");
1517        sub.add("baz", "qux");
1518        assert_eq!(sub.apply("foo and baz"), "bar and qux");
1519        assert_eq!(sub.len(), 2);
1520    }
1521    #[test]
1522    fn test_stopwatch() {
1523        let mut sw = Stopwatch::start();
1524        sw.split();
1525        sw.split();
1526        assert_eq!(sw.num_splits(), 2);
1527        assert!(sw.elapsed_ms() >= 0.0);
1528        for &s in sw.splits() {
1529            assert!(s >= 0.0);
1530        }
1531    }
1532    #[test]
1533    fn test_either2() {
1534        let e: Either2<i32, &str> = Either2::First(42);
1535        assert!(e.is_first());
1536        let mapped = e.map_first(|x| x * 2);
1537        assert_eq!(mapped.first(), Some(84));
1538        let e2: Either2<i32, &str> = Either2::Second("hello");
1539        assert!(e2.is_second());
1540        assert_eq!(e2.second(), Some("hello"));
1541    }
1542    #[test]
1543    fn test_write_once() {
1544        let wo: WriteOnce<u32> = WriteOnce::new();
1545        assert!(!wo.is_written());
1546        assert!(wo.write(42));
1547        assert!(!wo.write(99));
1548        assert_eq!(wo.read(), Some(42));
1549    }
1550    #[test]
1551    fn test_sparse_vec() {
1552        let mut sv: SparseVec<i32> = SparseVec::new(100);
1553        sv.set(5, 10);
1554        sv.set(50, 20);
1555        assert_eq!(*sv.get(5), 10);
1556        assert_eq!(*sv.get(50), 20);
1557        assert_eq!(*sv.get(0), 0);
1558        assert_eq!(sv.nnz(), 2);
1559        sv.set(5, 0);
1560        assert_eq!(sv.nnz(), 1);
1561    }
1562    #[test]
1563    fn test_stack_calc() {
1564        let mut calc = StackCalc::new();
1565        calc.push(3);
1566        calc.push(4);
1567        assert_eq!(calc.add(), Some(7));
1568        assert_eq!(calc.peek(), Some(7));
1569        calc.push(2);
1570        assert_eq!(calc.mul(), Some(14));
1571        assert_eq!(calc.peek(), Some(14));
1572    }
1573}