Skip to main content

logicaffeine_kernel/
context.rs

1//! Typing context for the kernel.
2//!
3//! A context maps variable names to their types.
4//! Used during type checking to track what variables are in scope.
5
6use crate::term::Term;
7use std::collections::HashMap;
8
9/// Typing context: maps variable names to their types.
10///
11/// The context is immutable-by-default: `extend` creates a new context
12/// with the additional binding, preserving the original.
13///
14/// Also stores global definitions:
15/// - Inductive types (e.g., Nat : Type 0)
16/// - Constructors (e.g., Zero : Nat, Succ : Nat -> Nat)
17/// - Declarations (e.g., hypotheses like h1 : P -> Q)
18#[derive(Debug, Clone, Default)]
19pub struct Context {
20    /// Local variable bindings (from λ and Π)
21    bindings: HashMap<String, Term>,
22
23    /// Inductive type definitions: name -> sort (e.g., "Nat" -> Type 0)
24    inductives: HashMap<String, Term>,
25
26    /// Constructor definitions: name -> (inductive_name, type)
27    constructors: HashMap<String, (String, Term)>,
28
29    /// Order of constructor registration per inductive.
30    /// HashMap doesn't preserve insertion order, so we track it explicitly.
31    constructor_order: HashMap<String, Vec<String>>,
32
33    /// Declaration bindings (axioms/hypotheses): name -> type
34    /// Used for certifying proofs where hypotheses are assumed.
35    declarations: HashMap<String, Term>,
36
37    /// Definition bodies: name -> (type, body)
38    /// Definitions are transparent - they unfold during normalization.
39    /// Distinguished from declarations (axioms) which have no body.
40    definitions: HashMap<String, (Term, Term)>,
41
42    /// Hint database: theorem names marked as hints for auto tactic.
43    /// When auto fails with decision procedures, it tries to apply these hints.
44    hints: Vec<String>,
45}
46
47impl Context {
48    /// Create an empty context.
49    pub fn new() -> Self {
50        Context {
51            bindings: HashMap::new(),
52            inductives: HashMap::new(),
53            constructors: HashMap::new(),
54            constructor_order: HashMap::new(),
55            declarations: HashMap::new(),
56            definitions: HashMap::new(),
57            hints: Vec::new(),
58        }
59    }
60
61    /// Add a local binding to this context (mutates in place).
62    pub fn add(&mut self, name: &str, ty: Term) {
63        self.bindings.insert(name.to_string(), ty);
64    }
65
66    /// Look up a local variable's type in the context.
67    pub fn get(&self, name: &str) -> Option<&Term> {
68        self.bindings.get(name)
69    }
70
71    /// Create a new context extended with an additional local binding.
72    ///
73    /// Does not mutate the original context.
74    pub fn extend(&self, name: &str, ty: Term) -> Context {
75        let mut new_ctx = self.clone();
76        new_ctx.add(name, ty);
77        new_ctx
78    }
79
80    /// Register an inductive type.
81    ///
82    /// The `sort` is the type of the inductive (e.g., Type 0 for Nat).
83    pub fn add_inductive(&mut self, name: &str, sort: Term) {
84        self.inductives.insert(name.to_string(), sort);
85    }
86
87    /// Register a constructor for an inductive type.
88    ///
89    /// The `ty` is the full type of the constructor
90    /// (e.g., `Nat` for Zero, `Nat -> Nat` for Succ).
91    ///
92    /// Constructors are tracked in registration order for match expressions.
93    pub fn add_constructor(&mut self, name: &str, inductive: &str, ty: Term) {
94        self.constructors
95            .insert(name.to_string(), (inductive.to_string(), ty));
96
97        // Track constructor order for this inductive
98        self.constructor_order
99            .entry(inductive.to_string())
100            .or_default()
101            .push(name.to_string());
102    }
103
104    /// Add a declaration (typed assumption/hypothesis).
105    ///
106    /// Used for proof certification where hypotheses are assumed.
107    /// Example: h1 : P -> Q
108    pub fn add_declaration(&mut self, name: &str, ty: Term) {
109        self.declarations.insert(name.to_string(), ty);
110    }
111
112    /// Register a definition: name : type := body
113    ///
114    /// Definitions are transparent and unfold during normalization (delta reduction).
115    /// This distinguishes them from declarations (axioms) which have no body.
116    pub fn add_definition(&mut self, name: String, ty: Term, body: Term) {
117        self.definitions.insert(name, (ty, body));
118    }
119
120    /// Look up a global definition (inductive, constructor, definition, or declaration).
121    ///
122    /// Returns the type of the global.
123    pub fn get_global(&self, name: &str) -> Option<&Term> {
124        // Check inductives first
125        if let Some(sort) = self.inductives.get(name) {
126            return Some(sort);
127        }
128        // Check constructors
129        if let Some((_, ty)) = self.constructors.get(name) {
130            return Some(ty);
131        }
132        // Check definitions (return type, not body)
133        if let Some((ty, _)) = self.definitions.get(name) {
134            return Some(ty);
135        }
136        // Check declarations (axioms)
137        self.declarations.get(name)
138    }
139
140    /// Check if a name is a definition (has a body that can be unfolded).
141    pub fn is_definition(&self, name: &str) -> bool {
142        self.definitions.contains_key(name)
143    }
144
145    /// Get the body of a definition, if it exists.
146    ///
147    /// Returns None for axioms, constructors, and inductives (only definitions have bodies).
148    pub fn get_definition_body(&self, name: &str) -> Option<&Term> {
149        self.definitions.get(name).map(|(_, body)| body)
150    }
151
152    /// Get the type of a definition, if it exists.
153    pub fn get_definition_type(&self, name: &str) -> Option<&Term> {
154        self.definitions.get(name).map(|(ty, _)| ty)
155    }
156
157    /// Check if a name is a constructor.
158    pub fn is_constructor(&self, name: &str) -> bool {
159        self.constructors.contains_key(name)
160    }
161
162    /// Get the inductive type a constructor belongs to.
163    pub fn constructor_inductive(&self, name: &str) -> Option<&str> {
164        self.constructors.get(name).map(|(ind, _)| ind.as_str())
165    }
166
167    /// Check if a name is an inductive type.
168    pub fn is_inductive(&self, name: &str) -> bool {
169        self.inductives.contains_key(name)
170    }
171
172    /// Get all constructors for an inductive type, in registration order.
173    ///
174    /// Returns a vector of (constructor_name, constructor_type) pairs.
175    pub fn get_constructors(&self, inductive: &str) -> Vec<(&str, &Term)> {
176        self.constructor_order
177            .get(inductive)
178            .map(|names| {
179                names
180                    .iter()
181                    .filter_map(|name| {
182                        self.constructors
183                            .get(name)
184                            .map(|(_, ty)| (name.as_str(), ty))
185                    })
186                    .collect()
187            })
188            .unwrap_or_default()
189    }
190
191    /// Iterate over all declarations (hypotheses).
192    ///
193    /// Used by the certifier to find hypothesis by type.
194    pub fn iter_declarations(&self) -> impl Iterator<Item = (&str, &Term)> {
195        self.declarations.iter().map(|(k, v)| (k.as_str(), v))
196    }
197
198    /// Iterate over all definitions.
199    ///
200    /// Used by the UI to display definitions.
201    pub fn iter_definitions(&self) -> impl Iterator<Item = (&str, &Term, &Term)> {
202        self.definitions.iter().map(|(k, (ty, body))| (k.as_str(), ty, body))
203    }
204
205    /// Iterate over all inductive types.
206    ///
207    /// Used by the UI to display inductive types.
208    pub fn iter_inductives(&self) -> impl Iterator<Item = (&str, &Term)> {
209        self.inductives.iter().map(|(k, v)| (k.as_str(), v))
210    }
211
212    /// Add a constructor with strict positivity checking.
213    ///
214    /// Returns an error if the inductive type appears negatively in the
215    /// constructor type. This prevents paradoxes like:
216    /// ```text
217    /// Inductive Bad := Cons : (Bad -> False) -> Bad
218    /// ```
219    pub fn add_constructor_checked(
220        &mut self,
221        name: &str,
222        inductive: &str,
223        ty: Term,
224    ) -> crate::error::KernelResult<()> {
225        // Check strict positivity first
226        crate::positivity::check_positivity(inductive, name, &ty)?;
227
228        // If it passes, add the constructor normally
229        self.add_constructor(name, inductive, ty);
230        Ok(())
231    }
232
233    /// Register a theorem as a hint for the auto tactic.
234    ///
235    /// Hints are theorems that auto will try to apply when decision
236    /// procedures fail. This allows auto to "learn" from proven theorems.
237    pub fn add_hint(&mut self, name: &str) {
238        if !self.hints.contains(&name.to_string()) {
239            self.hints.push(name.to_string());
240        }
241    }
242
243    /// Get all registered hints.
244    ///
245    /// Returns the names of theorems registered as hints.
246    pub fn get_hints(&self) -> &[String] {
247        &self.hints
248    }
249
250    /// Check if a theorem is registered as a hint.
251    pub fn is_hint(&self, name: &str) -> bool {
252        self.hints.contains(&name.to_string())
253    }
254}