Skip to main content

oxilean_kernel/check/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::declaration::{ConstantInfo, ConstructorVal, InductiveVal, QuotVal, RecursorVal};
6use crate::env::EnvError;
7use crate::Node;
8use crate::{Declaration, Environment, KernelError, TypeChecker};
9#[cfg(test)]
10use crate::{Expr, Name};
11use std::rc::Rc;
12
13use super::types::{
14    BatchCheckResult, CheckConfig, CheckStats, ConfigNode, DecisionNode, DeclKind, DeclSummary,
15    Either2, EnvBuilder, FlatSubstitution, FocusStack, LabelSet, NonEmptyVec, PathBuf, RewriteRule,
16    RewriteRuleSet, SimpleDag, SlidingSum, SmallMap, SparseVec, StackCalc, StatSummary, Stopwatch,
17    StringPool, TempEnvScope, TokenBucket, TransformStat, TransitiveClosure, VersionedRecord,
18    WellFormedResult, WindowIterator, WriteOnce,
19};
20
21/// Check a declaration and add it to the environment.
22///
23/// Validates that the declaration is well-formed and type-correct, then
24/// inserts it into the environment. Returns an error if the declaration
25/// fails type checking or if the name is already declared.
26#[allow(clippy::result_large_err)]
27pub fn check_declaration(env: &mut Environment, decl: Declaration) -> Result<(), KernelError> {
28    let name = decl.name().clone();
29    // Universe-parameter hygiene (S8): reject duplicate univ params, undeclared
30    // `Param`s appearing in the type/value, and any `Level` metavariable — all
31    // of which Lean's kernel rejects and any of which a hostile/malformed export
32    // could otherwise smuggle through.
33    check_univ_param_hygiene(&decl)?;
34    match &decl {
35        Declaration::Axiom { ty, .. } => {
36            let mut tc = TypeChecker::new(env);
37            tc.ensure_sort(ty)?;
38        }
39        Declaration::Definition { ty, val, .. } => {
40            let mut tc = TypeChecker::new(env);
41            tc.ensure_sort(ty)?;
42            let val_ty = tc.infer_type(val)?;
43            tc.check_type(val, &val_ty, ty)?;
44        }
45        Declaration::Theorem { ty, val, .. } => {
46            let mut tc = TypeChecker::new(env);
47            tc.ensure_sort(ty)?;
48            let val_ty = tc.infer_type(val)?;
49            tc.check_type(val, &val_ty, ty)?;
50        }
51        Declaration::Opaque { ty, val, .. } => {
52            let mut tc = TypeChecker::new(env);
53            tc.ensure_sort(ty)?;
54            let val_ty = tc.infer_type(val)?;
55            tc.check_type(val, &val_ty, ty)?;
56        }
57    }
58    env.add(decl).map_err(|e| match e {
59        EnvError::DuplicateDeclaration(_) => {
60            KernelError::Other(format!("duplicate declaration: {}", name))
61        }
62        EnvError::NotFound(_) => KernelError::Other(format!("declaration not found: {}", name)),
63        EnvError::InvalidQuotient(msg) => KernelError::Other(msg),
64    })
65}
66/// Enforce universe-parameter hygiene for a declaration (S8).
67///
68/// Three checks, all of which Lean's kernel performs and none of which the
69/// pre-Wave-2 checker did:
70///
71/// 1. **No duplicate universe parameters** in the declaration's `univ_params`.
72/// 2. **Every `Level::Param` occurring in the type or value is declared** in
73///    `univ_params` (an undeclared param is a free universe variable and must
74///    be rejected, else a hostile export can capture it).
75/// 3. **No `Level::MVar` anywhere** in the type or value — a kernel-checked
76///    declaration must be metavariable-free.
77#[allow(clippy::result_large_err)]
78pub(super) fn check_univ_param_hygiene(decl: &Declaration) -> Result<(), KernelError> {
79    check_univ_param_hygiene_parts(
80        decl.name(),
81        decl.univ_params(),
82        std::iter::once(decl.ty()).chain(decl.value()),
83    )
84}
85/// Enforce universe-parameter hygiene for a `ConstantInfo` (S8).
86///
87/// Identical checks to [`check_univ_param_hygiene`], but over the richer
88/// `ConstantInfo` variants (`Inductive`, `Constructor`, `Recursor`,
89/// `Quotient`, in addition to `Axiom`/`Definition`/`Theorem`/`Opaque`). Every
90/// variant exposes its `level_params`, `ty`, and optional `value` uniformly,
91/// so a hostile or malformed export cannot smuggle a duplicate universe
92/// parameter, a free (undeclared) universe variable, or a level metavariable
93/// through the `check_constant_info` path either.
94#[allow(clippy::result_large_err)]
95pub(super) fn check_univ_param_hygiene_ci(ci: &ConstantInfo) -> Result<(), KernelError> {
96    check_univ_param_hygiene_parts(
97        ci.name(),
98        ci.level_params(),
99        std::iter::once(ci.ty()).chain(ci.value()),
100    )
101}
102/// Shared core of the S8 universe-parameter hygiene checks, over the raw
103/// pieces (name, declared universe params, and the expressions to scan — the
104/// type followed by any value). Used by both [`check_univ_param_hygiene`]
105/// (`Declaration`) and [`check_univ_param_hygiene_ci`] (`ConstantInfo`).
106///
107/// Three checks, all of which Lean's kernel performs and none of which the
108/// pre-Wave-2 checker did:
109///
110/// 1. **No duplicate universe parameters** in `params`.
111/// 2. **Every `Level::Param` occurring in an `expr` is declared** in `params`
112///    (an undeclared param is a free universe variable and must be rejected,
113///    else a hostile export can capture it).
114/// 3. **No `Level::MVar` anywhere** in any `expr` — a kernel-checked
115///    declaration must be metavariable-free.
116#[allow(clippy::result_large_err)]
117fn check_univ_param_hygiene_parts<'a>(
118    name: &crate::Name,
119    params: &[crate::Name],
120    exprs: impl Iterator<Item = &'a crate::Expr>,
121) -> Result<(), KernelError> {
122    use std::collections::HashSet;
123
124    // (1) duplicate parameter names.
125    let mut declared: HashSet<&crate::Name> = HashSet::new();
126    for p in params {
127        if !declared.insert(p) {
128            return Err(KernelError::Other(format!(
129                "declaration {}: duplicate universe parameter `{}`",
130                name, p
131            )));
132        }
133    }
134
135    // (2) + (3): walk type and value.
136    for expr in exprs {
137        // (0) closed terms only, and none deeper than this thread's limit
138        // (where it has one) — before anything below walks the term.
139        check_term_depth(name, expr)?;
140        check_closed_term(name, expr)?;
141        // (3) metavariables.
142        if crate::expr_util::has_level_mvar(expr) {
143            return Err(KernelError::Other(format!(
144                "declaration {}: universe metavariable in kernel-checked declaration",
145                name
146            )));
147        }
148        // (2) undeclared parameters.
149        let mut used = Vec::new();
150        collect_expr_level_params(expr, &mut used);
151        for u in &used {
152            if !declared.contains(u) {
153                return Err(KernelError::Other(format!(
154                    "declaration {}: undeclared universe parameter `{}`",
155                    name, u
156                )));
157            }
158        }
159    }
160    Ok(())
161}
162/// A term of a kernel-checked declaration — its type, its value, a recursor
163/// rule — must be CLOSED: no free variable and no loose bound variable.
164/// Lean's kernel makes the same check before it looks at a declaration
165/// (`check_no_metavar_no_fvar`).
166///
167/// Free variables are the checker's own device: it mints one for each
168/// binder it opens, by number, and types what it finds under the binder
169/// against it. A declaration that arrives already containing a free variable
170/// can only be naming one of those numbers. Under a binder the checker
171/// happens to have opened with that number the variable passes for the
172/// bound one — and the declaration would then go into the environment with
173/// the free variable still in it, to be unfolded later in a context where
174/// the same number means something else. Since the checker remembers the
175/// types it has inferred ([`TypeChecker::infer_type`]), such a variable
176/// would also keep its type AFTER its binder closed:
177/// `(fun (x : False) => x) (FVar 0)` would check as a proof of `False`.
178/// No export can spell a free variable — the format has no record for one —
179/// so this concerns a declaration built through the Rust API; it is refused
180/// here, before any of it is typed. Both facts are read off the term in O(1).
181#[allow(clippy::result_large_err)]
182pub(crate) fn check_closed_term(name: &crate::Name, expr: &crate::Expr) -> Result<(), KernelError> {
183    if crate::expr_util::has_any_fvar(expr) {
184        return Err(KernelError::Other(format!(
185            "declaration {}: free variable in kernel-checked declaration",
186            name
187        )));
188    }
189    if crate::expr_util::has_loose_bvars(expr) {
190        return Err(KernelError::Other(format!(
191            "declaration {}: loose bound variable in kernel-checked declaration",
192            name
193        )));
194    }
195    Ok(())
196}
197/// A term of a declaration offered for checking — its type, its value, a
198/// recursor rule — must not be deeper than the limit of
199/// [`crate::fuel::set_term_depth_limit`], where one is set and the depth of
200/// terms is tracked ([`crate::fuel::TERM_DEPTH_TRACKED`]).
201///
202/// The limit stands for the stack of the thread the kernel runs on: every
203/// walk of a term is a recursion as deep as the term. A term built WHILE a
204/// declaration is checked spends the budget when it crosses the limit; this
205/// is the same refusal for a term that arrives already too deep, made
206/// before any of it is walked (the depth is read off the root in O(1)). The
207/// error is not a verdict on the declaration: the limit is recorded
208/// ([`crate::fuel::term_depth_limit_hit`]), and the caller reports a
209/// resource limit.
210#[allow(clippy::result_large_err)]
211pub(crate) fn check_term_depth(name: &crate::Name, expr: &crate::Expr) -> Result<(), KernelError> {
212    #[cfg(feature = "term-depth")]
213    {
214        let depth = expr.depth();
215        if crate::fuel::term_depth_limit().is_some_and(|limit| depth > limit) {
216            crate::fuel::note_term_depth(depth);
217            return Err(KernelError::Other(format!(
218                "declaration {name}: a term {depth} deep is over the term-depth limit"
219            )));
220        }
221    }
222    #[cfg(not(feature = "term-depth"))]
223    let _ = (name, expr);
224    Ok(())
225}
226/// Collect all `Level::Param` names referenced anywhere in an expression,
227/// including inside `Proj`'s struct expression (which the declaration-module
228/// collector skips). Deduplicated.
229///
230/// Every shared subterm is looked at once (the set of addresses already
231/// seen — sound for the duration of this walk, during which `expr` is
232/// borrowed and none of its subterms can be freed), and a subterm that
233/// mentions no universe parameter is not entered at all (the fact is cached
234/// on the node). A declaration whose value is a few thousand shared nodes
235/// used to be walked here as the millions of nodes of its tree.
236pub(super) fn collect_expr_level_params(expr: &crate::Expr, out: &mut Vec<crate::Name>) {
237    use crate::{Expr, Node};
238    let mut seen: std::collections::HashSet<*const Expr, crate::node_memo::WordBuildHasher> =
239        std::collections::HashSet::default();
240    let mut pending: Vec<&Node> = Vec::new();
241    let mut current: Option<&Expr> = Some(expr);
242    loop {
243        let cur = match current.take() {
244            Some(cur) => cur,
245            None => match pending.pop() {
246                Some(node) => {
247                    if !node.has_level_param() || !seen.insert(node.as_ptr()) {
248                        continue;
249                    }
250                    node
251                }
252                None => break,
253            },
254        };
255        match cur {
256            Expr::Sort(l) => collect_level_params_in_level(l, out),
257            Expr::Const(_, levels) => {
258                for l in levels {
259                    collect_level_params_in_level(l, out);
260                }
261            }
262            Expr::App(f, a) => {
263                pending.push(a);
264                pending.push(f);
265            }
266            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
267                pending.push(body);
268                pending.push(ty);
269            }
270            Expr::Let(_, ty, val, body) => {
271                pending.push(body);
272                pending.push(val);
273                pending.push(ty);
274            }
275            Expr::Proj(_, _, e) => pending.push(e),
276            Expr::BVar(_) | Expr::FVar(_) | Expr::Lit(_) => {}
277        }
278    }
279}
280fn collect_level_params_in_level(l: &crate::Level, out: &mut Vec<crate::Name>) {
281    use crate::LevelView;
282    match l.view() {
283        LevelView::Param(n) => {
284            if !out.contains(n) {
285                out.push(n.clone());
286            }
287        }
288        LevelView::Succ(inner) => collect_level_params_in_level(inner, out),
289        LevelView::Max(a, b) | LevelView::IMax(a, b) => {
290            collect_level_params_in_level(a, out);
291            collect_level_params_in_level(b, out);
292        }
293        LevelView::Zero | LevelView::MVar(_) => {}
294    }
295}
296/// Check multiple declarations in sequence.
297///
298/// Processes the declarations in the given order, stopping at the first
299/// error. Each successfully-checked declaration is added to the environment
300/// so that later declarations can refer to earlier ones.
301#[allow(clippy::result_large_err)]
302pub fn check_declarations(
303    env: &mut Environment,
304    decls: Vec<Declaration>,
305) -> Result<(), KernelError> {
306    for decl in decls {
307        check_declaration(env, decl)?;
308    }
309    Ok(())
310}
311/// Check a `ConstantInfo` (inductive, constructor, recursor, or quotient) and add
312/// it to the environment.
313///
314/// Unlike `check_declaration`, this function handles the richer LEAN 4-style
315/// constant info types that include inductive types, constructors, recursors, and
316/// quotient type components.
317#[allow(clippy::result_large_err)]
318pub fn check_constant_info(env: &mut Environment, ci: ConstantInfo) -> Result<(), KernelError> {
319    let name = ci.name().clone();
320    // Universe-parameter hygiene (S8): reject duplicate univ params, undeclared
321    // `Param`s in the type/value, and any `Level` metavariable — for every
322    // `ConstantInfo` variant, mirroring the `check_declaration` path.
323    check_univ_param_hygiene_ci(&ci)?;
324    match &ci {
325        ConstantInfo::Axiom(av) => {
326            let mut tc = TypeChecker::new(env);
327            tc.ensure_sort(&av.common.ty)?;
328        }
329        ConstantInfo::Definition(dv) => {
330            let mut tc = TypeChecker::new(env);
331            tc.ensure_sort(&dv.common.ty)?;
332            let val_ty = tc.infer_type(&dv.value)?;
333            tc.check_type(&dv.value, &val_ty, &dv.common.ty)?;
334        }
335        ConstantInfo::Theorem(tv) => {
336            let mut tc = TypeChecker::new(env);
337            tc.ensure_sort(&tv.common.ty)?;
338            let val_ty = tc.infer_type(&tv.value)?;
339            tc.check_type(&tv.value, &val_ty, &tv.common.ty)?;
340        }
341        ConstantInfo::Opaque(ov) => {
342            let mut tc = TypeChecker::new(env);
343            tc.ensure_sort(&ov.common.ty)?;
344            let val_ty = tc.infer_type(&ov.value)?;
345            tc.check_type(&ov.value, &val_ty, &ov.common.ty)?;
346        }
347        ConstantInfo::Inductive(iv) => {
348            check_inductive_val(env, iv)?;
349        }
350        ConstantInfo::Constructor(cv) => {
351            check_constructor_val(env, cv)?;
352        }
353        ConstantInfo::Recursor(rv) => {
354            // The rule right-hand sides are terms of the declaration too
355            // (closed lambdas over params, motives, minors and fields).
356            for rule in &rv.rules {
357                check_term_depth(&name, &rule.rhs)?;
358                check_closed_term(&name, &rule.rhs)?;
359            }
360            check_recursor_val(env, rv)?;
361        }
362        ConstantInfo::Quotient(qv) => {
363            check_quotient_val(env, qv)?;
364        }
365    }
366    env.add_constant(ci).map_err(|e| match e {
367        crate::env::EnvError::DuplicateDeclaration(_) => {
368            KernelError::Other(format!("duplicate declaration: {}", name))
369        }
370        crate::env::EnvError::NotFound(_) => {
371            KernelError::Other(format!("declaration not found: {}", name))
372        }
373        crate::env::EnvError::InvalidQuotient(msg) => KernelError::Other(msg),
374    })
375}
376/// Check multiple `ConstantInfo` declarations, stopping on the first error.
377#[allow(clippy::result_large_err)]
378pub fn check_constant_infos(
379    env: &mut Environment,
380    cis: Vec<ConstantInfo>,
381) -> Result<(), KernelError> {
382    for ci in cis {
383        check_constant_info(env, ci)?;
384    }
385    Ok(())
386}
387/// Validate an `InductiveVal`.
388///
389/// Beyond the sort check, the declared telescope must match the announced
390/// `num_params + num_indices`, universe parameters must be distinct and
391/// actually declared, and the `is_prop` flag must agree with the declared
392/// sort — a caller cannot lie about propositionality (large elimination is
393/// recomputed from the type when the recursor is checked anyway).
394/// Constructor types are checked via `check_constructor_val` when each
395/// constructor `ConstantInfo` is added; recursors are re-derived and
396/// compared in `check_recursor_val`. Empty inductives (0 constructors, like
397/// `Empty`) are valid.
398#[allow(clippy::result_large_err)]
399pub(super) fn check_inductive_val(
400    env: &mut Environment,
401    iv: &InductiveVal,
402) -> Result<(), KernelError> {
403    crate::inductive::derive::validate_inductive_val(env, iv)
404}
405/// Validate a `ConstructorVal` against the environment's inductive family:
406/// the parameter telescope must match the inductive's (definitionally),
407/// each field's universe must fit the inductive's universe (with the Prop
408/// exception), the fields must be strictly positive (WHNF-hardened,
409/// mutual-aware), and the codomain must be the parent type applied to
410/// exactly the parameters. Metadata (`cidx`, `num_params`, `num_fields`,
411/// level params) must agree with the parent inductive.
412#[allow(clippy::result_large_err)]
413pub(super) fn check_constructor_val(
414    env: &mut Environment,
415    cv: &ConstructorVal,
416) -> Result<(), KernelError> {
417    crate::inductive::derive::verify_constructor_val(env, cv)
418}
419/// Validate a `RecursorVal` by *re-deriving* it: the kernel reconstructs
420/// the recursors for the declared inductive family (motives, minor premises
421/// with induction hypotheses, elimination universe, K flag, iota rules) and
422/// requires the supplied declaration to match the derivation up to
423/// definitional equality. Externally-supplied recursors are never trusted.
424#[allow(clippy::result_large_err)]
425pub(super) fn check_recursor_val(
426    env: &mut Environment,
427    rv: &RecursorVal,
428) -> Result<(), KernelError> {
429    crate::inductive::derive::verify_recursor_val(env, rv)
430}
431/// Validate a `QuotVal`: verify the supplied type is definitionally equal to
432/// the kernel's canonical type for its [`QuotKind`].
433///
434/// Defense-in-depth: the kernel constructs the canonical quotient types itself
435/// (see [`crate::env::canonical_quot_type`]) and *never* trusts a
436/// caller-supplied type. A `ConstantInfo::Quotient` whose type is not defeq to
437/// the canonical shape is rejected here (in addition to being rejected outright
438/// by `Environment::add_constant`, which only accepts quotients through the
439/// once-only `add_quot` entry point).
440#[allow(clippy::result_large_err)]
441pub(super) fn check_quotient_val(env: &mut Environment, qv: &QuotVal) -> Result<(), KernelError> {
442    let canonical = crate::env::canonical_quot_type(qv.kind);
443    let expected_params = crate::env::canonical_quot_level_params(qv.kind);
444    if qv.common.level_params != expected_params {
445        return Err(KernelError::Other(format!(
446            "quotient `{}` has unexpected universe parameters: expected {:?}, got {:?}",
447            qv.common.name, expected_params, qv.common.level_params
448        )));
449    }
450    let mut tc = TypeChecker::new(env);
451    tc.ensure_sort(&qv.common.ty)?;
452    if !tc.is_def_eq(&qv.common.ty, &canonical) {
453        return Err(KernelError::TypeMismatch {
454            expected: canonical,
455            got: qv.common.ty.clone(),
456            context: format!("canonical type of quotient `{}`", qv.common.name),
457        });
458    }
459    Ok(())
460}
461/// Summarize a declaration without checking it.
462pub fn summarize_declaration(decl: &Declaration) -> DeclSummary {
463    match decl {
464        Declaration::Axiom {
465            name, univ_params, ..
466        } => DeclSummary {
467            name: format!("{}", name),
468            kind: DeclKind::Axiom,
469            has_body: false,
470            num_univ_params: univ_params.len(),
471        },
472        Declaration::Definition {
473            name, univ_params, ..
474        } => DeclSummary {
475            name: format!("{}", name),
476            kind: DeclKind::Definition,
477            has_body: true,
478            num_univ_params: univ_params.len(),
479        },
480        Declaration::Theorem {
481            name, univ_params, ..
482        } => DeclSummary {
483            name: format!("{}", name),
484            kind: DeclKind::Theorem,
485            has_body: true,
486            num_univ_params: univ_params.len(),
487        },
488        Declaration::Opaque {
489            name, univ_params, ..
490        } => DeclSummary {
491            name: format!("{}", name),
492            kind: DeclKind::Opaque,
493            has_body: true,
494            num_univ_params: univ_params.len(),
495        },
496    }
497}
498/// Check multiple declarations, collecting all errors rather than stopping at first.
499///
500/// Unlike `check_declarations`, this function continues even after encountering
501/// errors, and returns a summary of all results.
502pub fn check_declarations_collect_errors(
503    env: &mut Environment,
504    decls: Vec<Declaration>,
505) -> BatchCheckResult {
506    let mut num_ok = 0;
507    let mut num_failed = 0;
508    let mut errors = Vec::new();
509    for decl in decls {
510        let name = format!("{}", decl.name());
511        match check_declaration(env, decl) {
512            Ok(()) => num_ok += 1,
513            Err(e) => {
514                num_failed += 1;
515                errors.push((name, format!("{:?}", e)));
516            }
517        }
518    }
519    BatchCheckResult {
520        num_ok,
521        num_failed,
522        errors,
523    }
524}
525/// Check multiple declarations and collect statistics.
526///
527/// Returns `(stats, first_error)` where `first_error` is `Some(err)` if any
528/// declaration failed, and `None` if all succeeded.
529#[allow(clippy::result_large_err)]
530pub fn check_declarations_with_stats(
531    env: &mut Environment,
532    decls: Vec<Declaration>,
533) -> (CheckStats, Option<KernelError>) {
534    let mut stats = CheckStats::new();
535    for decl in decls {
536        let kind = summarize_declaration(&decl).kind;
537        match check_declaration(env, decl) {
538            Ok(()) => match kind {
539                DeclKind::Axiom => stats.num_axioms += 1,
540                DeclKind::Definition => stats.num_definitions += 1,
541                DeclKind::Theorem => stats.num_theorems += 1,
542                DeclKind::Opaque => stats.num_opaques += 1,
543            },
544            Err(e) => {
545                stats.num_failures += 1;
546                return (stats, Some(e));
547            }
548        }
549    }
550    (stats, None)
551}
552/// Check that a declaration name is valid (non-empty, no invalid characters).
553pub fn is_valid_decl_name(name: &str) -> bool {
554    !name.is_empty()
555        && name
556            .chars()
557            .all(|c| c.is_alphanumeric() || c == '_' || c == '.' || c == '\'')
558        && !name.starts_with('.')
559        && !name.ends_with('.')
560}
561/// Check that a declaration's universe parameter list has no duplicates.
562pub fn has_unique_univ_params(params: &[crate::Name]) -> bool {
563    let mut seen = std::collections::HashSet::new();
564    params.iter().all(|p| seen.insert(format!("{}", p)))
565}
566/// Check that two universe parameter lists are compatible (same length, same names).
567pub fn univ_params_compatible(p1: &[crate::Name], p2: &[crate::Name]) -> bool {
568    p1.len() == p2.len()
569        && p1
570            .iter()
571            .zip(p2.iter())
572            .all(|(a, b)| format!("{}", a) == format!("{}", b))
573}
574/// Format a declaration summary for display.
575pub fn format_decl_summary(summary: &DeclSummary) -> String {
576    let body_str = if summary.has_body { " := ..." } else { "" };
577    let params_str = if summary.num_univ_params > 0 {
578        format!(".{{u{}}}", summary.num_univ_params)
579    } else {
580        String::new()
581    };
582    format!(
583        "{} {}{}{} : ...",
584        summary.kind, summary.name, params_str, body_str
585    )
586}
587/// Format a batch check result for display.
588pub fn format_batch_result(result: &BatchCheckResult) -> String {
589    if result.all_ok() {
590        format!("All {} declarations OK", result.total())
591    } else {
592        let mut s = format!(
593            "{}/{} declarations OK. Failures:\n",
594            result.num_ok,
595            result.total()
596        );
597        for msg in result.error_messages() {
598            s.push_str("  - ");
599            s.push_str(&msg);
600            s.push('\n');
601        }
602        s
603    }
604}
605/// Check the integrity of an environment by re-type-checking all declarations.
606///
607/// This is an expensive operation intended for debugging and testing. It verifies
608/// that every declaration in the environment is still consistent.
609pub fn verify_environment_integrity(env: &Environment) -> Vec<String> {
610    let mut issues = Vec::new();
611    for name in env
612        .constant_names()
613        .cloned()
614        .collect::<Vec<_>>()
615        .into_iter()
616    {
617        if let Some(info) = env.get(&name) {
618            let ty = info.ty();
619            if !is_structurally_valid_expr(ty) {
620                issues.push(format!("{}: type is not structurally valid", name));
621            }
622        }
623    }
624    issues
625}
626/// Perform a basic structural validity check on an expression.
627///
628/// This checks that bound variable indices are in range, but does not
629/// perform full type checking.
630pub fn is_structurally_valid_expr(expr: &crate::Expr) -> bool {
631    check_expr_bvars(expr, 0)
632}
633pub(super) fn check_expr_bvars(expr: &crate::Expr, depth: u32) -> bool {
634    use crate::Expr;
635    match expr {
636        Expr::BVar(i) => *i < depth,
637        Expr::FVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => true,
638        Expr::App(f, a) => check_expr_bvars(f, depth) && check_expr_bvars(a, depth),
639        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
640            check_expr_bvars(ty, depth) && check_expr_bvars(body, depth + 1)
641        }
642        Expr::Let(_, ty, val, body) => {
643            check_expr_bvars(ty, depth)
644                && check_expr_bvars(val, depth)
645                && check_expr_bvars(body, depth + 1)
646        }
647        Expr::Proj(_, _, e) => check_expr_bvars(e, depth),
648    }
649}
650/// Collect the names of all constants referenced in a declaration.
651///
652/// This includes references in both the type and the value (if present).
653pub fn declaration_dependencies(decl: &Declaration) -> Vec<crate::Name> {
654    use crate::collect_const_names;
655    let mut deps = collect_const_names(decl.ty());
656    if let Some(val) = decl.value() {
657        deps.extend(collect_const_names(val));
658    }
659    deps.sort_by(|a, b| format!("{}", a).cmp(&format!("{}", b)));
660    deps.dedup_by(|a, b| format!("{}", a) == format!("{}", b));
661    deps
662}
663/// Check that all dependencies of a declaration exist in the environment.
664///
665/// Returns the names of any missing dependencies.
666pub fn check_dependencies_exist(env: &Environment, decl: &Declaration) -> Vec<crate::Name> {
667    let deps = declaration_dependencies(decl);
668    deps.into_iter()
669        .filter(|name| env.get(name).is_none())
670        .collect()
671}
672#[cfg(test)]
673mod tests {
674    use super::*;
675    use crate::{Level, Literal, ReducibilityHint};
676    fn mk_type0() -> Expr {
677        Expr::Sort(Level::succ(Level::zero()))
678    }
679    fn mk_nat_axiom() -> Declaration {
680        Declaration::Axiom {
681            name: Name::str("Nat"),
682            univ_params: vec![],
683            ty: mk_type0(),
684        }
685    }
686    fn mk_nat_const() -> Expr {
687        Expr::Const(Name::str("Nat"), vec![])
688    }
689    #[test]
690    fn test_check_axiom() {
691        let mut env = Environment::new();
692        let nat_ty = Expr::Sort(Level::succ(Level::zero()));
693        let axiom = Declaration::Axiom {
694            name: Name::str("Nat"),
695            univ_params: vec![],
696            ty: nat_ty,
697        };
698        check_declaration(&mut env, axiom).expect("value should be present");
699        assert!(env.contains(&Name::str("Nat")));
700    }
701    #[test]
702    fn test_check_definition() {
703        let mut env = Environment::new();
704        env.add(mk_nat_axiom()).expect("value should be present");
705        let nat_ty = mk_nat_const();
706        let val = Expr::Lit(Literal::nat(42));
707        let def = Declaration::Definition {
708            name: Name::str("answer"),
709            univ_params: vec![],
710            ty: nat_ty,
711            val,
712            hint: ReducibilityHint::Regular(1),
713        };
714        check_declaration(&mut env, def).expect("value should be present");
715        assert!(env.contains(&Name::str("answer")));
716    }
717    #[test]
718    fn test_type_mismatch() {
719        let mut env = Environment::new();
720        let type0 = mk_type0();
721        env.add(Declaration::Axiom {
722            name: Name::str("String"),
723            univ_params: vec![],
724            ty: type0.clone(),
725        })
726        .expect("value should be present");
727        env.add(Declaration::Axiom {
728            name: Name::str("Nat"),
729            univ_params: vec![],
730            ty: type0,
731        })
732        .expect("value should be present");
733        let string_ty = Expr::Const(Name::str("String"), vec![]);
734        let val = Expr::Lit(Literal::nat(42));
735        let def = Declaration::Definition {
736            name: Name::str("bad"),
737            univ_params: vec![],
738            ty: string_ty,
739            val,
740            hint: ReducibilityHint::Regular(1),
741        };
742        let result = check_declaration(&mut env, def);
743        assert!(result.is_err());
744    }
745    #[test]
746    fn test_check_declarations_sequence() {
747        let mut env = Environment::new();
748        let decls = vec![
749            Declaration::Axiom {
750                name: Name::str("A"),
751                univ_params: vec![],
752                ty: mk_type0(),
753            },
754            Declaration::Axiom {
755                name: Name::str("B"),
756                univ_params: vec![],
757                ty: mk_type0(),
758            },
759        ];
760        check_declarations(&mut env, decls).expect("value should be present");
761        assert!(env.contains(&Name::str("A")));
762        assert!(env.contains(&Name::str("B")));
763    }
764    #[test]
765    fn test_decl_kind_display() {
766        assert_eq!(DeclKind::Axiom.as_str(), "axiom");
767        assert_eq!(DeclKind::Definition.as_str(), "def");
768        assert_eq!(DeclKind::Theorem.as_str(), "theorem");
769        assert_eq!(DeclKind::Opaque.as_str(), "opaque");
770        assert!(DeclKind::Theorem.is_proven());
771        assert!(DeclKind::Axiom.is_assumed());
772        assert!(DeclKind::Definition.is_definition());
773        assert!(!DeclKind::Axiom.is_definition());
774    }
775    #[test]
776    fn test_summarize_declaration() {
777        let decl = mk_nat_axiom();
778        let summary = summarize_declaration(&decl);
779        assert_eq!(summary.name, "Nat");
780        assert_eq!(summary.kind, DeclKind::Axiom);
781        assert!(!summary.has_body);
782        assert_eq!(summary.num_univ_params, 0);
783    }
784    #[test]
785    fn test_is_valid_decl_name() {
786        assert!(is_valid_decl_name("Nat"));
787        assert!(is_valid_decl_name("Nat.succ"));
788        assert!(is_valid_decl_name("foo_bar"));
789        assert!(!is_valid_decl_name(""));
790        assert!(!is_valid_decl_name(".leading_dot"));
791        assert!(!is_valid_decl_name("trailing_dot."));
792    }
793    #[test]
794    fn test_has_unique_univ_params() {
795        let u = Name::str("u");
796        let v = Name::str("v");
797        let params = vec![u.clone(), v.clone()];
798        assert!(has_unique_univ_params(&params));
799        let dup_params = vec![u.clone(), u.clone()];
800        assert!(!has_unique_univ_params(&dup_params));
801    }
802    #[test]
803    fn test_check_config_default() {
804        let cfg = CheckConfig::default();
805        assert!(cfg.check_type_is_sort);
806        assert!(cfg.check_value_type);
807        assert!(cfg.check_no_free_vars);
808    }
809    #[test]
810    fn test_check_config_lenient() {
811        let cfg = CheckConfig::lenient();
812        assert!(cfg.check_type_is_sort);
813        assert!(!cfg.check_value_type);
814    }
815    #[test]
816    fn test_check_config_strict() {
817        let cfg = CheckConfig::strict();
818        assert!(!cfg.allow_axioms);
819        assert!(cfg.max_depth > 0);
820    }
821    #[test]
822    fn test_batch_check_all_ok() {
823        let mut env = Environment::new();
824        let decls = vec![
825            Declaration::Axiom {
826                name: Name::str("X"),
827                univ_params: vec![],
828                ty: mk_type0(),
829            },
830            Declaration::Axiom {
831                name: Name::str("Y"),
832                univ_params: vec![],
833                ty: mk_type0(),
834            },
835        ];
836        let result = check_declarations_collect_errors(&mut env, decls);
837        assert!(result.all_ok());
838        assert_eq!(result.num_ok, 2);
839        assert_eq!(result.num_failed, 0);
840    }
841    #[test]
842    fn test_batch_check_with_error() {
843        let mut env = Environment::new();
844        let decls = vec![
845            Declaration::Axiom {
846                name: Name::str("M"),
847                univ_params: vec![],
848                ty: mk_type0(),
849            },
850            Declaration::Definition {
851                name: Name::str("bad_def"),
852                univ_params: vec![],
853                ty: Expr::Const(Name::str("UndefinedType"), vec![]),
854                val: Expr::Lit(Literal::nat(0)),
855                hint: ReducibilityHint::Regular(1),
856            },
857        ];
858        let result = check_declarations_collect_errors(&mut env, decls);
859        assert!(!result.all_ok());
860        assert_eq!(result.num_ok, 1);
861        assert_eq!(result.num_failed, 1);
862    }
863    #[test]
864    fn test_env_builder_clean() {
865        let mut builder = EnvBuilder::new();
866        let added = builder.add(Declaration::Axiom {
867            name: Name::str("P"),
868            univ_params: vec![],
869            ty: mk_type0(),
870        });
871        assert!(added);
872        assert!(builder.is_clean());
873        assert_eq!(builder.stats().num_axioms, 1);
874    }
875    #[test]
876    fn test_env_builder_with_error() {
877        let mut builder = EnvBuilder::new();
878        let added = builder.add(Declaration::Definition {
879            name: Name::str("oops"),
880            univ_params: vec![],
881            ty: Expr::Const(Name::str("NoSuchType"), vec![]),
882            val: Expr::Lit(Literal::nat(0)),
883            hint: ReducibilityHint::Regular(1),
884        });
885        assert!(!added);
886        assert!(!builder.is_clean());
887        assert_eq!(builder.errors().len(), 1);
888    }
889    #[test]
890    fn test_well_formed_result_display() {
891        let ok = WellFormedResult::Ok;
892        assert!(ok.is_ok());
893        assert_eq!(ok.description(), "well-formed");
894        let mismatch = WellFormedResult::TypeMismatch {
895            description: "Nat vs String".to_string(),
896        };
897        assert!(!mismatch.is_ok());
898        assert!(mismatch.description().contains("Nat vs String"));
899    }
900    #[test]
901    fn test_is_structurally_valid_expr() {
902        let bad = Expr::BVar(0);
903        assert!(!is_structurally_valid_expr(&bad));
904        let sort = Expr::Sort(Level::zero());
905        assert!(is_structurally_valid_expr(&sort));
906        let lam = Expr::Lam(
907            crate::BinderInfo::Default,
908            Name::str("x"),
909            Node::new(Expr::Sort(Level::zero())),
910            Node::new(Expr::BVar(0)),
911        );
912        assert!(is_structurally_valid_expr(&lam));
913    }
914    #[test]
915    fn test_format_decl_summary() {
916        let summary = DeclSummary {
917            name: "Nat.add".to_string(),
918            kind: DeclKind::Definition,
919            has_body: true,
920            num_univ_params: 0,
921        };
922        let s = format_decl_summary(&summary);
923        assert!(s.contains("Nat.add"));
924        assert!(s.contains("def"));
925    }
926    #[test]
927    fn test_check_stats_totals() {
928        let stats = CheckStats {
929            num_axioms: 2,
930            num_definitions: 3,
931            num_theorems: 1,
932            num_opaques: 0,
933            num_failures: 1,
934        };
935        assert_eq!(stats.total(), 6);
936        assert_eq!(stats.num_ok(), 6);
937    }
938    #[test]
939    fn test_format_batch_result_all_ok() {
940        let result = BatchCheckResult {
941            num_ok: 3,
942            num_failed: 0,
943            errors: vec![],
944        };
945        let s = format_batch_result(&result);
946        assert!(s.contains("3"));
947    }
948    #[test]
949    fn test_temp_env_scope() {
950        let mut env = Environment::new();
951        {
952            let mut scope = TempEnvScope::new(&mut env);
953            let ok = scope.add(Declaration::Axiom {
954                name: Name::str("TempDecl"),
955                univ_params: vec![],
956                ty: mk_type0(),
957            });
958            assert!(ok.is_ok());
959            assert_eq!(scope.num_added(), 1);
960        }
961        assert!(env.contains(&Name::str("TempDecl")));
962    }
963    #[allow(dead_code)]
964    fn mk_type1() -> Expr {
965        Expr::Sort(Level::succ(Level::succ(Level::zero())))
966    }
967    #[test]
968    fn test_check_constant_info_axiom() {
969        use crate::declaration::{AxiomVal, ConstantVal};
970        let mut env = Environment::new();
971        let ci = ConstantInfo::Axiom(AxiomVal {
972            common: ConstantVal {
973                name: Name::str("MyAxiom"),
974                level_params: vec![],
975                ty: mk_type0(),
976            },
977            is_unsafe: false,
978        });
979        check_constant_info(&mut env, ci).expect("value should be present");
980        assert!(env.contains(&Name::str("MyAxiom")));
981    }
982    #[test]
983    fn test_check_constant_info_inductive_simple() {
984        use crate::declaration::{ConstantVal, InductiveVal};
985        let mut env = Environment::new();
986        let iv = InductiveVal {
987            common: ConstantVal {
988                name: Name::str("Bool"),
989                level_params: vec![],
990                ty: mk_type0(),
991            },
992            num_params: 0,
993            num_indices: 0,
994            all: vec![Name::str("Bool")],
995            ctors: vec![Name::str("Bool.true"), Name::str("Bool.false")],
996            num_nested: 0,
997            is_rec: false,
998            is_unsafe: false,
999            is_reflexive: false,
1000            is_prop: false,
1001        };
1002        let ci = ConstantInfo::Inductive(iv);
1003        check_constant_info(&mut env, ci).expect("value should be present");
1004        assert!(env.contains(&Name::str("Bool")));
1005    }
1006    #[test]
1007    fn test_check_constant_info_inductive_empty() {
1008        use crate::declaration::{ConstantVal, InductiveVal};
1009        let mut env = Environment::new();
1010        let iv = InductiveVal {
1011            common: ConstantVal {
1012                name: Name::str("Empty"),
1013                level_params: vec![],
1014                ty: mk_type0(),
1015            },
1016            num_params: 0,
1017            num_indices: 0,
1018            all: vec![Name::str("Empty")],
1019            ctors: vec![],
1020            num_nested: 0,
1021            is_rec: false,
1022            is_unsafe: false,
1023            is_reflexive: false,
1024            is_prop: false,
1025        };
1026        let ci = ConstantInfo::Inductive(iv);
1027        check_constant_info(&mut env, ci).expect("value should be present");
1028        assert!(env.contains(&Name::str("Empty")));
1029    }
1030    #[test]
1031    fn test_check_constant_info_constructor_valid() {
1032        use crate::declaration::{ConstantVal, ConstructorVal, InductiveVal};
1033        let mut env = Environment::new();
1034        let ind_ci = ConstantInfo::Inductive(InductiveVal {
1035            common: ConstantVal {
1036                name: Name::str("Bool"),
1037                level_params: vec![],
1038                ty: mk_type0(),
1039            },
1040            num_params: 0,
1041            num_indices: 0,
1042            all: vec![Name::str("Bool")],
1043            ctors: vec![Name::str("Bool.true")],
1044            num_nested: 0,
1045            is_rec: false,
1046            is_unsafe: false,
1047            is_reflexive: false,
1048            is_prop: false,
1049        });
1050        check_constant_info(&mut env, ind_ci).expect("value should be present");
1051        let ctor_ci = ConstantInfo::Constructor(ConstructorVal {
1052            common: ConstantVal {
1053                name: Name::str("Bool.true"),
1054                level_params: vec![],
1055                ty: Expr::Const(Name::str("Bool"), vec![]),
1056            },
1057            induct: Name::str("Bool"),
1058            cidx: 0,
1059            num_params: 0,
1060            num_fields: 0,
1061            is_unsafe: false,
1062        });
1063        check_constant_info(&mut env, ctor_ci).expect("value should be present");
1064        assert!(env.contains(&Name::str("Bool.true")));
1065    }
1066    #[test]
1067    fn test_check_constant_info_constructor_wrong_return_type() {
1068        use crate::declaration::{ConstantVal, ConstructorVal, InductiveVal};
1069        let mut env = Environment::new();
1070        env.add_constant(ConstantInfo::Inductive(InductiveVal {
1071            common: ConstantVal {
1072                name: Name::str("Bool"),
1073                level_params: vec![],
1074                ty: mk_type0(),
1075            },
1076            num_params: 0,
1077            num_indices: 0,
1078            all: vec![Name::str("Bool")],
1079            ctors: vec![Name::str("Bool.bad")],
1080            num_nested: 0,
1081            is_rec: false,
1082            is_unsafe: false,
1083            is_reflexive: false,
1084            is_prop: false,
1085        }))
1086        .expect("value should be present");
1087        env.add_constant(ConstantInfo::Inductive(InductiveVal {
1088            common: ConstantVal {
1089                name: Name::str("Nat"),
1090                level_params: vec![],
1091                ty: mk_type0(),
1092            },
1093            num_params: 0,
1094            num_indices: 0,
1095            all: vec![Name::str("Nat")],
1096            ctors: vec![],
1097            num_nested: 0,
1098            is_rec: false,
1099            is_unsafe: false,
1100            is_reflexive: false,
1101            is_prop: false,
1102        }))
1103        .expect("value should be present");
1104        let ctor_ci = ConstantInfo::Constructor(ConstructorVal {
1105            common: ConstantVal {
1106                name: Name::str("Bool.bad"),
1107                level_params: vec![],
1108                ty: Expr::Const(Name::str("Nat"), vec![]),
1109            },
1110            induct: Name::str("Bool"),
1111            cidx: 0,
1112            num_params: 0,
1113            num_fields: 0,
1114            is_unsafe: false,
1115        });
1116        let result = check_constant_info(&mut env, ctor_ci);
1117        assert!(result.is_err(), "expected error for wrong return type");
1118    }
1119    #[test]
1120    fn test_check_constant_info_recursor_rejects_bogus() {
1121        // Recursors are RE-DERIVED by the kernel and compared against the
1122        // supplied declaration: a recursor whose type is literally `Type`
1123        // with no rules must be rejected, never trusted (audit item S3).
1124        use crate::declaration::{ConstantVal, InductiveVal, RecursorVal};
1125        let mut env = Environment::new();
1126        env.add_constant(ConstantInfo::Inductive(InductiveVal {
1127            common: ConstantVal {
1128                name: Name::str("Bool"),
1129                level_params: vec![],
1130                ty: mk_type0(),
1131            },
1132            num_params: 0,
1133            num_indices: 0,
1134            all: vec![Name::str("Bool")],
1135            ctors: vec![],
1136            num_nested: 0,
1137            is_rec: false,
1138            is_unsafe: false,
1139            is_reflexive: false,
1140            is_prop: false,
1141        }))
1142        .expect("value should be present");
1143        let rec_ty = mk_type0();
1144        let rec_ci = ConstantInfo::Recursor(RecursorVal {
1145            common: ConstantVal {
1146                name: Name::str("Bool.rec"),
1147                level_params: vec![],
1148                ty: rec_ty,
1149            },
1150            all: vec![Name::str("Bool")],
1151            num_params: 0,
1152            num_indices: 0,
1153            num_motives: 1,
1154            num_minors: 2,
1155            rules: vec![],
1156            k: false,
1157            is_unsafe: false,
1158        });
1159        let result = check_constant_info(&mut env, rec_ci);
1160        assert!(
1161            result.is_err(),
1162            "hand-supplied bogus recursor must be rejected"
1163        );
1164        assert!(!env.contains(&Name::str("Bool.rec")));
1165    }
1166    #[test]
1167    fn test_check_constant_info_quotient_rejects_noncanonical() {
1168        // Quotient constants must have their kernel-canonical type. A caller
1169        // supplying `Quot : Sort 0` (with no universe params) must be rejected:
1170        // both by `check_quotient_val` (wrong type / universe params) and by
1171        // `Environment::add_constant` (quotients only via `add_quot`).
1172        use crate::declaration::{ConstantVal, QuotKind, QuotVal};
1173        let mut env = Environment::new();
1174        let quot_ci = ConstantInfo::Quotient(QuotVal {
1175            common: ConstantVal {
1176                name: Name::str("Quot"),
1177                level_params: vec![],
1178                ty: mk_type0(),
1179            },
1180            kind: QuotKind::Type,
1181        });
1182        assert!(
1183            check_constant_info(&mut env, quot_ci).is_err(),
1184            "non-canonical quotient type must be rejected"
1185        );
1186        assert!(!env.contains(&Name::str("Quot")));
1187    }
1188    #[test]
1189    fn test_check_constant_infos_sequence() {
1190        use crate::declaration::{AxiomVal, ConstantVal};
1191        let mut env = Environment::new();
1192        let cis = vec![
1193            ConstantInfo::Axiom(AxiomVal {
1194                common: ConstantVal {
1195                    name: Name::str("P"),
1196                    level_params: vec![],
1197                    ty: mk_type0(),
1198                },
1199                is_unsafe: false,
1200            }),
1201            ConstantInfo::Axiom(AxiomVal {
1202                common: ConstantVal {
1203                    name: Name::str("Q"),
1204                    level_params: vec![],
1205                    ty: mk_type0(),
1206                },
1207                is_unsafe: false,
1208            }),
1209        ];
1210        check_constant_infos(&mut env, cis).expect("value should be present");
1211        assert!(env.contains(&Name::str("P")));
1212        assert!(env.contains(&Name::str("Q")));
1213    }
1214    #[test]
1215    fn test_check_constant_info_duplicate() {
1216        use crate::declaration::{AxiomVal, ConstantVal};
1217        let mut env = Environment::new();
1218        let make_ci = || {
1219            ConstantInfo::Axiom(AxiomVal {
1220                common: ConstantVal {
1221                    name: Name::str("Dup"),
1222                    level_params: vec![],
1223                    ty: mk_type0(),
1224                },
1225                is_unsafe: false,
1226            })
1227        };
1228        check_constant_info(&mut env, make_ci()).expect("value should be present");
1229        let result = check_constant_info(&mut env, make_ci());
1230        assert!(result.is_err(), "duplicate should fail");
1231    }
1232}
1233#[cfg(test)]
1234mod tests_padding_infra {
1235    use super::*;
1236    #[test]
1237    fn test_stat_summary() {
1238        let mut ss = StatSummary::new();
1239        ss.record(10.0);
1240        ss.record(20.0);
1241        ss.record(30.0);
1242        assert_eq!(ss.count(), 3);
1243        assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1244        assert_eq!(ss.min().expect("min should succeed") as i64, 10);
1245        assert_eq!(ss.max().expect("max should succeed") as i64, 30);
1246    }
1247    #[test]
1248    fn test_transform_stat() {
1249        let mut ts = TransformStat::new();
1250        ts.record_before(100.0);
1251        ts.record_after(80.0);
1252        let ratio = ts.mean_ratio().expect("ratio should be present");
1253        assert!((ratio - 0.8).abs() < 1e-9);
1254    }
1255    #[test]
1256    fn test_small_map() {
1257        let mut m: SmallMap<u32, &str> = SmallMap::new();
1258        m.insert(3, "three");
1259        m.insert(1, "one");
1260        m.insert(2, "two");
1261        assert_eq!(m.get(&2), Some(&"two"));
1262        assert_eq!(m.len(), 3);
1263        let keys = m.keys();
1264        assert_eq!(*keys[0], 1);
1265        assert_eq!(*keys[2], 3);
1266    }
1267    #[test]
1268    fn test_label_set() {
1269        let mut ls = LabelSet::new();
1270        ls.add("foo");
1271        ls.add("bar");
1272        ls.add("foo");
1273        assert_eq!(ls.count(), 2);
1274        assert!(ls.has("bar"));
1275        assert!(!ls.has("baz"));
1276    }
1277    #[test]
1278    fn test_config_node() {
1279        let mut root = ConfigNode::section("root");
1280        let child = ConfigNode::leaf("key", "value");
1281        root.add_child(child);
1282        assert_eq!(root.num_children(), 1);
1283    }
1284    #[test]
1285    fn test_versioned_record() {
1286        let mut vr = VersionedRecord::new(0u32);
1287        vr.update(1);
1288        vr.update(2);
1289        assert_eq!(*vr.current(), 2);
1290        assert_eq!(vr.version(), 2);
1291        assert!(vr.has_history());
1292        assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
1293    }
1294    #[test]
1295    fn test_simple_dag() {
1296        let mut dag = SimpleDag::new(4);
1297        dag.add_edge(0, 1);
1298        dag.add_edge(1, 2);
1299        dag.add_edge(2, 3);
1300        assert!(dag.can_reach(0, 3));
1301        assert!(!dag.can_reach(3, 0));
1302        let order = dag.topological_sort().expect("order should be present");
1303        assert_eq!(order, vec![0, 1, 2, 3]);
1304    }
1305    #[test]
1306    fn test_focus_stack() {
1307        let mut fs: FocusStack<&str> = FocusStack::new();
1308        fs.focus("a");
1309        fs.focus("b");
1310        assert_eq!(fs.current(), Some(&"b"));
1311        assert_eq!(fs.depth(), 2);
1312        fs.blur();
1313        assert_eq!(fs.current(), Some(&"a"));
1314    }
1315}
1316#[cfg(test)]
1317mod tests_extra_iterators {
1318    use super::*;
1319    #[test]
1320    fn test_window_iterator() {
1321        let data = vec![1u32, 2, 3, 4, 5];
1322        let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
1323        assert_eq!(windows.len(), 3);
1324        assert_eq!(windows[0], &[1, 2, 3]);
1325        assert_eq!(windows[2], &[3, 4, 5]);
1326    }
1327    #[test]
1328    fn test_non_empty_vec() {
1329        let mut nev = NonEmptyVec::singleton(10u32);
1330        nev.push(20);
1331        nev.push(30);
1332        assert_eq!(nev.len(), 3);
1333        assert_eq!(*nev.first(), 10);
1334        assert_eq!(*nev.last(), 30);
1335    }
1336}
1337#[cfg(test)]
1338mod tests_padding2 {
1339    use super::*;
1340    #[test]
1341    fn test_sliding_sum() {
1342        let mut ss = SlidingSum::new(3);
1343        ss.push(1.0);
1344        ss.push(2.0);
1345        ss.push(3.0);
1346        assert!((ss.sum() - 6.0).abs() < 1e-9);
1347        ss.push(4.0);
1348        assert!((ss.sum() - 9.0).abs() < 1e-9);
1349        assert_eq!(ss.count(), 3);
1350    }
1351    #[test]
1352    fn test_path_buf() {
1353        let mut pb = PathBuf::new();
1354        pb.push("src");
1355        pb.push("main");
1356        assert_eq!(pb.as_str(), "src/main");
1357        assert_eq!(pb.depth(), 2);
1358        pb.pop();
1359        assert_eq!(pb.as_str(), "src");
1360    }
1361    #[test]
1362    fn test_string_pool() {
1363        let mut pool = StringPool::new();
1364        let s = pool.take();
1365        assert!(s.is_empty());
1366        pool.give("hello".to_string());
1367        let s2 = pool.take();
1368        assert!(s2.is_empty());
1369        assert_eq!(pool.free_count(), 0);
1370    }
1371    #[test]
1372    fn test_transitive_closure() {
1373        let mut tc = TransitiveClosure::new(4);
1374        tc.add_edge(0, 1);
1375        tc.add_edge(1, 2);
1376        tc.add_edge(2, 3);
1377        assert!(tc.can_reach(0, 3));
1378        assert!(!tc.can_reach(3, 0));
1379        let r = tc.reachable_from(0);
1380        assert_eq!(r.len(), 4);
1381    }
1382    #[test]
1383    fn test_token_bucket() {
1384        let mut tb = TokenBucket::new(100, 0);
1385        assert_eq!(tb.available(), 100);
1386        assert!(tb.try_consume(50));
1387        assert_eq!(tb.available(), 50);
1388        assert!(!tb.try_consume(60));
1389        assert_eq!(tb.capacity(), 100);
1390    }
1391    #[test]
1392    fn test_rewrite_rule_set() {
1393        let mut rrs = RewriteRuleSet::new();
1394        rrs.add(RewriteRule::unconditional(
1395            "beta",
1396            "App(Lam(x, b), v)",
1397            "b[x:=v]",
1398        ));
1399        rrs.add(RewriteRule::conditional("comm", "a + b", "b + a"));
1400        assert_eq!(rrs.len(), 2);
1401        assert_eq!(rrs.unconditional_rules().len(), 1);
1402        assert_eq!(rrs.conditional_rules().len(), 1);
1403        assert!(rrs.get("beta").is_some());
1404        let disp = rrs
1405            .get("beta")
1406            .expect("element at \'beta\' should exist")
1407            .display();
1408        assert!(disp.contains("→"));
1409    }
1410}
1411#[cfg(test)]
1412mod tests_padding3 {
1413    use super::*;
1414    #[test]
1415    fn test_decision_node() {
1416        let tree = DecisionNode::Branch {
1417            key: "x".into(),
1418            val: "1".into(),
1419            yes_branch: Box::new(DecisionNode::Leaf("yes".into())),
1420            no_branch: Box::new(DecisionNode::Leaf("no".into())),
1421        };
1422        let mut ctx = std::collections::HashMap::new();
1423        ctx.insert("x".into(), "1".into());
1424        assert_eq!(tree.evaluate(&ctx), "yes");
1425        ctx.insert("x".into(), "2".into());
1426        assert_eq!(tree.evaluate(&ctx), "no");
1427        assert_eq!(tree.depth(), 1);
1428    }
1429    #[test]
1430    fn test_flat_substitution() {
1431        let mut sub = FlatSubstitution::new();
1432        sub.add("foo", "bar");
1433        sub.add("baz", "qux");
1434        assert_eq!(sub.apply("foo and baz"), "bar and qux");
1435        assert_eq!(sub.len(), 2);
1436    }
1437    #[test]
1438    fn test_stopwatch() {
1439        let mut sw = Stopwatch::start();
1440        sw.split();
1441        sw.split();
1442        assert_eq!(sw.num_splits(), 2);
1443        assert!(sw.elapsed_ms() >= 0.0);
1444        for &s in sw.splits() {
1445            assert!(s >= 0.0);
1446        }
1447    }
1448    #[test]
1449    fn test_either2() {
1450        let e: Either2<i32, &str> = Either2::First(42);
1451        assert!(e.is_first());
1452        let mapped = e.map_first(|x| x * 2);
1453        assert_eq!(mapped.first(), Some(84));
1454        let e2: Either2<i32, &str> = Either2::Second("hello");
1455        assert!(e2.is_second());
1456        assert_eq!(e2.second(), Some("hello"));
1457    }
1458    #[test]
1459    fn test_write_once() {
1460        let wo: WriteOnce<u32> = WriteOnce::new();
1461        assert!(!wo.is_written());
1462        assert!(wo.write(42));
1463        assert!(!wo.write(99));
1464        assert_eq!(wo.read(), Some(42));
1465    }
1466    #[test]
1467    fn test_sparse_vec() {
1468        let mut sv: SparseVec<i32> = SparseVec::new(100);
1469        sv.set(5, 10);
1470        sv.set(50, 20);
1471        assert_eq!(*sv.get(5), 10);
1472        assert_eq!(*sv.get(50), 20);
1473        assert_eq!(*sv.get(0), 0);
1474        assert_eq!(sv.nnz(), 2);
1475        sv.set(5, 0);
1476        assert_eq!(sv.nnz(), 1);
1477    }
1478    #[test]
1479    fn test_stack_calc() {
1480        let mut calc = StackCalc::new();
1481        calc.push(3);
1482        calc.push(4);
1483        assert_eq!(calc.add(), Some(7));
1484        assert_eq!(calc.peek(), Some(7));
1485        calc.push(2);
1486        assert_eq!(calc.mul(), Some(14));
1487        assert_eq!(calc.peek(), Some(14));
1488    }
1489}