Skip to main content

oxilean_kernel/hash_cons/
key.rs

1//! `ExprKey` — a hashable, owning representation of an `Expr` node.
2//!
3//! `Expr` is already `Hash + Eq` (derived), so `ExprKey` is simply a
4//! newtype wrapper that makes the intent explicit: it identifies a node's
5//! *structural content*, not its arena slot.
6//!
7//! Using a newtype keeps the cache `HashMap` key type distinct from `Expr`
8//! so callers cannot accidentally conflate "a key for lookup" with "a live
9//! expression value".
10
11use crate::{BinderInfo, Expr, FVarId, Level, Literal, Name};
12use std::hash::{Hash, Hasher};
13
14/// Structural key derived from the content of one `Expr` node.
15///
16/// Two `ExprKey` values are equal if and only if the corresponding `Expr`
17/// values are structurally identical (same variant, same fields). This is
18/// precisely the condition under which hash-consing must return the same
19/// `Idx<Expr>`.
20///
21/// Internally the key clones the `Expr` so that the `HashMap<ExprKey, …>`
22/// owns its keys independently of any arena.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct ExprKey(pub Expr);
25
26impl Hash for ExprKey {
27    fn hash<H: Hasher>(&self, state: &mut H) {
28        // Delegate to `Expr`'s derived `Hash` implementation which performs
29        // a full structural walk.
30        self.0.hash(state);
31    }
32}
33
34impl ExprKey {
35    /// Construct an `ExprKey` from an owned `Expr`.
36    #[inline]
37    pub fn new(expr: Expr) -> Self {
38        Self(expr)
39    }
40
41    /// Construct an `ExprKey` from a reference, cloning the expression.
42    #[inline]
43    pub fn from_ref(expr: &Expr) -> Self {
44        Self(expr.clone())
45    }
46
47    /// Return the inner `Expr`.
48    #[inline]
49    pub fn into_inner(self) -> Expr {
50        self.0
51    }
52
53    /// Borrow the inner `Expr`.
54    #[inline]
55    pub fn inner(&self) -> &Expr {
56        &self.0
57    }
58}
59
60// ---------------------------------------------------------------------------
61// Constructors that build ExprKey values directly from field values.
62// These mirror the `mk_*` constructors on `HashConsArena` so the cache layer
63// can construct a lookup key without first allocating an `Expr`.
64// ---------------------------------------------------------------------------
65
66impl ExprKey {
67    /// Key for `Expr::Sort(level)`.
68    pub fn sort(level: Level) -> Self {
69        Self::new(Expr::Sort(level))
70    }
71
72    /// Key for `Expr::BVar(idx)`.
73    pub fn bvar(idx: u32) -> Self {
74        Self::new(Expr::BVar(idx))
75    }
76
77    /// Key for `Expr::FVar(id)`.
78    pub fn fvar(id: FVarId) -> Self {
79        Self::new(Expr::FVar(id))
80    }
81
82    /// Key for `Expr::Const(name, levels)`.
83    pub fn const_(name: Name, levels: Vec<Level>) -> Self {
84        Self::new(Expr::Const(name, levels))
85    }
86
87    /// Key for `Expr::App(f, a)`.
88    pub fn app(f: Expr, a: Expr) -> Self {
89        Self::new(Expr::App(Box::new(f), Box::new(a)))
90    }
91
92    /// Key for `Expr::Lam(bi, name, dom, body)`.
93    pub fn lam(bi: BinderInfo, name: Name, dom: Expr, body: Expr) -> Self {
94        Self::new(Expr::Lam(bi, name, Box::new(dom), Box::new(body)))
95    }
96
97    /// Key for `Expr::Pi(bi, name, dom, cod)`.
98    pub fn pi(bi: BinderInfo, name: Name, dom: Expr, cod: Expr) -> Self {
99        Self::new(Expr::Pi(bi, name, Box::new(dom), Box::new(cod)))
100    }
101
102    /// Key for `Expr::Let(name, ty, val, body)`.
103    pub fn let_(name: Name, ty: Expr, val: Expr, body: Expr) -> Self {
104        Self::new(Expr::Let(name, Box::new(ty), Box::new(val), Box::new(body)))
105    }
106
107    /// Key for `Expr::Lit(lit)`.
108    pub fn lit(lit: Literal) -> Self {
109        Self::new(Expr::Lit(lit))
110    }
111
112    /// Key for `Expr::Proj(name, idx, struct_expr)`.
113    pub fn proj(name: Name, idx: u32, struct_expr: Expr) -> Self {
114        Self::new(Expr::Proj(name, idx, Box::new(struct_expr)))
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_expr_key_eq_same_bvar() {
124        let k1 = ExprKey::bvar(0);
125        let k2 = ExprKey::bvar(0);
126        assert_eq!(k1, k2);
127    }
128
129    #[test]
130    fn test_expr_key_ne_different_bvar() {
131        let k1 = ExprKey::bvar(0);
132        let k2 = ExprKey::bvar(1);
133        assert_ne!(k1, k2);
134    }
135
136    #[test]
137    fn test_expr_key_sort_zero_equals() {
138        let k1 = ExprKey::sort(Level::Zero);
139        let k2 = ExprKey::sort(Level::Zero);
140        assert_eq!(k1, k2);
141    }
142
143    #[test]
144    fn test_expr_key_fvar_equals() {
145        let id = FVarId::new(42);
146        let k1 = ExprKey::fvar(id);
147        let k2 = ExprKey::fvar(id);
148        assert_eq!(k1, k2);
149    }
150
151    #[test]
152    fn test_expr_key_hash_consistent() {
153        use std::collections::hash_map::DefaultHasher;
154        let k1 = ExprKey::bvar(5);
155        let k2 = ExprKey::bvar(5);
156        let mut h1 = DefaultHasher::new();
157        let mut h2 = DefaultHasher::new();
158        k1.hash(&mut h1);
159        k2.hash(&mut h2);
160        assert_eq!(h1.finish(), h2.finish());
161    }
162
163    #[test]
164    fn test_expr_key_into_inner() {
165        let k = ExprKey::bvar(3);
166        let expr = k.into_inner();
167        assert_eq!(expr, Expr::BVar(3));
168    }
169
170    #[test]
171    fn test_expr_key_from_ref() {
172        let expr = Expr::BVar(7);
173        let k1 = ExprKey::from_ref(&expr);
174        let k2 = ExprKey::bvar(7);
175        assert_eq!(k1, k2);
176    }
177
178    #[test]
179    fn test_expr_key_const_equals() {
180        let name = Name::str("Nat");
181        let k1 = ExprKey::const_(name.clone(), vec![]);
182        let k2 = ExprKey::const_(name, vec![]);
183        assert_eq!(k1, k2);
184    }
185
186    #[test]
187    fn test_expr_key_app_equals() {
188        let f = Expr::BVar(0);
189        let a = Expr::BVar(1);
190        let k1 = ExprKey::app(f.clone(), a.clone());
191        let k2 = ExprKey::app(f, a);
192        assert_eq!(k1, k2);
193    }
194
195    #[test]
196    fn test_expr_key_lam_equals() {
197        let name = Name::str("x");
198        let dom = Expr::Sort(Level::Zero);
199        let body = Expr::BVar(0);
200        let k1 = ExprKey::lam(BinderInfo::Default, name.clone(), dom.clone(), body.clone());
201        let k2 = ExprKey::lam(BinderInfo::Default, name, dom, body);
202        assert_eq!(k1, k2);
203    }
204
205    #[test]
206    fn test_expr_key_pi_equals() {
207        let name = Name::str("A");
208        let dom = Expr::Sort(Level::Zero);
209        let cod = Expr::BVar(0);
210        let k1 = ExprKey::pi(BinderInfo::Default, name.clone(), dom.clone(), cod.clone());
211        let k2 = ExprKey::pi(BinderInfo::Default, name, dom, cod);
212        assert_eq!(k1, k2);
213    }
214
215    #[test]
216    fn test_expr_key_let_equals() {
217        let name = Name::str("n");
218        let ty = Expr::Sort(Level::Zero);
219        let val = Expr::BVar(0);
220        let body = Expr::BVar(1);
221        let k1 = ExprKey::let_(name.clone(), ty.clone(), val.clone(), body.clone());
222        let k2 = ExprKey::let_(name, ty, val, body);
223        assert_eq!(k1, k2);
224    }
225
226    #[test]
227    fn test_expr_key_lit_nat_equals() {
228        let k1 = ExprKey::lit(Literal::Nat(42));
229        let k2 = ExprKey::lit(Literal::Nat(42));
230        assert_eq!(k1, k2);
231    }
232
233    #[test]
234    fn test_expr_key_proj_equals() {
235        let name = Name::str("fst");
236        let se = Expr::BVar(0);
237        let k1 = ExprKey::proj(name.clone(), 0, se.clone());
238        let k2 = ExprKey::proj(name, 0, se);
239        assert_eq!(k1, k2);
240    }
241
242    #[test]
243    fn test_expr_key_usable_as_hashmap_key() {
244        use std::collections::HashMap;
245        let mut map: HashMap<ExprKey, u32> = HashMap::new();
246        map.insert(ExprKey::bvar(0), 100);
247        map.insert(ExprKey::bvar(1), 200);
248        assert_eq!(map.get(&ExprKey::bvar(0)), Some(&100));
249        assert_eq!(map.get(&ExprKey::bvar(1)), Some(&200));
250        assert_eq!(map.get(&ExprKey::bvar(2)), None);
251    }
252}