Skip to main content

oxilean_elab/
lib.rs

1#![allow(unused_imports)]
2
3//! # OxiLean Elaborator — From Surface Syntax to Typed Kernel Terms
4//!
5//! The elaborator bridges the gap between user-written OxiLean code (surface syntax)
6//! and kernel-verified terms. It performs type inference, implicit argument resolution,
7//! unification, and tactic execution.
8//!
9//! ## Quick Start
10//!
11//! ### Elaborating an Expression
12//!
13//! ```ignore
14//! use oxilean_elab::{ElabContext, elaborate_expr};
15//! use oxilean_kernel::Environment;
16//!
17//! let env = Environment::new();
18//! let ctx = ElabContext::new(&env);
19//! let surface_expr = /* parsed from source */;
20//! let (kernel_expr, ty) = elaborate_expr(&ctx, surface_expr)?;
21//! ```
22//!
23//! ### Elaborating a Declaration
24//!
25//! ```ignore
26//! use oxilean_elab::elaborate_decl;
27//!
28//! let decl = /* parsed surface declaration */;
29//! let elaborator = DeclElaborator::new(&ctx);
30//! elaborator.elaborate(decl)?;
31//! ```
32//!
33//! ## Architecture Overview
34//!
35//! The elaborator is a multi-stage system:
36//!
37//! ```text
38//! Surface Syntax AST (from parser)
39//!     │
40//!     ▼
41//! ┌───────────────────────────┐
42//! │  Name Resolution          │  → Resolve names to definitions
43//! │  (context.rs, elab_decl)  │  → Build local contexts
44//! └───────────────────────────┘
45//!     │
46//!     ▼
47//! ┌───────────────────────────┐
48//! │  Type Inference           │  → Synthesize types for unknowns
49//! │  (infer.rs)               │  → Generate metavariables (?m)
50//! └───────────────────────────┘
51//!     │
52//!     ▼
53//! ┌───────────────────────────┐
54//! │  Implicit Arg Resolution  │  → Resolve {x} and [x]
55//! │  (implicit.rs)            │  → Unify with inferred types
56//! └───────────────────────────┘
57//!     │
58//!     ▼
59//! ┌───────────────────────────┐
60//! │  Unification & Solving    │  → Solve metavariable constraints
61//! │  (unify.rs, solver.rs)    │  → Higher-order unification
62//! └───────────────────────────┘
63//!     │
64//!     ▼
65//! ┌───────────────────────────┐
66//! │  Elaboration Passes       │  → Coercions, macros, tactics
67//! │  (elaborate.rs)           │  → Build kernel terms
68//! └───────────────────────────┘
69//!     │
70//!     ▼
71//! Kernel Expressions (fully elaborated)
72//!     │
73//!     └─→ Kernel Type Checker (kernel TCB validates)
74//!     └─→ Environment (if type-check passes)
75//! ```
76//!
77//! ## Key Concepts & Terminology
78//!
79//! ### Metavariables
80//!
81//! Metavariables (`?m`, `?t`, `?P`) represent unknown terms during elaboration:
82//! - `?m : τ` = metavariable with type τ
83//! - Initially unsolved
84//! - Solved via unification constraints
85//! - Must be fully solved before kernel validation
86//!
87//! Example:
88//! ```text
89//! User writes: let x := _ in x + 1
90//! Elaborator creates: let x := ?m in x + 1
91//! Constraint: ?m : Nat (inferred from context)
92//! Solution: ?m = 5 (if inferrable from usage)
93//! Final: let x := 5 in x + 1
94//! ```
95//!
96//! ### Type Inference
97//!
98//! Bidirectional type checking:
99//! - **Synthesis** (↑): Infer type of expression `expr ↑ τ`
100//! - **Checking** (↓): Check expression against type `expr ↓ τ`
101//!
102//! Example:
103//! ```text
104//! Synth: f x ↑ ?τ
105//!   ├─ Synth: f ↑ (α → β)
106//!   ├─ Check: x ↓ α
107//!   └─ Result: β
108//!
109//! Check: fun x => x + 1 ↓ Nat → Nat
110//!   ├─ Expect x : Nat
111//!   └─ Check: x + 1 ↓ Nat
112//! ```
113//!
114//! ### Implicit Arguments
115//!
116//! Arguments can be:
117//! - **Explicit** `(x : T)`: Must be provided by user
118//! - **Implicit** `{x : T}`: Inferred from context
119//! - **Instance** `[C x]`: Filled by typeclass resolution
120//!
121//! The elaborator inserts placeholder metavariables for implicit args,
122//! then solves them via unification.
123//!
124//! ### Unification
125//!
126//! Solves constraints of the form `?m =?= expr`:
127//! - **First-order**: `?m = f x`
128//! - **Higher-order**: `?f = λ x. ?body`
129//! - **Occurs check**: Prevents infinite types like `?m = f ?m`
130//!
131//! ### Elaboration Context
132//!
133//! Manages state during elaboration:
134//! - **Local variables**: `x : T` in scope
135//! - **Metavariables**: Mapping from `MetaVarId` to assignment
136//! - **Configuration**: Options for tactics, coercions, etc.
137//! - **Environment**: Global definitions and axioms
138//!
139//! ## Module Organization
140//!
141//! ### Core Elaboration Modules
142//!
143//! | Module | Purpose |
144//! |--------|---------|
145//! | `context` | Elaboration context and local variable management |
146//! | `metavar` | Metavariable creation and tracking |
147//! | `infer` | Type synthesis and checking |
148//! | `unify` | Higher-order unification algorithm |
149//!
150//! ### Expression & Declaration Elaboration
151//!
152//! | Module | Purpose |
153//! |--------|---------|
154//! | `elaborate` | Main expression elaboration pipeline |
155//! | `elab_decl` | Declaration (def, theorem, etc.) elaboration |
156//! | `binder` | Binder (`fun`, `forall`) elaboration |
157//!
158//! ### Argument Resolution
159//!
160//! | Module | Purpose |
161//! |--------|---------|
162//! | `implicit` | Implicit argument resolution and synthesis |
163//! | `instance` | Typeclass instance resolution |
164//!
165//! ### Advanced Features
166//!
167//! | Module | Purpose |
168//! |--------|---------|
169//! | `unify` | Higher-order unification |
170//! | `solver` | Constraint solver |
171//! | `coercion` | Type coercion insertion |
172//! | `pattern_match` | Pattern matching elaboration |
173//! | `mutual` | Mutual recursion validation |
174//! | `equation` | Equation compiler (pattern matching) |
175//!
176//! ### Tactics
177//!
178//! | Module | Purpose |
179//! |--------|---------|
180//! | `tactic` | Tactic engine and core tactics |
181//! | `tactic::intro` | `intro` tactic (introduce binders) |
182//! | `tactic::apply` | `apply` tactic (apply theorem) |
183//! | `tactic::exact` | `exact` tactic (provide term directly) |
184//! | `tactic::rw` | `rw` tactic (rewrite by equality) |
185//!
186//! ### Utilities
187//!
188//! | Module | Purpose |
189//! |--------|---------|
190//! | `error_msg` | Error message formatting |
191//! | `notation` | Notation expansion |
192//! | `macro_expand` | Macro expansion |
193//! | `attribute` | Attribute processing (e.g., `@[simp]`) |
194//! | `derive` | Deriving instances (e.g., `Eq`, `Show`) |
195//! | `trace` | Debug tracing |
196//!
197//! ## Elaboration Pipeline Details
198//!
199//! ### Phase 1: Name Resolution
200//!
201//! Converts surface names to qualified names:
202//! - Local variables: `x` → local index
203//! - Global constants: `Nat.add` → fully qualified path
204//! - Scoped opens: Apply namespace resolution
205//! - Shadowing: Inner scopes shadow outer
206//!
207//! ### Phase 2: Type Inference
208//!
209//! Generates type constraints:
210//! - For each subexpression, create metavariable for unknown type
211//! - Collect unification constraints
212//! - Maintain bidirectional flow (synthesis ↑, checking ↓)
213//!
214//! ### Phase 3: Implicit Argument Synthesis
215//!
216//! Fill in implicit arguments:
217//! - For each implicit parameter, create metavariable\
218//! - Pass to unification solver
219//! - Fails if multiple solutions or no solution\
220//!
221//! ### Phase 4: Unification & Solving
222//!
223//! Solve all metavariable constraints:
224//! - Higher-order unification algorithm
225//! - Occurs check to prevent infinite terms
226//! - Backtracking for multiple solutions
227//!
228//! ### Phase 5: Term Construction
229//!
230//! Build final kernel `Expr`:
231//! - Substitute metavars with solutions
232//! - Insert coercions where needed
233//! - Expand macros
234//! - Validate termination
235//!
236//! ### Phase 6: Tactic Execution (for proofs)
237//!
238//! If proof is `by <tactic>`:
239//! - Execute tactic to transform goal
240//! - May generate new subgoals
241//! - Recursively elaborate subgoals\
242//!
243//! ### Phase 7: Kernel Validation
244//!
245//! Pass to kernel type checker:
246//! - Independent verification
247//! - All metavars must be solved
248//! - Type check must succeed
249//!
250//! ## Usage Examples
251//!
252//! ### Example 1: Simple Expression
253//!
254//! ```ignore
255//! use oxilean_elab::{ElabContext, elaborate_expr};
256//!
257//! let ctx = ElabContext::new(&env);
258//! let expr = SurfaceExpr::app(
259//!     SurfaceExpr::const_("Nat.add"),
260//!     vec![SurfaceExpr::lit(5), SurfaceExpr::lit(3)],
261//! );
262//! let (kernel_expr, ty) = elaborate_expr(&ctx, expr)?;
263//! // kernel_expr is now fully elaborated and type-checked
264//! ```
265//!
266//! ### Example 2: Function with Implicit Arguments
267//!
268//! ```ignore
269//! // User writes: myFunc x (where myFunc has implicit args)
270//! // Elaborator infers and fills implicit args from context
271//! let elaborate = elaborate_expr(&ctx, surface_expr)?;
272//! ```
273//!
274//! ### Example 3: Tactic-Based Proof
275//!
276//! ```text
277//! // User writes: theorem my_thm : P := by intro h; exact h
278//! // Elaborator:
279//! //   1. Creates goal: ⊢ P
280//! //   2. Runs tactic: intro h
281//! //   3. New goal: h : ⊢ P (with h in context)
282//! //   4. Runs tactic: exact h
283//! //   5. Constructs proof term
284//! ```
285//!
286//! ## Error Handling
287//!
288//! Elaboration errors include:
289//! - **Type mismatch**: Expected type τ, got σ
290//! - **Unification failure**: Cannot unify terms
291//! - **Unsolved metavariables**: `?m` remains after elaboration
292//! - **Unknown identifier**: Name not in scope
293//! - **Ambiguous instance**: Multiple typeclass instances match
294//! - **Tactic error**: Tactic failed or invalid in context\
295//!
296//! All errors carry source location and helpful context messages.
297//!
298//! ## Tactic System
299//!
300//! Tactics are proof-building procedures:
301//! - **Interactive**: In REPL or IDE
302//! - **Automated**: Called during elaboration
303//!
304//! Core tactics:
305//! - `intro`: Introduce binders from goal type
306//! - `apply`: Apply theorem to goal
307//! - `exact`: Provide exact proof term
308//! - `rw`: Rewrite using equality
309//! - `simp`: Simplify using lemmas
310//! - `cases`: Case analysis on inductive type
311//! - `induction`: Inductive reasoning\
312//!
313//! ## Integration with Other Crates
314//!
315//! ### With oxilean-kernel
316//!
317//! - Uses kernel `Expr`, `Environment`, `TypeChecker`
318//! - Passes elaborated terms to kernel for validation
319//! - Kernel is **never** bypassed
320//!
321//! ### With oxilean-parse
322//!
323//! - Consumes `SurfaceExpr`, `SurfaceDecl` from parser
324//! - Converts to kernel types
325//!
326//! ### With oxilean-meta
327//!
328//! - Meta layer provides metavariable-aware operations
329//! - Extends kernel WHNF, DefEq, TypeInfer with metavar support
330//!
331//! ## Performance Considerations
332//!
333//! - **Metavar allocation**: O(1) arena insertion
334//! - **Unification**: O(expr_size) per constraint
335//! - **Memoization**: Avoid re-elaborating same subexpressions
336//! - **Lazy evaluation**: Defer expensive operations (normalization)\
337//!
338//! ## Further Reading
339//!
340//! - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System architecture
341//! - Module documentation for specific subcomponents
342
343#![allow(missing_docs)]
344#![warn(clippy::all)]
345#![allow(clippy::result_large_err)]
346#![allow(clippy::field_reassign_with_default)]
347#![allow(clippy::ptr_arg)]
348#![allow(clippy::derivable_impls)]
349#![allow(clippy::len_without_is_empty)]
350#![allow(clippy::should_implement_trait)]
351#![allow(clippy::type_complexity)]
352#![allow(clippy::collapsible_if)]
353#![allow(clippy::single_match)]
354#![allow(clippy::needless_ifs)]
355#![allow(clippy::useless_format)]
356#![allow(clippy::new_without_default)]
357#![allow(clippy::manual_strip)]
358#![allow(clippy::needless_borrows_for_generic_args)]
359#![allow(clippy::manual_saturating_arithmetic)]
360#![allow(clippy::manual_is_variant_and)]
361#![allow(clippy::iter_kv_map)]
362#![allow(clippy::if_same_then_else)]
363#![allow(clippy::collapsible_str_replace)]
364#![allow(clippy::bool_comparison)]
365#![allow(clippy::nonminimal_bool)]
366#![allow(clippy::manual_range_contains)]
367#![allow(clippy::len_zero)]
368#![allow(clippy::unnecessary_map_or)]
369#![allow(clippy::enum_variant_names)]
370#![allow(clippy::implicit_saturating_sub)]
371#![allow(clippy::to_string_in_format_args)]
372#![allow(clippy::incompatible_msrv)]
373#![allow(clippy::int_plus_one)]
374#![allow(clippy::manual_map)]
375#![allow(clippy::needless_bool)]
376#![allow(clippy::needless_else)]
377#![allow(clippy::clone_on_copy)]
378#![allow(clippy::inherent_to_string)]
379#![allow(clippy::manual_find)]
380#![allow(clippy::double_ended_iterator_last)]
381#![allow(clippy::for_kv_map)]
382#![allow(clippy::needless_splitn)]
383#![allow(clippy::trim_split_whitespace)]
384#![allow(clippy::useless_vec)]
385#![allow(clippy::cloned_ref_to_slice_refs)]
386#![allow(non_snake_case)]
387
388use oxilean_kernel::{Expr, Literal, Name};
389pub mod attribute;
390pub mod bench_support;
391pub mod binder;
392pub mod coercion;
393pub mod context;
394pub mod derive;
395/// Full do-notation elaboration (bind, pure, for, try-catch, return).
396pub mod do_notation;
397pub mod elab_decl;
398pub mod elab_expr;
399pub mod elaborate;
400pub mod equation;
401/// Error message formatting and reporting infrastructure.
402pub mod error_msg;
403pub mod implicit;
404pub mod infer;
405/// Info tree for IDE integration (hover, go-to-def, type-on-hover).
406pub mod info_tree;
407pub mod instance;
408pub mod macro_expand;
409pub mod metaprog;
410pub mod metavar;
411pub mod mutual;
412pub mod notation;
413pub mod parallel;
414pub mod pattern_match;
415/// Pre-definition analysis: well-foundedness, structural recursion, termination.
416pub mod predef;
417pub mod quote;
418pub mod solver;
419pub mod structure;
420pub mod tactic;
421pub mod tactic_auto;
422pub mod trace;
423pub mod typeclass;
424pub mod unify;
425
426pub mod command_elab;
427pub mod completion_provider;
428/// Delaborator: convert kernel Expr to surface syntax.
429pub mod delaborator;
430pub mod derive_adv;
431/// Differential testing framework: compare OxiLean elaboration against expected outputs.
432pub mod differential_test;
433pub mod elaboration_profiler;
434pub mod hover_info;
435pub mod lean4_compat;
436pub mod module_import;
437
438pub use attribute::{
439    apply_attributes, process_attributes, AttrEntry, AttrError, AttrHandler, AttributeManager,
440    ProcessedAttrs,
441};
442pub use binder::{BinderElabResult, BinderTypeResult};
443pub use coercion::{Coercion, CoercionRegistry};
444pub use context::{ElabContext, LocalEntry};
445pub use derive::{DerivableClass, DeriveRegistry, Deriver};
446pub use elab_decl::{elaborate_decl, DeclElabError, DeclElaborator, PendingDecl};
447pub use elaborate::elaborate_expr;
448pub use equation::{DecisionTree, Equation, EquationCompiler, Pattern};
449pub use implicit::{resolve_implicits, resolve_instance, ImplicitArg};
450pub use infer::{Constraint, InferResult, TypeInferencer};
451pub use instance::{InstanceDecl, InstanceResolver};
452pub use macro_expand::{MacroDef, MacroExpander};
453pub use metavar::{MetaVar, MetaVarContext};
454pub use mutual::{
455    CallGraph, MutualBlock, MutualChecker, MutualElabError, StructuralRecursion, TerminationKind,
456    WellFoundedRecursion,
457};
458pub use notation::{expand_do_notation, expand_list_literal, Notation, NotationRegistry};
459pub use pattern_match::{
460    check_exhaustive, check_redundant, elaborate_match, ElabPattern, ExhaustivenessResult,
461    MatchEquation, PatternCompiler,
462};
463pub use quote::{quote, unquote, QuoteContext};
464pub use solver::{is_unifiable, ConstraintSolver};
465pub use structure::{
466    FieldInfo, ProjectionDecl, StructElabError, StructureElaborator, StructureInfo,
467};
468pub use tactic::{
469    eval_tactic_block, tactic_apply, tactic_by_contra, tactic_cases, tactic_contrapose,
470    tactic_exact, tactic_induction, tactic_intro, tactic_push_neg, tactic_split, Goal, Tactic,
471    TacticError, TacticRegistry, TacticResult, TacticState,
472};
473pub use typeclass::{Instance, Method, TypeClass, TypeClassRegistry};
474pub use unify::unify;
475
476// ============================================================================
477// Elaboration Configuration & Pipeline Settings
478// ============================================================================
479
480/// Global configuration for the elaboration pipeline.
481#[allow(dead_code)]
482#[derive(Debug, Clone)]
483pub struct ElabConfig {
484    /// Maximum depth for elaboration recursion.
485    pub max_depth: u32,
486    /// Whether to use proof irrelevance when elaborating proofs.
487    pub proof_irrelevance: bool,
488    /// Whether to insert implicit arguments automatically.
489    pub auto_implicit: bool,
490    /// Whether to report incomplete instances as errors.
491    pub strict_instances: bool,
492    /// Maximum number of tactic steps per proof.
493    pub max_tactic_steps: u32,
494    /// Whether to enable tracing for debugging.
495    pub trace_elaboration: bool,
496    /// Whether to run the kernel type checker after elaboration.
497    pub kernel_check: bool,
498    /// Whether to allow sorry (placeholder proofs).
499    pub allow_sorry: bool,
500    /// Universe polymorphism level limit.
501    pub max_universe_level: u32,
502}
503
504impl Default for ElabConfig {
505    fn default() -> Self {
506        Self {
507            max_depth: 512,
508            proof_irrelevance: true,
509            auto_implicit: true,
510            strict_instances: false,
511            max_tactic_steps: 100_000,
512            trace_elaboration: false,
513            kernel_check: true,
514            allow_sorry: false,
515            max_universe_level: 100,
516        }
517    }
518}
519
520impl ElabConfig {
521    /// Create a configuration suitable for interactive / IDE use.
522    #[allow(dead_code)]
523    pub fn interactive() -> Self {
524        Self {
525            allow_sorry: true,
526            strict_instances: false,
527            ..Self::default()
528        }
529    }
530
531    /// Create a strict configuration for verified builds.
532    #[allow(dead_code)]
533    pub fn strict() -> Self {
534        Self {
535            allow_sorry: false,
536            strict_instances: true,
537            kernel_check: true,
538            ..Self::default()
539        }
540    }
541
542    /// Create a debug-tracing configuration.
543    #[allow(dead_code)]
544    pub fn debug() -> Self {
545        Self {
546            trace_elaboration: true,
547            ..Self::default()
548        }
549    }
550
551    /// Create a configuration for batch/compilation use.
552    #[allow(dead_code)]
553    pub fn batch() -> Self {
554        Self {
555            allow_sorry: false,
556            strict_instances: true,
557            kernel_check: true,
558            trace_elaboration: false,
559            ..Self::default()
560        }
561    }
562}
563
564/// Statistics collected during an elaboration run.
565#[allow(dead_code)]
566#[derive(Debug, Clone, Default)]
567pub struct ElabStats {
568    /// Number of declarations elaborated.
569    pub num_decls: usize,
570    /// Number of metavariables created.
571    pub num_mvars_created: usize,
572    /// Number of metavariables solved.
573    pub num_mvars_solved: usize,
574    /// Number of unification constraints solved.
575    pub num_unifications: usize,
576    /// Number of tactic steps executed.
577    pub num_tactic_steps: usize,
578    /// Number of instance lookups performed.
579    pub num_instance_lookups: usize,
580    /// Number of sorry placeholders encountered.
581    pub num_sorry: usize,
582    /// Number of coercions inserted.
583    pub num_coercions: usize,
584    /// Maximum recursion depth reached.
585    pub max_depth_reached: u32,
586}
587
588impl ElabStats {
589    /// Create a fresh stats instance.
590    #[allow(dead_code)]
591    pub fn new() -> Self {
592        Self::default()
593    }
594
595    /// Merge another stats object into this one.
596    #[allow(dead_code)]
597    pub fn merge(&mut self, other: &ElabStats) {
598        self.num_decls += other.num_decls;
599        self.num_mvars_created += other.num_mvars_created;
600        self.num_mvars_solved += other.num_mvars_solved;
601        self.num_unifications += other.num_unifications;
602        self.num_tactic_steps += other.num_tactic_steps;
603        self.num_instance_lookups += other.num_instance_lookups;
604        self.num_sorry += other.num_sorry;
605        self.num_coercions += other.num_coercions;
606        self.max_depth_reached = self.max_depth_reached.max(other.max_depth_reached);
607    }
608
609    /// Return the mvar solve rate as a fraction in [0, 1].
610    #[allow(dead_code)]
611    pub fn mvar_solve_rate(&self) -> f64 {
612        if self.num_mvars_created == 0 {
613            1.0
614        } else {
615            self.num_mvars_solved as f64 / self.num_mvars_created as f64
616        }
617    }
618
619    /// Check if all created metavariables were solved.
620    #[allow(dead_code)]
621    pub fn all_mvars_solved(&self) -> bool {
622        self.num_mvars_created == self.num_mvars_solved
623    }
624}
625
626/// Structured error codes for elaboration failures.
627#[allow(dead_code)]
628#[derive(Debug, Clone, PartialEq, Eq)]
629pub enum ElabErrorCode {
630    /// A name was not found in scope.
631    UnknownName,
632    /// Type mismatch: expected vs. actual type differ.
633    TypeMismatch,
634    /// A metavariable could not be solved.
635    UnsolvedMvar,
636    /// Multiple typeclass instances match.
637    AmbiguousInstance,
638    /// No typeclass instance found.
639    NoInstance,
640    /// Unification failed.
641    UnificationFailed,
642    /// Expression is ill-typed.
643    IllTyped,
644    /// Tactic execution failed.
645    TacticFailed,
646    /// Pattern matching is not exhaustive.
647    NonExhaustiveMatch,
648    /// Syntax error (propagated from parser).
649    SyntaxError,
650    /// The kernel rejected the term.
651    KernelRejected,
652    /// Sorry was used but not allowed.
653    SorryNotAllowed,
654    /// Recursion limit exceeded.
655    RecursionLimit,
656    /// Mutual recursion cycle detected.
657    MutualCycle,
658    /// Other/unclassified error.
659    Other,
660}
661
662impl std::fmt::Display for ElabErrorCode {
663    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
664        let s = match self {
665            ElabErrorCode::UnknownName => "unknown name",
666            ElabErrorCode::TypeMismatch => "type mismatch",
667            ElabErrorCode::UnsolvedMvar => "unsolved metavariable",
668            ElabErrorCode::AmbiguousInstance => "ambiguous instance",
669            ElabErrorCode::NoInstance => "no instance found",
670            ElabErrorCode::UnificationFailed => "unification failed",
671            ElabErrorCode::IllTyped => "ill-typed expression",
672            ElabErrorCode::TacticFailed => "tactic failed",
673            ElabErrorCode::NonExhaustiveMatch => "non-exhaustive match",
674            ElabErrorCode::SyntaxError => "syntax error",
675            ElabErrorCode::KernelRejected => "kernel rejected term",
676            ElabErrorCode::SorryNotAllowed => "sorry not allowed",
677            ElabErrorCode::RecursionLimit => "recursion limit exceeded",
678            ElabErrorCode::MutualCycle => "mutual recursion cycle",
679            ElabErrorCode::Other => "elaboration error",
680        };
681        write!(f, "{}", s)
682    }
683}
684
685/// Represents a named stage in the elaboration pipeline.
686#[allow(dead_code)]
687#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
688pub enum ElabStage {
689    /// Name resolution.
690    NameResolution,
691    /// Type inference.
692    TypeInference,
693    /// Implicit argument resolution.
694    ImplicitArgs,
695    /// Typeclass instance resolution.
696    InstanceResolution,
697    /// Higher-order unification.
698    Unification,
699    /// Coercion insertion.
700    Coercions,
701    /// Macro expansion.
702    MacroExpansion,
703    /// Tactic execution.
704    TacticExecution,
705    /// Kernel validation.
706    KernelValidation,
707}
708
709impl ElabStage {
710    /// Get all stages in pipeline order.
711    #[allow(dead_code)]
712    pub fn all_in_order() -> &'static [ElabStage] {
713        &[
714            ElabStage::NameResolution,
715            ElabStage::TypeInference,
716            ElabStage::ImplicitArgs,
717            ElabStage::InstanceResolution,
718            ElabStage::Unification,
719            ElabStage::Coercions,
720            ElabStage::MacroExpansion,
721            ElabStage::TacticExecution,
722            ElabStage::KernelValidation,
723        ]
724    }
725
726    /// Get a short name for this stage.
727    #[allow(dead_code)]
728    pub fn name(&self) -> &'static str {
729        match self {
730            ElabStage::NameResolution => "name_resolution",
731            ElabStage::TypeInference => "type_inference",
732            ElabStage::ImplicitArgs => "implicit_args",
733            ElabStage::InstanceResolution => "instance_resolution",
734            ElabStage::Unification => "unification",
735            ElabStage::Coercions => "coercions",
736            ElabStage::MacroExpansion => "macro_expansion",
737            ElabStage::TacticExecution => "tactic_execution",
738            ElabStage::KernelValidation => "kernel_validation",
739        }
740    }
741}
742
743/// Well-known attribute names used in elaboration.
744#[allow(dead_code)]
745pub mod attr_names {
746    /// `@[simp]` marks a lemma for use by simp.
747    pub const SIMP: &str = "simp";
748    /// `@[reducible]` marks a definition as always unfolded.
749    pub const REDUCIBLE: &str = "reducible";
750    /// `@[semireducible]` default reducibility.
751    pub const SEMIREDUCIBLE: &str = "semireducible";
752    /// `@[irreducible]` never unfolded.
753    pub const IRREDUCIBLE: &str = "irreducible";
754    /// `@[inline]` hint to inline during code generation.
755    pub const INLINE: &str = "inline";
756    /// `@[instance]` typeclass instance.
757    pub const INSTANCE: &str = "instance";
758    /// `@[class]` typeclass definition.
759    pub const CLASS: &str = "class";
760    /// `@[derive]` automatic instance derivation.
761    pub const DERIVE: &str = "derive";
762    /// `@[ext]` extensionality lemma.
763    pub const EXT: &str = "ext";
764    /// `@[norm_cast]` for norm_cast / push_cast tactics.
765    pub const NORM_CAST: &str = "norm_cast";
766    /// `@[protected]` name requires qualified access.
767    pub const PROTECTED: &str = "protected";
768    /// `@[macro]` macro definition.
769    pub const MACRO: &str = "macro";
770}
771
772#[cfg(test)]
773mod lib_tests {
774    use super::*;
775
776    #[test]
777    fn test_elab_config_default() {
778        let cfg = ElabConfig::default();
779        assert_eq!(cfg.max_depth, 512);
780        assert!(cfg.kernel_check);
781        assert!(!cfg.allow_sorry);
782    }
783
784    #[test]
785    fn test_elab_config_interactive() {
786        let cfg = ElabConfig::interactive();
787        assert!(cfg.allow_sorry);
788        assert!(!cfg.strict_instances);
789    }
790
791    #[test]
792    fn test_elab_config_strict() {
793        let cfg = ElabConfig::strict();
794        assert!(!cfg.allow_sorry);
795        assert!(cfg.strict_instances);
796        assert!(cfg.kernel_check);
797    }
798
799    #[test]
800    fn test_elab_config_batch() {
801        let cfg = ElabConfig::batch();
802        assert!(!cfg.allow_sorry);
803        assert!(!cfg.trace_elaboration);
804    }
805
806    #[test]
807    fn test_elab_config_debug() {
808        let cfg = ElabConfig::debug();
809        assert!(cfg.trace_elaboration);
810    }
811
812    #[test]
813    fn test_elab_stats_default() {
814        let s = ElabStats::new();
815        assert_eq!(s.num_decls, 0);
816        assert!(s.all_mvars_solved());
817        assert_eq!(s.mvar_solve_rate(), 1.0);
818    }
819
820    #[test]
821    fn test_elab_stats_merge() {
822        let mut s1 = ElabStats {
823            num_decls: 3,
824            num_mvars_created: 5,
825            num_mvars_solved: 5,
826            ..Default::default()
827        };
828        let s2 = ElabStats {
829            num_decls: 2,
830            num_mvars_created: 3,
831            num_mvars_solved: 2,
832            max_depth_reached: 100,
833            ..Default::default()
834        };
835        s1.merge(&s2);
836        assert_eq!(s1.num_decls, 5);
837        assert_eq!(s1.num_mvars_created, 8);
838        assert_eq!(s1.max_depth_reached, 100);
839    }
840
841    #[test]
842    fn test_elab_stats_mvar_rate() {
843        let s = ElabStats {
844            num_mvars_created: 10,
845            num_mvars_solved: 8,
846            ..Default::default()
847        };
848        let rate = s.mvar_solve_rate();
849        assert!((rate - 0.8).abs() < 1e-10);
850        assert!(!s.all_mvars_solved());
851    }
852
853    #[test]
854    fn test_elab_error_codes_display() {
855        assert_eq!(format!("{}", ElabErrorCode::TypeMismatch), "type mismatch");
856        assert_eq!(format!("{}", ElabErrorCode::UnknownName), "unknown name");
857        assert_eq!(format!("{}", ElabErrorCode::TacticFailed), "tactic failed");
858    }
859
860    #[test]
861    fn test_elab_stage_order() {
862        let stages = ElabStage::all_in_order();
863        assert_eq!(stages.len(), 9);
864        assert_eq!(stages[0], ElabStage::NameResolution);
865        assert_eq!(stages[8], ElabStage::KernelValidation);
866    }
867
868    #[test]
869    fn test_elab_stage_names() {
870        assert_eq!(ElabStage::Unification.name(), "unification");
871        assert_eq!(ElabStage::KernelValidation.name(), "kernel_validation");
872    }
873
874    #[test]
875    fn test_attr_names() {
876        assert_eq!(attr_names::SIMP, "simp");
877        assert_eq!(attr_names::INSTANCE, "instance");
878        assert_eq!(attr_names::DERIVE, "derive");
879    }
880
881    #[test]
882    fn test_elab_error_other() {
883        assert_eq!(format!("{}", ElabErrorCode::Other), "elaboration error");
884    }
885
886    #[test]
887    fn test_all_error_variants_display() {
888        let variants = [
889            ElabErrorCode::UnknownName,
890            ElabErrorCode::TypeMismatch,
891            ElabErrorCode::UnsolvedMvar,
892            ElabErrorCode::AmbiguousInstance,
893            ElabErrorCode::NoInstance,
894            ElabErrorCode::UnificationFailed,
895            ElabErrorCode::IllTyped,
896            ElabErrorCode::TacticFailed,
897            ElabErrorCode::NonExhaustiveMatch,
898            ElabErrorCode::SyntaxError,
899            ElabErrorCode::KernelRejected,
900            ElabErrorCode::SorryNotAllowed,
901            ElabErrorCode::RecursionLimit,
902            ElabErrorCode::MutualCycle,
903            ElabErrorCode::Other,
904        ];
905        for v in &variants {
906            assert!(!format!("{}", v).is_empty());
907        }
908    }
909}
910
911/// Pipeline configuration registry.
912///
913/// Allows registering custom elaboration passes to be run at specific stages.
914#[allow(dead_code)]
915#[derive(Default)]
916pub struct ElabPipelineRegistry {
917    /// Pre-processing passes run before type inference.
918    pre_passes: Vec<String>,
919    /// Post-processing passes run after type inference.
920    post_passes: Vec<String>,
921    /// Tactic preprocessing passes.
922    tactic_passes: Vec<String>,
923}
924
925impl ElabPipelineRegistry {
926    /// Create a new empty registry.
927    #[allow(dead_code)]
928    pub fn new() -> Self {
929        Self::default()
930    }
931
932    /// Register a pre-processing pass.
933    #[allow(dead_code)]
934    pub fn add_pre_pass(&mut self, pass_name: impl Into<String>) {
935        self.pre_passes.push(pass_name.into());
936    }
937
938    /// Register a post-processing pass.
939    #[allow(dead_code)]
940    pub fn add_post_pass(&mut self, pass_name: impl Into<String>) {
941        self.post_passes.push(pass_name.into());
942    }
943
944    /// Register a tactic preprocessing pass.
945    #[allow(dead_code)]
946    pub fn add_tactic_pass(&mut self, pass_name: impl Into<String>) {
947        self.tactic_passes.push(pass_name.into());
948    }
949
950    /// Get number of registered pre-passes.
951    #[allow(dead_code)]
952    pub fn num_pre_passes(&self) -> usize {
953        self.pre_passes.len()
954    }
955
956    /// Get number of registered post-passes.
957    #[allow(dead_code)]
958    pub fn num_post_passes(&self) -> usize {
959        self.post_passes.len()
960    }
961
962    /// Get number of registered tactic passes.
963    #[allow(dead_code)]
964    pub fn num_tactic_passes(&self) -> usize {
965        self.tactic_passes.len()
966    }
967
968    /// Get all pass names (pre + post + tactic).
969    #[allow(dead_code)]
970    pub fn all_passes(&self) -> Vec<&str> {
971        self.pre_passes
972            .iter()
973            .chain(self.post_passes.iter())
974            .chain(self.tactic_passes.iter())
975            .map(|s| s.as_str())
976            .collect()
977    }
978}
979
980#[cfg(test)]
981mod pipeline_tests {
982    use super::*;
983
984    #[test]
985    fn test_pipeline_registry_empty() {
986        let reg = ElabPipelineRegistry::new();
987        assert_eq!(reg.num_pre_passes(), 0);
988        assert_eq!(reg.num_post_passes(), 0);
989        assert!(reg.all_passes().is_empty());
990    }
991
992    #[test]
993    fn test_pipeline_registry_add_passes() {
994        let mut reg = ElabPipelineRegistry::new();
995        reg.add_pre_pass("normalize");
996        reg.add_post_pass("kernel_check");
997        reg.add_tactic_pass("simp_prep");
998        assert_eq!(reg.num_pre_passes(), 1);
999        assert_eq!(reg.num_post_passes(), 1);
1000        assert_eq!(reg.num_tactic_passes(), 1);
1001        assert_eq!(reg.all_passes().len(), 3);
1002    }
1003}
1004
1005// ============================================================================
1006// ElabNote: structured notes attached to declarations
1007// ============================================================================
1008
1009/// A structured note (hint, warning, or info) attached to an elaborated item.
1010#[allow(dead_code)]
1011#[derive(Debug, Clone, PartialEq, Eq)]
1012pub enum ElabNote {
1013    /// Hint about a potential improvement.
1014    Hint(String),
1015    /// Warning about a potential problem.
1016    Warning(String),
1017    /// Informational message.
1018    Info(String),
1019    /// A sorry was used.
1020    SorryUsed {
1021        /// The declaration that used sorry.
1022        declaration: String,
1023    },
1024    /// Implicit universe was introduced.
1025    ImplicitUniverse(String),
1026}
1027
1028impl ElabNote {
1029    /// Return a short prefix for display.
1030    #[allow(dead_code)]
1031    pub fn prefix(&self) -> &'static str {
1032        match self {
1033            ElabNote::Hint(_) => "hint",
1034            ElabNote::Warning(_) => "warning",
1035            ElabNote::Info(_) => "info",
1036            ElabNote::SorryUsed { .. } => "sorry",
1037            ElabNote::ImplicitUniverse(_) => "universe",
1038        }
1039    }
1040
1041    /// The message text.
1042    #[allow(dead_code)]
1043    pub fn message(&self) -> &str {
1044        match self {
1045            ElabNote::Hint(s)
1046            | ElabNote::Warning(s)
1047            | ElabNote::Info(s)
1048            | ElabNote::ImplicitUniverse(s) => s,
1049            ElabNote::SorryUsed { declaration } => declaration,
1050        }
1051    }
1052
1053    /// Whether this note is a warning or sorry.
1054    #[allow(dead_code)]
1055    pub fn is_warning_like(&self) -> bool {
1056        matches!(self, ElabNote::Warning(_) | ElabNote::SorryUsed { .. })
1057    }
1058}
1059
1060impl std::fmt::Display for ElabNote {
1061    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1062        write!(f, "[{}] {}", self.prefix(), self.message())
1063    }
1064}
1065
1066/// A collection of elaboration notes for a single declaration.
1067#[allow(dead_code)]
1068#[derive(Debug, Clone, Default)]
1069pub struct ElabNoteSet {
1070    notes: Vec<ElabNote>,
1071}
1072
1073impl ElabNoteSet {
1074    /// Create an empty note set.
1075    #[allow(dead_code)]
1076    pub fn new() -> Self {
1077        Self::default()
1078    }
1079
1080    /// Add a note.
1081    #[allow(dead_code)]
1082    pub fn add(&mut self, note: ElabNote) {
1083        self.notes.push(note);
1084    }
1085
1086    /// Add a hint.
1087    #[allow(dead_code)]
1088    pub fn add_hint(&mut self, msg: impl Into<String>) {
1089        self.add(ElabNote::Hint(msg.into()));
1090    }
1091
1092    /// Add a warning.
1093    #[allow(dead_code)]
1094    pub fn add_warning(&mut self, msg: impl Into<String>) {
1095        self.add(ElabNote::Warning(msg.into()));
1096    }
1097
1098    /// Add an info.
1099    #[allow(dead_code)]
1100    pub fn add_info(&mut self, msg: impl Into<String>) {
1101        self.add(ElabNote::Info(msg.into()));
1102    }
1103
1104    /// Record a sorry usage.
1105    #[allow(dead_code)]
1106    pub fn add_sorry(&mut self, decl: impl Into<String>) {
1107        self.add(ElabNote::SorryUsed {
1108            declaration: decl.into(),
1109        });
1110    }
1111
1112    /// Count notes of all types.
1113    #[allow(dead_code)]
1114    pub fn len(&self) -> usize {
1115        self.notes.len()
1116    }
1117
1118    /// Check if empty.
1119    #[allow(dead_code)]
1120    pub fn is_empty(&self) -> bool {
1121        self.notes.is_empty()
1122    }
1123
1124    /// Check if there are any warning-like notes.
1125    #[allow(dead_code)]
1126    pub fn has_warnings(&self) -> bool {
1127        self.notes.iter().any(|n| n.is_warning_like())
1128    }
1129
1130    /// Collect all warning-like notes.
1131    #[allow(dead_code)]
1132    pub fn warnings(&self) -> Vec<&ElabNote> {
1133        self.notes.iter().filter(|n| n.is_warning_like()).collect()
1134    }
1135
1136    /// Iterate over all notes.
1137    #[allow(dead_code)]
1138    pub fn iter(&self) -> impl Iterator<Item = &ElabNote> {
1139        self.notes.iter()
1140    }
1141
1142    /// Merge another note set into this one.
1143    #[allow(dead_code)]
1144    pub fn merge(&mut self, other: ElabNoteSet) {
1145        self.notes.extend(other.notes);
1146    }
1147
1148    /// Clear all notes.
1149    #[allow(dead_code)]
1150    pub fn clear(&mut self) {
1151        self.notes.clear();
1152    }
1153}
1154
1155// ============================================================================
1156// Well-known tactic names
1157// ============================================================================
1158
1159/// Names of all well-known tactics supported by the elaborator.
1160#[allow(dead_code)]
1161pub mod tactic_names {
1162    /// Introduce a binder into the context.
1163    pub const INTRO: &str = "intro";
1164    /// Introduce multiple binders at once.
1165    pub const INTROS: &str = "intros";
1166    /// Apply a lemma to the goal.
1167    pub const APPLY: &str = "apply";
1168    /// Provide an exact proof term.
1169    pub const EXACT: &str = "exact";
1170    /// Close goal by reflexivity.
1171    pub const REFL: &str = "refl";
1172    /// Assumption — close by hypothesis.
1173    pub const ASSUMPTION: &str = "assumption";
1174    /// Trivially close a trivial goal.
1175    pub const TRIVIAL: &str = "trivial";
1176    /// Placeholder proof.
1177    pub const SORRY: &str = "sorry";
1178    /// Rewrite goal using equality.
1179    pub const RW: &str = "rw";
1180    /// Simplify using simp lemmas.
1181    pub const SIMP: &str = "simp";
1182    /// Simp using all hypotheses.
1183    pub const SIMP_ALL: &str = "simp_all";
1184    /// Case split.
1185    pub const CASES: &str = "cases";
1186    /// Induction.
1187    pub const INDUCTION: &str = "induction";
1188    /// Apply first constructor.
1189    pub const CONSTRUCTOR: &str = "constructor";
1190    /// Apply left constructor of Or.
1191    pub const LEFT: &str = "left";
1192    /// Apply right constructor of Or.
1193    pub const RIGHT: &str = "right";
1194    /// Provide existential witness.
1195    pub const EXISTSI: &str = "existsi";
1196    /// Use witness (alias for existsi).
1197    pub const USE: &str = "use";
1198    /// Push negation inward.
1199    pub const PUSH_NEG: &str = "push_neg";
1200    /// By contradiction.
1201    pub const BY_CONTRA: &str = "by_contra";
1202    /// Contrapositive.
1203    pub const CONTRAPOSE: &str = "contrapose";
1204    /// Split an iff/and goal.
1205    pub const SPLIT: &str = "split";
1206    /// Exfalso: change goal to False.
1207    pub const EXFALSO: &str = "exfalso";
1208    /// Linear arithmetic.
1209    pub const LINARITH: &str = "linarith";
1210    /// Ring simplification.
1211    pub const RING: &str = "ring";
1212    /// Norm_cast.
1213    pub const NORM_CAST: &str = "norm_cast";
1214    /// Clear a hypothesis.
1215    pub const CLEAR: &str = "clear";
1216    /// Have: introduce a new hypothesis with proof.
1217    pub const HAVE: &str = "have";
1218    /// Obtain: like cases but with pattern.
1219    pub const OBTAIN: &str = "obtain";
1220    /// Show: change the goal type.
1221    pub const SHOW: &str = "show";
1222    /// Revert: move hypotheses back to goal.
1223    pub const REVERT: &str = "revert";
1224    /// Specialize an applied hypothesis.
1225    pub const SPECIALIZE: &str = "specialize";
1226    /// Rename a hypothesis.
1227    pub const RENAME: &str = "rename";
1228}
1229
1230/// Check whether a string is a known tactic name.
1231#[allow(dead_code)]
1232pub fn is_known_tactic(name: &str) -> bool {
1233    matches!(
1234        name,
1235        "intro"
1236            | "intros"
1237            | "apply"
1238            | "exact"
1239            | "refl"
1240            | "assumption"
1241            | "trivial"
1242            | "sorry"
1243            | "rw"
1244            | "simp"
1245            | "simp_all"
1246            | "cases"
1247            | "induction"
1248            | "constructor"
1249            | "left"
1250            | "right"
1251            | "existsi"
1252            | "use"
1253            | "push_neg"
1254            | "by_contra"
1255            | "by_contradiction"
1256            | "contrapose"
1257            | "split"
1258            | "exfalso"
1259            | "linarith"
1260            | "ring"
1261            | "norm_cast"
1262            | "clear"
1263            | "have"
1264            | "obtain"
1265            | "show"
1266            | "revert"
1267            | "specialize"
1268            | "rename"
1269            | "repeat"
1270            | "first"
1271            | "try"
1272            | "all_goals"
1273            | "any_goals"
1274            | "field_simp"
1275            | "push_cast"
1276            | "exact_mod_cast"
1277    )
1278}
1279
1280/// Return the category of a tactic (proof-search, rewriting, etc.).
1281#[allow(dead_code)]
1282pub fn tactic_category(name: &str) -> &'static str {
1283    match name {
1284        "intro" | "intros" | "revert" | "clear" | "rename" | "obtain" | "have" | "show" => {
1285            "context"
1286        }
1287        "apply" | "exact" | "assumption" | "trivial" | "sorry" | "refl" => "proof-search",
1288        "rw" | "simp" | "simp_all" | "field_simp" | "ring" | "linarith" | "norm_cast"
1289        | "push_cast" | "exact_mod_cast" => "rewriting",
1290        "cases" | "induction" | "constructor" | "left" | "right" | "existsi" | "use" | "split"
1291        | "exfalso" => "structure",
1292        "push_neg" | "by_contra" | "by_contradiction" | "contrapose" => "logic",
1293        "repeat" | "first" | "try" | "all_goals" | "any_goals" => "combinator",
1294        "specialize" => "context",
1295        _ => "unknown",
1296    }
1297}
1298
1299// ============================================================================
1300// Reducibility hints
1301// ============================================================================
1302
1303/// Reducibility annotation for a definition.
1304#[allow(dead_code)]
1305#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
1306pub enum Reducibility {
1307    /// Always unfold (e.g., `abbrev`, inline lets).
1308    Reducible = 0,
1309    /// Unfold on semi-transparent passes.
1310    #[default]
1311    SemiReducible = 1,
1312    /// Never unfold unless explicitly requested.
1313    Irreducible = 2,
1314}
1315
1316impl Reducibility {
1317    /// Check if the definition is always unfolded.
1318    #[allow(dead_code)]
1319    pub fn is_reducible(&self) -> bool {
1320        *self == Reducibility::Reducible
1321    }
1322    /// Check if the definition is never unfolded.
1323    #[allow(dead_code)]
1324    pub fn is_irreducible(&self) -> bool {
1325        *self == Reducibility::Irreducible
1326    }
1327    /// The attribute name corresponding to this reducibility level.
1328    #[allow(dead_code)]
1329    pub fn attr_name(&self) -> &'static str {
1330        match self {
1331            Reducibility::Reducible => "reducible",
1332            Reducibility::SemiReducible => "semireducible",
1333            Reducibility::Irreducible => "irreducible",
1334        }
1335    }
1336}
1337
1338impl std::fmt::Display for Reducibility {
1339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1340        write!(f, "{}", self.attr_name())
1341    }
1342}
1343
1344#[cfg(test)]
1345mod elab_lib_extra_tests {
1346    use super::*;
1347
1348    #[test]
1349    fn test_elab_note_hint() {
1350        let n = ElabNote::Hint("use norm_num".to_string());
1351        assert_eq!(n.prefix(), "hint");
1352        assert!(!n.is_warning_like());
1353    }
1354
1355    #[test]
1356    fn test_elab_note_warning() {
1357        let n = ElabNote::Warning("unsupported construct".to_string());
1358        assert!(n.is_warning_like());
1359    }
1360
1361    #[test]
1362    fn test_elab_note_sorry() {
1363        let n = ElabNote::SorryUsed {
1364            declaration: "myTheorem".to_string(),
1365        };
1366        assert!(n.is_warning_like());
1367        assert_eq!(n.message(), "myTheorem");
1368    }
1369
1370    #[test]
1371    fn test_elab_note_display() {
1372        let n = ElabNote::Info("no issues".to_string());
1373        let s = format!("{}", n);
1374        assert!(s.contains("info"));
1375    }
1376
1377    #[test]
1378    fn test_elab_note_set_add_warning() {
1379        let mut ns = ElabNoteSet::new();
1380        ns.add_warning("potential issue");
1381        assert!(ns.has_warnings());
1382        assert_eq!(ns.len(), 1);
1383    }
1384
1385    #[test]
1386    fn test_elab_note_set_merge() {
1387        let mut a = ElabNoteSet::new();
1388        a.add_hint("hint 1");
1389        let mut b = ElabNoteSet::new();
1390        b.add_info("info 1");
1391        a.merge(b);
1392        assert_eq!(a.len(), 2);
1393    }
1394
1395    #[test]
1396    fn test_elab_note_set_clear() {
1397        let mut ns = ElabNoteSet::new();
1398        ns.add_sorry("myThm");
1399        ns.clear();
1400        assert!(ns.is_empty());
1401    }
1402
1403    #[test]
1404    fn test_is_known_tactic() {
1405        assert!(is_known_tactic("intro"));
1406        assert!(is_known_tactic("simp"));
1407        assert!(is_known_tactic("ring"));
1408        assert!(!is_known_tactic("unknownTac"));
1409    }
1410
1411    #[test]
1412    fn test_tactic_category() {
1413        assert_eq!(tactic_category("intro"), "context");
1414        assert_eq!(tactic_category("simp"), "rewriting");
1415        assert_eq!(tactic_category("cases"), "structure");
1416        assert_eq!(tactic_category("push_neg"), "logic");
1417        assert_eq!(tactic_category("repeat"), "combinator");
1418    }
1419
1420    #[test]
1421    fn test_reducibility_ordering() {
1422        assert!(Reducibility::Reducible < Reducibility::SemiReducible);
1423        assert!(Reducibility::SemiReducible < Reducibility::Irreducible);
1424    }
1425
1426    #[test]
1427    fn test_reducibility_attr_names() {
1428        assert_eq!(Reducibility::Reducible.attr_name(), "reducible");
1429        assert_eq!(Reducibility::Irreducible.attr_name(), "irreducible");
1430    }
1431
1432    #[test]
1433    fn test_reducibility_default() {
1434        assert_eq!(Reducibility::default(), Reducibility::SemiReducible);
1435    }
1436
1437    #[test]
1438    fn test_tactic_names_intro() {
1439        assert_eq!(tactic_names::INTRO, "intro");
1440        assert_eq!(tactic_names::SORRY, "sorry");
1441    }
1442
1443    #[test]
1444    fn test_elab_note_warnings_filter() {
1445        let mut ns = ElabNoteSet::new();
1446        ns.add_hint("h1");
1447        ns.add_warning("w1");
1448        ns.add_sorry("decl");
1449        let warns = ns.warnings();
1450        assert_eq!(warns.len(), 2);
1451    }
1452}
1453
1454// ============================================================================
1455// Elaboration pass infrastructure
1456// ============================================================================
1457
1458/// A named elaboration pass that transforms an expression.
1459#[allow(dead_code)]
1460pub trait ElabPass {
1461    /// Name of this pass.
1462    fn name(&self) -> &str;
1463
1464    /// Run the pass on an expression, returning the (possibly transformed) result.
1465    fn run(&self, expr: oxilean_kernel::Expr) -> Result<oxilean_kernel::Expr, String>;
1466
1467    /// Whether this pass is enabled by default.
1468    fn enabled_by_default(&self) -> bool {
1469        true
1470    }
1471}
1472
1473/// A pipeline of elaboration passes applied in sequence.
1474#[allow(dead_code)]
1475pub struct ElabPipeline {
1476    passes: Vec<Box<dyn ElabPass>>,
1477    enabled: Vec<bool>,
1478}
1479
1480impl ElabPipeline {
1481    /// Create an empty pipeline.
1482    #[allow(dead_code)]
1483    pub fn new() -> Self {
1484        Self {
1485            passes: Vec::new(),
1486            enabled: Vec::new(),
1487        }
1488    }
1489
1490    /// Add a pass to the pipeline.
1491    #[allow(dead_code)]
1492    pub fn add<P: ElabPass + 'static>(&mut self, pass: P) {
1493        let enabled = pass.enabled_by_default();
1494        self.passes.push(Box::new(pass));
1495        self.enabled.push(enabled);
1496    }
1497
1498    /// Enable or disable a pass by index.
1499    #[allow(dead_code)]
1500    pub fn set_enabled(&mut self, idx: usize, enabled: bool) {
1501        if let Some(e) = self.enabled.get_mut(idx) {
1502            *e = enabled;
1503        }
1504    }
1505
1506    /// Run all enabled passes in sequence.
1507    #[allow(dead_code)]
1508    pub fn run_all(&self, expr: oxilean_kernel::Expr) -> Result<oxilean_kernel::Expr, String> {
1509        let mut current = expr;
1510        for (pass, &enabled) in self.passes.iter().zip(self.enabled.iter()) {
1511            if enabled {
1512                current = pass.run(current)?;
1513            }
1514        }
1515        Ok(current)
1516    }
1517
1518    /// Return the number of passes.
1519    #[allow(dead_code)]
1520    pub fn len(&self) -> usize {
1521        self.passes.len()
1522    }
1523
1524    /// Whether the pipeline is empty.
1525    #[allow(dead_code)]
1526    pub fn is_empty(&self) -> bool {
1527        self.passes.is_empty()
1528    }
1529}
1530
1531impl Default for ElabPipeline {
1532    fn default() -> Self {
1533        Self::new()
1534    }
1535}
1536
1537// ============================================================================
1538// Elaboration configuration
1539// ============================================================================
1540
1541/// Configuration for the elaborator.
1542#[derive(Debug, Clone)]
1543#[allow(dead_code)]
1544pub struct ElabConfigExt {
1545    /// Maximum number of metavariables to create.
1546    pub max_metavars: usize,
1547    /// Maximum recursion depth for type inference.
1548    pub max_depth: u32,
1549    /// Whether to emit sorry warnings.
1550    pub warn_sorry: bool,
1551    /// Whether to check for unused hypotheses.
1552    pub check_unused_hyps: bool,
1553    /// Whether to allow sorry at all.
1554    pub allow_sorry: bool,
1555    /// Whether to run coercion resolution.
1556    pub resolve_coercions: bool,
1557    /// Whether bidirectional type checking is enabled.
1558    pub bidir_checking: bool,
1559    /// Universe checking mode.
1560    pub universe_checking: UniverseCheckMode,
1561}
1562
1563/// Universe checking mode.
1564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1565#[allow(dead_code)]
1566pub enum UniverseCheckMode {
1567    /// Fully check universe polymorphism.
1568    Full,
1569    /// Only check that sorts are well-formed.
1570    Partial,
1571    /// Skip universe checking (unsafe).
1572    Skip,
1573}
1574
1575impl Default for ElabConfigExt {
1576    fn default() -> Self {
1577        Self {
1578            max_metavars: 10_000,
1579            max_depth: 500,
1580            warn_sorry: true,
1581            check_unused_hyps: false,
1582            allow_sorry: true,
1583            resolve_coercions: true,
1584            bidir_checking: true,
1585            universe_checking: UniverseCheckMode::Partial,
1586        }
1587    }
1588}
1589
1590impl ElabConfigExt {
1591    /// Create a new configuration with defaults.
1592    #[allow(dead_code)]
1593    pub fn new() -> Self {
1594        Self::default()
1595    }
1596
1597    /// Create a strict configuration (no sorry, full universe checking).
1598    #[allow(dead_code)]
1599    pub fn strict() -> Self {
1600        Self {
1601            allow_sorry: false,
1602            warn_sorry: true,
1603            check_unused_hyps: true,
1604            universe_checking: UniverseCheckMode::Full,
1605            ..Self::default()
1606        }
1607    }
1608
1609    /// Create a permissive configuration for prototyping.
1610    #[allow(dead_code)]
1611    pub fn permissive() -> Self {
1612        Self {
1613            allow_sorry: true,
1614            warn_sorry: false,
1615            check_unused_hyps: false,
1616            universe_checking: UniverseCheckMode::Skip,
1617            ..Self::default()
1618        }
1619    }
1620
1621    /// Check if sorry is both allowed and warned about.
1622    #[allow(dead_code)]
1623    pub fn sorry_warned(&self) -> bool {
1624        self.allow_sorry && self.warn_sorry
1625    }
1626}
1627
1628// ============================================================================
1629// Elaboration metrics
1630// ============================================================================
1631
1632/// Metrics collected during elaboration.
1633#[derive(Debug, Clone, Default)]
1634#[allow(dead_code)]
1635pub struct ElabMetrics {
1636    /// Total number of declarations elaborated.
1637    pub declarations_elaborated: u64,
1638    /// Total number of tactics executed.
1639    pub tactics_executed: u64,
1640    /// Number of sorry usages.
1641    pub sorry_count: u64,
1642    /// Number of unification steps.
1643    pub unification_steps: u64,
1644    /// Number of metavariables created.
1645    pub metavars_created: u64,
1646    /// Number of metavariables solved.
1647    pub metavars_solved: u64,
1648    /// Total inference steps.
1649    pub inference_steps: u64,
1650    /// Elaboration failures.
1651    pub failures: u64,
1652}
1653
1654impl ElabMetrics {
1655    /// Create zeroed metrics.
1656    #[allow(dead_code)]
1657    pub fn new() -> Self {
1658        Self::default()
1659    }
1660
1661    /// Record one declaration.
1662    #[allow(dead_code)]
1663    pub fn record_decl(&mut self) {
1664        self.declarations_elaborated += 1;
1665    }
1666
1667    /// Record one tactic.
1668    #[allow(dead_code)]
1669    pub fn record_tactic(&mut self) {
1670        self.tactics_executed += 1;
1671    }
1672
1673    /// Record a sorry usage.
1674    #[allow(dead_code)]
1675    pub fn record_sorry(&mut self) {
1676        self.sorry_count += 1;
1677    }
1678
1679    /// Record a failure.
1680    #[allow(dead_code)]
1681    pub fn record_failure(&mut self) {
1682        self.failures += 1;
1683    }
1684
1685    /// Return the solve rate (metavars_solved / metavars_created).
1686    #[allow(dead_code)]
1687    pub fn solve_rate(&self) -> f64 {
1688        if self.metavars_created == 0 {
1689            1.0
1690        } else {
1691            self.metavars_solved as f64 / self.metavars_created as f64
1692        }
1693    }
1694
1695    /// Merge another metrics record into this one.
1696    #[allow(dead_code)]
1697    pub fn merge(&mut self, other: &ElabMetrics) {
1698        self.declarations_elaborated += other.declarations_elaborated;
1699        self.tactics_executed += other.tactics_executed;
1700        self.sorry_count += other.sorry_count;
1701        self.unification_steps += other.unification_steps;
1702        self.metavars_created += other.metavars_created;
1703        self.metavars_solved += other.metavars_solved;
1704        self.inference_steps += other.inference_steps;
1705        self.failures += other.failures;
1706    }
1707}
1708
1709// ============================================================================
1710// Declaration kind classification
1711// ============================================================================
1712
1713/// The kind of a top-level declaration.
1714#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1715#[allow(dead_code)]
1716pub enum DeclKind {
1717    /// A definition (`def`).
1718    Def,
1719    /// A theorem (`theorem`).
1720    Theorem,
1721    /// An axiom (`axiom`).
1722    Axiom,
1723    /// An inductive type declaration.
1724    Inductive,
1725    /// A structure declaration.
1726    Structure,
1727    /// A class declaration.
1728    Class,
1729    /// An instance declaration.
1730    Instance,
1731    /// A namespace declaration.
1732    Namespace,
1733    /// An abbreviation (`abbrev`).
1734    Abbrev,
1735    /// A noncomputable definition.
1736    Noncomputable,
1737    /// An opaque definition.
1738    Opaque,
1739}
1740
1741impl DeclKind {
1742    /// Return the keyword for this declaration kind.
1743    #[allow(dead_code)]
1744    pub fn keyword(&self) -> &'static str {
1745        match self {
1746            DeclKind::Def => "def",
1747            DeclKind::Theorem => "theorem",
1748            DeclKind::Axiom => "axiom",
1749            DeclKind::Inductive => "inductive",
1750            DeclKind::Structure => "structure",
1751            DeclKind::Class => "class",
1752            DeclKind::Instance => "instance",
1753            DeclKind::Namespace => "namespace",
1754            DeclKind::Abbrev => "abbrev",
1755            DeclKind::Noncomputable => "noncomputable",
1756            DeclKind::Opaque => "opaque",
1757        }
1758    }
1759
1760    /// Whether this declaration kind produces a term.
1761    #[allow(dead_code)]
1762    pub fn produces_term(&self) -> bool {
1763        matches!(
1764            self,
1765            DeclKind::Def
1766                | DeclKind::Theorem
1767                | DeclKind::Axiom
1768                | DeclKind::Abbrev
1769                | DeclKind::Noncomputable
1770                | DeclKind::Opaque
1771        )
1772    }
1773
1774    /// Whether this declaration kind requires a proof.
1775    #[allow(dead_code)]
1776    pub fn requires_proof(&self) -> bool {
1777        matches!(self, DeclKind::Theorem)
1778    }
1779
1780    /// Whether this declaration is computable.
1781    #[allow(dead_code)]
1782    pub fn is_computable(&self) -> bool {
1783        !matches!(self, DeclKind::Noncomputable | DeclKind::Axiom)
1784    }
1785}
1786
1787impl std::fmt::Display for DeclKind {
1788    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1789        write!(f, "{}", self.keyword())
1790    }
1791}
1792
1793// ============================================================================
1794// Tactic proof state snapshot
1795// ============================================================================
1796
1797/// A snapshot of a tactic proof state (for undo/redo).
1798#[derive(Debug, Clone)]
1799#[allow(dead_code)]
1800pub struct ProofStateSnapshot {
1801    /// Snapshot ID.
1802    pub id: u64,
1803    /// Description of the state.
1804    pub description: String,
1805    /// Number of remaining goals.
1806    pub goal_count: usize,
1807    /// Names of current hypotheses.
1808    pub hypothesis_names: Vec<oxilean_kernel::Name>,
1809}
1810
1811impl ProofStateSnapshot {
1812    /// Create a new snapshot.
1813    #[allow(dead_code)]
1814    pub fn new(
1815        id: u64,
1816        description: impl Into<String>,
1817        goal_count: usize,
1818        hypothesis_names: Vec<oxilean_kernel::Name>,
1819    ) -> Self {
1820        Self {
1821            id,
1822            description: description.into(),
1823            goal_count,
1824            hypothesis_names,
1825        }
1826    }
1827
1828    /// Whether the proof is complete (0 goals remaining).
1829    #[allow(dead_code)]
1830    pub fn is_complete(&self) -> bool {
1831        self.goal_count == 0
1832    }
1833}
1834
1835/// A history of proof state snapshots for undo support.
1836#[derive(Debug, Default)]
1837#[allow(dead_code)]
1838pub struct ProofHistory {
1839    snapshots: Vec<ProofStateSnapshot>,
1840    current: usize,
1841}
1842
1843impl ProofHistory {
1844    /// Create an empty history.
1845    #[allow(dead_code)]
1846    pub fn new() -> Self {
1847        Self::default()
1848    }
1849
1850    /// Push a new snapshot.
1851    #[allow(dead_code)]
1852    pub fn push(&mut self, snap: ProofStateSnapshot) {
1853        // Remove any forward history
1854        self.snapshots.truncate(self.current);
1855        self.snapshots.push(snap);
1856        self.current = self.snapshots.len();
1857    }
1858
1859    /// Undo to the previous snapshot.
1860    #[allow(dead_code)]
1861    pub fn undo(&mut self) -> Option<&ProofStateSnapshot> {
1862        if self.current > 1 {
1863            self.current -= 1;
1864            self.snapshots.get(self.current - 1)
1865        } else {
1866            None
1867        }
1868    }
1869
1870    /// Redo to the next snapshot.
1871    #[allow(dead_code)]
1872    pub fn redo(&mut self) -> Option<&ProofStateSnapshot> {
1873        if self.current < self.snapshots.len() {
1874            self.current += 1;
1875            self.snapshots.get(self.current - 1)
1876        } else {
1877            None
1878        }
1879    }
1880
1881    /// Return the current snapshot.
1882    #[allow(dead_code)]
1883    pub fn current(&self) -> Option<&ProofStateSnapshot> {
1884        self.snapshots.get(self.current.saturating_sub(1))
1885    }
1886
1887    /// Return the total number of snapshots.
1888    #[allow(dead_code)]
1889    pub fn len(&self) -> usize {
1890        self.snapshots.len()
1891    }
1892
1893    /// Whether the history is empty.
1894    #[allow(dead_code)]
1895    pub fn is_empty(&self) -> bool {
1896        self.snapshots.is_empty()
1897    }
1898}
1899
1900// ============================================================================
1901// Coercion infrastructure
1902// ============================================================================
1903
1904/// A coercion rule: how to convert from type A to type B.
1905#[derive(Debug, Clone)]
1906#[allow(dead_code)]
1907pub struct CoercionExt {
1908    /// Source type name.
1909    pub from_type: oxilean_kernel::Name,
1910    /// Target type name.
1911    pub to_type: oxilean_kernel::Name,
1912    /// The coercion function (constant name).
1913    pub coercion_fn: oxilean_kernel::Name,
1914    /// Priority (higher = preferred).
1915    pub priority: u32,
1916}
1917
1918impl CoercionExt {
1919    /// Create a new coercion.
1920    #[allow(dead_code)]
1921    pub fn new(
1922        from_type: oxilean_kernel::Name,
1923        to_type: oxilean_kernel::Name,
1924        coercion_fn: oxilean_kernel::Name,
1925    ) -> Self {
1926        Self {
1927            from_type,
1928            to_type,
1929            coercion_fn,
1930            priority: 0,
1931        }
1932    }
1933
1934    /// Set priority.
1935    #[allow(dead_code)]
1936    pub fn with_priority(mut self, priority: u32) -> Self {
1937        self.priority = priority;
1938        self
1939    }
1940
1941    /// Apply this coercion to an expression.
1942    #[allow(dead_code)]
1943    pub fn apply(&self, expr: oxilean_kernel::Expr) -> oxilean_kernel::Expr {
1944        use oxilean_kernel::Expr;
1945        Expr::App(
1946            Box::new(Expr::Const(self.coercion_fn.clone(), vec![])),
1947            Box::new(expr),
1948        )
1949    }
1950}
1951
1952/// A registry of coercions.
1953#[derive(Debug, Default)]
1954#[allow(dead_code)]
1955pub struct CoercionRegistryExt {
1956    coercions: Vec<CoercionExt>,
1957}
1958
1959impl CoercionRegistryExt {
1960    /// Create an empty registry.
1961    #[allow(dead_code)]
1962    pub fn new() -> Self {
1963        Self::default()
1964    }
1965
1966    /// Register a coercion.
1967    #[allow(dead_code)]
1968    pub fn register(&mut self, coercion: CoercionExt) {
1969        self.coercions.push(coercion);
1970    }
1971
1972    /// Find a coercion from one type to another.
1973    #[allow(dead_code)]
1974    pub fn find(
1975        &self,
1976        from: &oxilean_kernel::Name,
1977        to: &oxilean_kernel::Name,
1978    ) -> Option<&CoercionExt> {
1979        self.coercions
1980            .iter()
1981            .filter(|c| &c.from_type == from && &c.to_type == to)
1982            .max_by_key(|c| c.priority)
1983    }
1984
1985    /// Return all coercions from a given type.
1986    #[allow(dead_code)]
1987    pub fn coercions_from(&self, from: &oxilean_kernel::Name) -> Vec<&CoercionExt> {
1988        self.coercions
1989            .iter()
1990            .filter(|c| &c.from_type == from)
1991            .collect()
1992    }
1993
1994    /// Return the number of registered coercions.
1995    #[allow(dead_code)]
1996    pub fn len(&self) -> usize {
1997        self.coercions.len()
1998    }
1999
2000    /// Whether there are no coercions.
2001    #[allow(dead_code)]
2002    pub fn is_empty(&self) -> bool {
2003        self.coercions.is_empty()
2004    }
2005
2006    /// Remove all coercions from type `from`.
2007    #[allow(dead_code)]
2008    pub fn remove_from(&mut self, from: &oxilean_kernel::Name) {
2009        self.coercions.retain(|c| &c.from_type != from);
2010    }
2011}
2012
2013// ============================================================================
2014// Type class instance resolution
2015// ============================================================================
2016
2017/// A type class instance record.
2018#[derive(Debug, Clone)]
2019#[allow(dead_code)]
2020pub struct ClassInstance {
2021    /// The class name.
2022    pub class: oxilean_kernel::Name,
2023    /// The instance name.
2024    pub instance: oxilean_kernel::Name,
2025    /// Type parameters that this instance applies to.
2026    pub type_params: Vec<oxilean_kernel::Expr>,
2027    /// Priority for instance selection.
2028    pub priority: u32,
2029    /// Whether this is a default instance.
2030    pub is_default: bool,
2031}
2032
2033impl ClassInstance {
2034    /// Create a new class instance.
2035    #[allow(dead_code)]
2036    pub fn new(class: oxilean_kernel::Name, instance: oxilean_kernel::Name) -> Self {
2037        Self {
2038            class,
2039            instance,
2040            type_params: Vec::new(),
2041            priority: 100,
2042            is_default: false,
2043        }
2044    }
2045
2046    /// Set as a default instance.
2047    #[allow(dead_code)]
2048    pub fn as_default(mut self) -> Self {
2049        self.is_default = true;
2050        self
2051    }
2052
2053    /// Set priority.
2054    #[allow(dead_code)]
2055    pub fn with_priority(mut self, priority: u32) -> Self {
2056        self.priority = priority;
2057        self
2058    }
2059
2060    /// Add a type parameter.
2061    #[allow(dead_code)]
2062    pub fn with_type_param(mut self, param: oxilean_kernel::Expr) -> Self {
2063        self.type_params.push(param);
2064        self
2065    }
2066}
2067
2068/// A registry for type class instances.
2069#[derive(Debug, Default)]
2070#[allow(dead_code)]
2071pub struct InstanceRegistry {
2072    instances: Vec<ClassInstance>,
2073}
2074
2075impl InstanceRegistry {
2076    /// Create a new empty registry.
2077    #[allow(dead_code)]
2078    pub fn new() -> Self {
2079        Self::default()
2080    }
2081
2082    /// Register an instance.
2083    #[allow(dead_code)]
2084    pub fn register(&mut self, instance: ClassInstance) {
2085        self.instances.push(instance);
2086    }
2087
2088    /// Find instances of a given class.
2089    #[allow(dead_code)]
2090    pub fn instances_of(&self, class: &oxilean_kernel::Name) -> Vec<&ClassInstance> {
2091        let mut results: Vec<&ClassInstance> = self
2092            .instances
2093            .iter()
2094            .filter(|i| &i.class == class)
2095            .collect();
2096        results.sort_by(|a, b| b.priority.cmp(&a.priority));
2097        results
2098    }
2099
2100    /// Find the default instance of a given class.
2101    #[allow(dead_code)]
2102    pub fn default_instance(&self, class: &oxilean_kernel::Name) -> Option<&ClassInstance> {
2103        self.instances_of(class).into_iter().find(|i| i.is_default)
2104    }
2105
2106    /// Return the total number of instances.
2107    #[allow(dead_code)]
2108    pub fn len(&self) -> usize {
2109        self.instances.len()
2110    }
2111
2112    /// Whether there are no instances.
2113    #[allow(dead_code)]
2114    pub fn is_empty(&self) -> bool {
2115        self.instances.is_empty()
2116    }
2117
2118    /// Remove all instances of a given class.
2119    #[allow(dead_code)]
2120    pub fn remove_class(&mut self, class: &oxilean_kernel::Name) {
2121        self.instances.retain(|i| &i.class != class);
2122    }
2123}
2124
2125// ============================================================================
2126// Attribute registry
2127// ============================================================================
2128
2129/// An attribute that can be attached to declarations.
2130#[derive(Debug, Clone)]
2131#[allow(dead_code)]
2132pub struct DeclAttribute {
2133    /// Attribute name.
2134    pub name: String,
2135    /// Optional argument.
2136    pub arg: Option<String>,
2137    /// The declaration this attribute applies to.
2138    pub decl: oxilean_kernel::Name,
2139}
2140
2141impl DeclAttribute {
2142    /// Create a new attribute.
2143    #[allow(dead_code)]
2144    pub fn new(name: impl Into<String>, decl: oxilean_kernel::Name) -> Self {
2145        Self {
2146            name: name.into(),
2147            arg: None,
2148            decl,
2149        }
2150    }
2151
2152    /// Attach an argument.
2153    #[allow(dead_code)]
2154    pub fn with_arg(mut self, arg: impl Into<String>) -> Self {
2155        self.arg = Some(arg.into());
2156        self
2157    }
2158}
2159
2160/// A registry for declaration attributes.
2161#[derive(Debug, Default)]
2162#[allow(dead_code)]
2163pub struct AttributeRegistry {
2164    attrs: Vec<DeclAttribute>,
2165}
2166
2167impl AttributeRegistry {
2168    /// Create a new empty attribute registry.
2169    #[allow(dead_code)]
2170    pub fn new() -> Self {
2171        Self::default()
2172    }
2173
2174    /// Register an attribute.
2175    #[allow(dead_code)]
2176    pub fn register(&mut self, attr: DeclAttribute) {
2177        self.attrs.push(attr);
2178    }
2179
2180    /// Find all attributes for a declaration.
2181    #[allow(dead_code)]
2182    pub fn attrs_of(&self, decl: &oxilean_kernel::Name) -> Vec<&DeclAttribute> {
2183        self.attrs.iter().filter(|a| &a.decl == decl).collect()
2184    }
2185
2186    /// Find all declarations with a given attribute name.
2187    #[allow(dead_code)]
2188    pub fn decls_with(&self, attr_name: &str) -> Vec<&oxilean_kernel::Name> {
2189        self.attrs
2190            .iter()
2191            .filter(|a| a.name == attr_name)
2192            .map(|a| &a.decl)
2193            .collect()
2194    }
2195
2196    /// Return the total number of attributes.
2197    #[allow(dead_code)]
2198    pub fn len(&self) -> usize {
2199        self.attrs.len()
2200    }
2201
2202    /// Whether there are no attributes.
2203    #[allow(dead_code)]
2204    pub fn is_empty(&self) -> bool {
2205        self.attrs.is_empty()
2206    }
2207}
2208
2209// ============================================================================
2210// Namespace management
2211// ============================================================================
2212
2213/// A namespace entry.
2214#[derive(Debug, Clone)]
2215#[allow(dead_code)]
2216pub struct NamespaceEntry {
2217    /// Fully-qualified name of this namespace.
2218    pub name: oxilean_kernel::Name,
2219    /// Whether the namespace is currently open.
2220    pub is_open: bool,
2221    /// Parent namespace (None = root).
2222    pub parent: Option<oxilean_kernel::Name>,
2223}
2224
2225impl NamespaceEntry {
2226    /// Create a new namespace entry.
2227    #[allow(dead_code)]
2228    pub fn new(name: oxilean_kernel::Name, parent: Option<oxilean_kernel::Name>) -> Self {
2229        Self {
2230            name,
2231            is_open: false,
2232            parent,
2233        }
2234    }
2235}
2236
2237/// A namespace manager tracking open namespaces.
2238#[derive(Debug, Default)]
2239#[allow(dead_code)]
2240pub struct NamespaceManager {
2241    namespaces: Vec<NamespaceEntry>,
2242    open_stack: Vec<oxilean_kernel::Name>,
2243}
2244
2245impl NamespaceManager {
2246    /// Create a new namespace manager.
2247    #[allow(dead_code)]
2248    pub fn new() -> Self {
2249        Self::default()
2250    }
2251
2252    /// Open a namespace.
2253    #[allow(dead_code)]
2254    pub fn open(&mut self, name: oxilean_kernel::Name) {
2255        // Register if not already known
2256        if !self.namespaces.iter().any(|ns| ns.name == name) {
2257            self.namespaces
2258                .push(NamespaceEntry::new(name.clone(), self.current_namespace()));
2259        }
2260        // Mark as open
2261        if let Some(ns) = self.namespaces.iter_mut().find(|ns| ns.name == name) {
2262            ns.is_open = true;
2263        }
2264        self.open_stack.push(name);
2265    }
2266
2267    /// Close the current namespace.
2268    #[allow(dead_code)]
2269    pub fn close(&mut self) -> Option<oxilean_kernel::Name> {
2270        if let Some(name) = self.open_stack.pop() {
2271            if let Some(ns) = self.namespaces.iter_mut().find(|ns| ns.name == name) {
2272                ns.is_open = !self.open_stack.contains(&name);
2273            }
2274            Some(name)
2275        } else {
2276            None
2277        }
2278    }
2279
2280    /// Return the current namespace (innermost open).
2281    #[allow(dead_code)]
2282    pub fn current_namespace(&self) -> Option<oxilean_kernel::Name> {
2283        self.open_stack.last().cloned()
2284    }
2285
2286    /// Return all open namespaces.
2287    #[allow(dead_code)]
2288    pub fn open_namespaces(&self) -> &[oxilean_kernel::Name] {
2289        &self.open_stack
2290    }
2291
2292    /// Qualify a name with the current namespace.
2293    #[allow(dead_code)]
2294    pub fn qualify(&self, name: &str) -> oxilean_kernel::Name {
2295        match self.current_namespace() {
2296            Some(ns) => oxilean_kernel::Name::str(format!("{}.{}", ns, name)),
2297            None => oxilean_kernel::Name::str(name),
2298        }
2299    }
2300
2301    /// Return the depth of namespace nesting.
2302    #[allow(dead_code)]
2303    pub fn depth(&self) -> usize {
2304        self.open_stack.len()
2305    }
2306}
2307
2308// ============================================================================
2309// Expression pretty-printing helpers
2310// ============================================================================
2311
2312/// Format a kernel expression as a human-readable string.
2313#[allow(dead_code)]
2314pub fn pretty_expr(expr: &oxilean_kernel::Expr) -> String {
2315    match expr {
2316        Expr::Sort(l) => format!("Sort({:?})", l),
2317        Expr::BVar(i) => format!("#{}", i),
2318        Expr::FVar(fv) => format!("@{}", fv.0),
2319        Expr::Const(name, _) => name.to_string(),
2320        Expr::App(f, a) => format!("({} {})", pretty_expr(f), pretty_expr(a)),
2321        Expr::Lam(_, name, _ty, body) => {
2322            format!("(fun {} => {})", name, pretty_expr(body))
2323        }
2324        Expr::Pi(_, name, ty, body) => {
2325            format!(
2326                "(({} : {}) -> {})",
2327                name,
2328                pretty_expr(ty),
2329                pretty_expr(body)
2330            )
2331        }
2332        Expr::Let(name, _ty, val, body) => {
2333            format!(
2334                "(let {} := {} in {})",
2335                name,
2336                pretty_expr(val),
2337                pretty_expr(body)
2338            )
2339        }
2340        Expr::Lit(lit) => {
2341            use oxilean_kernel::Literal;
2342            match lit {
2343                Literal::Nat(n) => format!("{}", n),
2344                Literal::Str(s) => format!("{:?}", s),
2345            }
2346        }
2347        Expr::Proj(name, idx, inner) => {
2348            format!("{}.{} ({})", name, idx, pretty_expr(inner))
2349        }
2350    }
2351}
2352
2353/// Format a list of expressions as a comma-separated string.
2354#[allow(dead_code)]
2355pub fn pretty_expr_list(exprs: &[oxilean_kernel::Expr]) -> String {
2356    exprs.iter().map(pretty_expr).collect::<Vec<_>>().join(", ")
2357}
2358
2359// ============================================================================
2360// Well-foundedness checking helpers
2361// ============================================================================
2362
2363/// Check if a declaration name looks like a recursive definition.
2364///
2365/// This is a heuristic check — actual recursion analysis happens in the kernel.
2366#[allow(dead_code)]
2367pub fn might_be_recursive(name: &oxilean_kernel::Name, body: &oxilean_kernel::Expr) -> bool {
2368    fn contains_name(expr: &Expr, target: &oxilean_kernel::Name) -> bool {
2369        match expr {
2370            Expr::Const(n, _) => n == target,
2371            Expr::App(f, a) => contains_name(f, target) || contains_name(a, target),
2372            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
2373                contains_name(ty, target) || contains_name(body, target)
2374            }
2375            Expr::Let(_, ty, val, b) => {
2376                contains_name(ty, target) || contains_name(val, target) || contains_name(b, target)
2377            }
2378            Expr::Proj(_, _, inner) => contains_name(inner, target),
2379            _ => false,
2380        }
2381    }
2382    contains_name(body, name)
2383}
2384
2385// ============================================================================
2386// Tactic name constants module (extended)
2387// ============================================================================
2388
2389/// Extended tactic name constants.
2390#[allow(dead_code)]
2391pub mod tactic_names_ext {
2392    /// `norm_num` — numeric normalization.
2393    pub const NORM_NUM: &str = "norm_num";
2394    /// `omega` — linear arithmetic over integers.
2395    pub const OMEGA: &str = "omega";
2396    /// `decide` — decidable proposition checker.
2397    pub const DECIDE: &str = "decide";
2398    /// `native_decide` — faster decide using native code.
2399    pub const NATIVE_DECIDE: &str = "native_decide";
2400    /// `aesop` — automated proof search.
2401    pub const AESOP: &str = "aesop";
2402    /// `tauto` — propositional tautology prover.
2403    pub const TAUTO: &str = "tauto";
2404    /// `fin_cases` — case split on finite types.
2405    pub const FIN_CASES: &str = "fin_cases";
2406    /// `interval_cases` — case split on integer intervals.
2407    pub const INTERVAL_CASES: &str = "interval_cases";
2408    /// `gcongr` — generalized congruence.
2409    pub const GCONGR: &str = "gcongr";
2410    /// `positivity` — prove positivity of expressions.
2411    pub const POSITIVITY: &str = "positivity";
2412    /// `polyrith` — polynomial arithmetic.
2413    pub const POLYRITH: &str = "polyrith";
2414    /// `linear_combination` — linear combination proof.
2415    pub const LINEAR_COMBINATION: &str = "linear_combination";
2416    /// `ext` — extensionality.
2417    pub const EXT: &str = "ext";
2418    /// `funext` — function extensionality.
2419    pub const FUNEXT: &str = "funext";
2420    /// `congr` — congruence.
2421    pub const CONGR: &str = "congr";
2422    /// `unfold` — unfold a definition.
2423    pub const UNFOLD: &str = "unfold";
2424    /// `change` — change goal to definitionally equal form.
2425    pub const CHANGE: &str = "change";
2426    /// `subst` — substitute a hypothesis.
2427    pub const SUBST: &str = "subst";
2428    /// `symm` — symmetry of equality.
2429    pub const SYMM: &str = "symm";
2430    /// `trans` — transitivity.
2431    pub const TRANS: &str = "trans";
2432    /// `calc` — calculation proof.
2433    pub const CALC: &str = "calc";
2434    /// `rcases` — recursive case split.
2435    pub const RCASES: &str = "rcases";
2436    /// `rintro` — recursive intro.
2437    pub const RINTRO: &str = "rintro";
2438    /// `refine` — partial proof.
2439    pub const REFINE: &str = "refine";
2440    /// `ac_rfl` — AC-refl.
2441    pub const AC_RFL: &str = "ac_rfl";
2442}
2443
2444/// Check if a tactic name is a Mathlib-style extended tactic.
2445#[allow(dead_code)]
2446pub fn is_mathlib_tactic(name: &str) -> bool {
2447    matches!(
2448        name,
2449        "norm_num"
2450            | "omega"
2451            | "decide"
2452            | "native_decide"
2453            | "aesop"
2454            | "tauto"
2455            | "fin_cases"
2456            | "interval_cases"
2457            | "gcongr"
2458            | "positivity"
2459            | "polyrith"
2460            | "linear_combination"
2461            | "ext"
2462            | "funext"
2463            | "congr"
2464            | "unfold"
2465            | "change"
2466            | "subst"
2467            | "symm"
2468            | "trans"
2469            | "calc"
2470            | "rcases"
2471            | "rintro"
2472            | "refine"
2473            | "ac_rfl"
2474    )
2475}
2476
2477// ============================================================================
2478// Scoped environment snapshot
2479// ============================================================================
2480
2481/// A snapshot of the elaboration environment at a point in time.
2482#[derive(Debug, Clone)]
2483#[allow(dead_code)]
2484pub struct EnvSnapshot {
2485    /// Snapshot ID.
2486    pub id: u64,
2487    /// Number of declarations in the environment.
2488    pub decl_count: usize,
2489    /// Description.
2490    pub description: String,
2491}
2492
2493impl EnvSnapshot {
2494    /// Create a new environment snapshot.
2495    #[allow(dead_code)]
2496    pub fn new(id: u64, decl_count: usize, description: impl Into<String>) -> Self {
2497        Self {
2498            id,
2499            decl_count,
2500            description: description.into(),
2501        }
2502    }
2503}
2504
2505/// A manager for environment snapshots (for module-level rollback).
2506#[derive(Debug, Default)]
2507#[allow(dead_code)]
2508pub struct EnvSnapshotManager {
2509    snapshots: Vec<EnvSnapshot>,
2510    next_id: u64,
2511}
2512
2513impl EnvSnapshotManager {
2514    /// Create a new snapshot manager.
2515    #[allow(dead_code)]
2516    pub fn new() -> Self {
2517        Self::default()
2518    }
2519
2520    /// Take a new snapshot.
2521    #[allow(dead_code)]
2522    pub fn take(&mut self, decl_count: usize, description: impl Into<String>) -> u64 {
2523        let id = self.next_id;
2524        self.next_id += 1;
2525        self.snapshots
2526            .push(EnvSnapshot::new(id, decl_count, description));
2527        id
2528    }
2529
2530    /// Find a snapshot by ID.
2531    #[allow(dead_code)]
2532    pub fn get(&self, id: u64) -> Option<&EnvSnapshot> {
2533        self.snapshots.iter().find(|s| s.id == id)
2534    }
2535
2536    /// Return all snapshots.
2537    #[allow(dead_code)]
2538    pub fn all(&self) -> &[EnvSnapshot] {
2539        &self.snapshots
2540    }
2541
2542    /// Return the most recent snapshot.
2543    #[allow(dead_code)]
2544    pub fn latest(&self) -> Option<&EnvSnapshot> {
2545        self.snapshots.last()
2546    }
2547
2548    /// Return the number of snapshots.
2549    #[allow(dead_code)]
2550    pub fn len(&self) -> usize {
2551        self.snapshots.len()
2552    }
2553
2554    /// Whether there are no snapshots.
2555    #[allow(dead_code)]
2556    pub fn is_empty(&self) -> bool {
2557        self.snapshots.is_empty()
2558    }
2559}
2560
2561// ============================================================================
2562// Additional tests
2563// ============================================================================
2564
2565#[cfg(test)]
2566mod lib_extended_tests {
2567    use super::*;
2568    use oxilean_kernel::Name;
2569
2570    #[test]
2571    fn test_elab_config_defaults() {
2572        let cfg = ElabConfig::default();
2573        assert!(!cfg.allow_sorry);
2574        assert!(cfg.kernel_check);
2575        assert!(cfg.proof_irrelevance);
2576        assert!(cfg.auto_implicit);
2577    }
2578
2579    #[test]
2580    fn test_elab_config_strict() {
2581        let cfg = ElabConfig::strict();
2582        assert!(!cfg.allow_sorry);
2583        assert!(cfg.strict_instances);
2584        assert!(cfg.kernel_check);
2585    }
2586
2587    #[test]
2588    fn test_elab_config_interactive() {
2589        let cfg = ElabConfig::interactive();
2590        assert!(cfg.allow_sorry);
2591        assert!(!cfg.strict_instances);
2592    }
2593
2594    #[test]
2595    fn test_elab_config_batch() {
2596        let cfg = ElabConfig::batch();
2597        assert!(!cfg.allow_sorry);
2598        assert!(cfg.strict_instances);
2599        assert!(!cfg.trace_elaboration);
2600    }
2601
2602    #[test]
2603    fn test_elab_metrics_solve_rate() {
2604        let mut m = ElabMetrics::new();
2605        m.metavars_created = 10;
2606        m.metavars_solved = 8;
2607        let rate = m.solve_rate();
2608        assert!((rate - 0.8).abs() < 1e-10);
2609    }
2610
2611    #[test]
2612    fn test_elab_metrics_solve_rate_zero() {
2613        let m = ElabMetrics::new();
2614        assert_eq!(m.solve_rate(), 1.0);
2615    }
2616
2617    #[test]
2618    fn test_elab_metrics_merge() {
2619        let mut a = ElabMetrics::new();
2620        a.declarations_elaborated = 5;
2621        let mut b = ElabMetrics::new();
2622        b.declarations_elaborated = 3;
2623        a.merge(&b);
2624        assert_eq!(a.declarations_elaborated, 8);
2625    }
2626
2627    #[test]
2628    fn test_decl_kind_keyword() {
2629        assert_eq!(DeclKind::Def.keyword(), "def");
2630        assert_eq!(DeclKind::Theorem.keyword(), "theorem");
2631        assert_eq!(DeclKind::Axiom.keyword(), "axiom");
2632    }
2633
2634    #[test]
2635    fn test_decl_kind_produces_term() {
2636        assert!(DeclKind::Def.produces_term());
2637        assert!(DeclKind::Theorem.produces_term());
2638        assert!(!DeclKind::Inductive.produces_term());
2639        assert!(!DeclKind::Namespace.produces_term());
2640    }
2641
2642    #[test]
2643    fn test_decl_kind_requires_proof() {
2644        assert!(DeclKind::Theorem.requires_proof());
2645        assert!(!DeclKind::Def.requires_proof());
2646    }
2647
2648    #[test]
2649    fn test_decl_kind_is_computable() {
2650        assert!(DeclKind::Def.is_computable());
2651        assert!(!DeclKind::Noncomputable.is_computable());
2652        assert!(!DeclKind::Axiom.is_computable());
2653    }
2654
2655    #[test]
2656    fn test_proof_history_undo_redo() {
2657        let mut h = ProofHistory::new();
2658        assert!(h.is_empty());
2659        h.push(ProofStateSnapshot::new(0, "start", 2, vec![]));
2660        h.push(ProofStateSnapshot::new(1, "step1", 1, vec![]));
2661        h.push(ProofStateSnapshot::new(2, "step2", 0, vec![]));
2662        assert_eq!(h.len(), 3);
2663
2664        let prev = h.undo();
2665        assert!(prev.is_some());
2666        assert_eq!(prev.expect("test operation should succeed").id, 1);
2667
2668        let next = h.redo();
2669        assert!(next.is_some());
2670        assert_eq!(next.expect("test operation should succeed").id, 2);
2671    }
2672
2673    #[test]
2674    fn test_proof_history_current() {
2675        let mut h = ProofHistory::new();
2676        h.push(ProofStateSnapshot::new(0, "start", 1, vec![]));
2677        assert!(h.current().is_some());
2678        assert_eq!(h.current().expect("test operation should succeed").id, 0);
2679        assert!(!h
2680            .current()
2681            .expect("test operation should succeed")
2682            .is_complete());
2683    }
2684
2685    #[test]
2686    fn test_coercion_registry_find() {
2687        let mut reg = CoercionRegistryExt::new();
2688        let c = CoercionExt::new(Name::str("Nat"), Name::str("Int"), Name::str("Int.ofNat"));
2689        reg.register(c);
2690        assert!(reg.find(&Name::str("Nat"), &Name::str("Int")).is_some());
2691        assert!(reg.find(&Name::str("Int"), &Name::str("Nat")).is_none());
2692    }
2693
2694    #[test]
2695    fn test_coercion_apply() {
2696        let c = CoercionExt::new(Name::str("Nat"), Name::str("Int"), Name::str("Int.ofNat"));
2697        let nat_expr = Expr::Const(Name::str("zero"), vec![]);
2698        let coerced = c.apply(nat_expr);
2699        assert!(matches!(coerced, Expr::App(_, _)));
2700    }
2701
2702    #[test]
2703    fn test_instance_registry() {
2704        let mut reg = InstanceRegistry::new();
2705        let inst = ClassInstance::new(Name::str("Add"), Name::str("instAddNat")).as_default();
2706        reg.register(inst);
2707        assert_eq!(reg.instances_of(&Name::str("Add")).len(), 1);
2708        assert!(reg.default_instance(&Name::str("Add")).is_some());
2709    }
2710
2711    #[test]
2712    fn test_attribute_registry() {
2713        let mut reg = AttributeRegistry::new();
2714        let attr = DeclAttribute::new("simp", Name::str("myLemma")).with_arg("all");
2715        reg.register(attr);
2716        assert_eq!(reg.attrs_of(&Name::str("myLemma")).len(), 1);
2717        assert_eq!(reg.decls_with("simp").len(), 1);
2718    }
2719
2720    #[test]
2721    fn test_namespace_manager() {
2722        let mut nm = NamespaceManager::new();
2723        assert_eq!(nm.depth(), 0);
2724        nm.open(Name::str("Nat"));
2725        assert_eq!(nm.depth(), 1);
2726        let q = nm.qualify("succ");
2727        assert!(q.to_string().contains("succ"));
2728        nm.close();
2729        assert_eq!(nm.depth(), 0);
2730    }
2731
2732    #[test]
2733    fn test_pretty_expr() {
2734        let nat = Expr::Const(Name::str("Nat"), vec![]);
2735        let s = pretty_expr(&nat);
2736        assert_eq!(s, "Nat");
2737
2738        let bvar = Expr::BVar(2);
2739        let s2 = pretty_expr(&bvar);
2740        assert!(s2.contains('2'));
2741    }
2742
2743    #[test]
2744    fn test_pretty_expr_list() {
2745        let exprs = vec![
2746            Expr::Const(Name::str("a"), vec![]),
2747            Expr::Const(Name::str("b"), vec![]),
2748        ];
2749        let s = pretty_expr_list(&exprs);
2750        assert!(s.contains("a"));
2751        assert!(s.contains("b"));
2752        assert!(s.contains(','));
2753    }
2754
2755    #[test]
2756    fn test_might_be_recursive_yes() {
2757        let name = Name::str("fib");
2758        let body = Expr::App(
2759            Box::new(Expr::Const(Name::str("fib"), vec![])),
2760            Box::new(Expr::BVar(0)),
2761        );
2762        assert!(might_be_recursive(&name, &body));
2763    }
2764
2765    #[test]
2766    fn test_might_be_recursive_no() {
2767        let name = Name::str("fib");
2768        let body = Expr::Const(Name::str("Nat.succ"), vec![]);
2769        assert!(!might_be_recursive(&name, &body));
2770    }
2771
2772    #[test]
2773    fn test_is_mathlib_tactic() {
2774        assert!(is_mathlib_tactic("omega"));
2775        assert!(is_mathlib_tactic("norm_num"));
2776        assert!(is_mathlib_tactic("aesop"));
2777        assert!(!is_mathlib_tactic("intro"));
2778        assert!(!is_mathlib_tactic("unknown"));
2779    }
2780
2781    #[test]
2782    fn test_tactic_names_ext_constants() {
2783        assert_eq!(tactic_names_ext::OMEGA, "omega");
2784        assert_eq!(tactic_names_ext::NORM_NUM, "norm_num");
2785        assert_eq!(tactic_names_ext::EXT, "ext");
2786    }
2787
2788    #[test]
2789    fn test_env_snapshot_manager() {
2790        let mut mgr = EnvSnapshotManager::new();
2791        assert!(mgr.is_empty());
2792        let id1 = mgr.take(10, "after module A");
2793        let _id2 = mgr.take(20, "after module B");
2794        assert_eq!(mgr.len(), 2);
2795        let snap = mgr.get(id1).expect("key should exist");
2796        assert_eq!(snap.decl_count, 10);
2797        let latest = mgr.latest().expect("test operation should succeed");
2798        assert_eq!(latest.decl_count, 20);
2799    }
2800
2801    #[test]
2802    fn test_universe_check_mode_equality() {
2803        assert_eq!(UniverseCheckMode::Full, UniverseCheckMode::Full);
2804        assert_ne!(UniverseCheckMode::Full, UniverseCheckMode::Skip);
2805    }
2806
2807    #[test]
2808    fn test_coercion_registry_remove_from() {
2809        let mut reg = CoercionRegistryExt::new();
2810        reg.register(CoercionExt::new(
2811            Name::str("Nat"),
2812            Name::str("Int"),
2813            Name::str("f"),
2814        ));
2815        reg.register(CoercionExt::new(
2816            Name::str("Nat"),
2817            Name::str("Real"),
2818            Name::str("g"),
2819        ));
2820        reg.register(CoercionExt::new(
2821            Name::str("Int"),
2822            Name::str("Real"),
2823            Name::str("h"),
2824        ));
2825        assert_eq!(reg.len(), 3);
2826        reg.remove_from(&Name::str("Nat"));
2827        assert_eq!(reg.len(), 1);
2828    }
2829
2830    #[test]
2831    fn test_instance_registry_remove_class() {
2832        let mut reg = InstanceRegistry::new();
2833        reg.register(ClassInstance::new(Name::str("Add"), Name::str("addNat")));
2834        reg.register(ClassInstance::new(Name::str("Add"), Name::str("addInt")));
2835        reg.register(ClassInstance::new(Name::str("Mul"), Name::str("mulNat")));
2836        assert_eq!(reg.len(), 3);
2837        reg.remove_class(&Name::str("Add"));
2838        assert_eq!(reg.len(), 1);
2839    }
2840
2841    #[test]
2842    fn test_decl_kind_display() {
2843        let s = format!("{}", DeclKind::Def);
2844        assert_eq!(s, "def");
2845    }
2846}