1use crate::ast::{Declaration, TypeExpr};
4use crate::parser::{parse_search_pattern, SearchPattern};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SearchHit {
11 pub name: String,
12 pub full_name: String,
13 pub kind: String,
14 pub type_surface: String,
15 pub file: String,
16 pub line: usize,
17 pub score: i32,
18}
19
20pub fn matches_type(pattern: &TypeExpr, target: &TypeExpr) -> bool {
22 let mut env: HashMap<String, TypeExpr> = HashMap::new();
23 unify(pattern, target, &mut env)
24}
25
26fn unify(pat: &TypeExpr, target: &TypeExpr, env: &mut HashMap<String, TypeExpr>) -> bool {
27 match pat {
28 TypeExpr::Hole => true,
29 TypeExpr::NamedHole(name) => {
30 if let Some(bound) = env.get(name) {
31 alpha_eq(bound, target)
33 } else {
34 env.insert(name.clone(), target.clone());
35 true
36 }
37 }
38 TypeExpr::Ident(a) => match target {
39 TypeExpr::Ident(b) => idents_compatible(a, b),
40 _ => false,
42 },
43 TypeExpr::NatLit(a) => matches!(target, TypeExpr::NatLit(b) if a == b),
44 TypeExpr::Literal(a) => matches!(target, TypeExpr::Literal(b) if a == b),
45 TypeExpr::App(pf, pa) => match target {
46 TypeExpr::App(tf, ta) => unify(pf, tf, env) && unify(pa, ta, env),
47 _ => false,
48 },
49 TypeExpr::BinOp {
50 op: po,
51 left: pl,
52 right: pr,
53 } => match target {
54 TypeExpr::BinOp {
55 op: to,
56 left: tl,
57 right: tr,
58 } if ops_compatible(po, to) => unify(pl, tl, env) && unify(pr, tr, env),
59 _ => false,
60 },
61 TypeExpr::UnaryOp { op: po, arg: pa } => match target {
62 TypeExpr::UnaryOp { op: to, arg: ta } if ops_compatible(po, to) => {
63 unify(pa, ta, env)
64 }
65 _ => false,
66 },
67 TypeExpr::Postfix { arg: pa, op: po } => match target {
68 TypeExpr::Postfix { arg: ta, op: to } if ops_compatible(po, to) => {
69 unify(pa, ta, env)
70 }
71 _ => false,
72 },
73 TypeExpr::Arrow(pa, pb) => match target {
74 TypeExpr::Arrow(ta, tb) => unify(pa, ta, env) && unify(pb, tb, env),
75 TypeExpr::Pi { binder, body } => {
76 let domain_ok = match &binder.ty {
78 Some(ty) => unify(pa, ty, env),
79 None => true,
80 };
81 domain_ok && unify(pb, body, env)
82 }
83 _ => false,
84 },
85 TypeExpr::Forall { body: pb, .. } => {
86 match target {
88 TypeExpr::Forall { body: tb, .. } => unify(pb, tb, env) || unify(pb, target, env),
89 _ => unify(pb, target, env),
90 }
91 }
92 TypeExpr::Exists { body: pb, .. } => match target {
93 TypeExpr::Exists { body: tb, .. } => unify(pb, tb, env),
94 _ => unify(pb, target, env),
95 },
96 TypeExpr::Lambda { body: pb, .. } => match target {
97 TypeExpr::Lambda { body: tb, .. } => unify(pb, tb, env),
98 _ => false,
99 },
100 TypeExpr::Pi {
101 binder: _,
102 body: pb,
103 } => match target {
104 TypeExpr::Pi { body: tb, .. } => unify(pb, tb, env),
105 TypeExpr::Arrow(_, tb) => unify(pb, tb, env),
106 _ => unify(pb, target, env),
107 },
108 TypeExpr::Proj {
109 base: pb,
110 field: pf,
111 } => match target {
112 TypeExpr::Proj {
113 base: tb,
114 field: tf,
115 } if pf == tf => unify(pb, tb, env),
116 _ => false,
117 },
118 TypeExpr::Sort { name: pn, level: pl } => match target {
119 TypeExpr::Sort { name: tn, level: tl } if pn == tn => match (pl, tl) {
120 (None, _) => true,
121 (Some(a), Some(b)) => unify(a, b, env),
122 (Some(_), None) => false,
123 },
124 _ => false,
125 },
126 TypeExpr::Raw(a) => match target {
127 TypeExpr::Raw(b) => a == b,
128 other => other.surface().contains(a.as_str()) || a == &other.surface(),
129 },
130 }
131}
132
133fn alpha_eq(a: &TypeExpr, b: &TypeExpr) -> bool {
134 match (a, b) {
135 (TypeExpr::Hole, _) | (_, TypeExpr::Hole) => true,
136 (TypeExpr::NamedHole(x), TypeExpr::NamedHole(y)) => x == y,
137 (TypeExpr::Ident(x), TypeExpr::Ident(y)) => idents_compatible(x, y),
138 (TypeExpr::NatLit(x), TypeExpr::NatLit(y)) => x == y,
139 (TypeExpr::Literal(x), TypeExpr::Literal(y)) => x == y,
140 (TypeExpr::App(f1, a1), TypeExpr::App(f2, a2)) => alpha_eq(f1, f2) && alpha_eq(a1, a2),
141 (
142 TypeExpr::BinOp {
143 op: o1,
144 left: l1,
145 right: r1,
146 },
147 TypeExpr::BinOp {
148 op: o2,
149 left: l2,
150 right: r2,
151 },
152 ) => ops_compatible(o1, o2) && alpha_eq(l1, l2) && alpha_eq(r1, r2),
153 (TypeExpr::UnaryOp { op: o1, arg: a1 }, TypeExpr::UnaryOp { op: o2, arg: a2 }) => {
154 ops_compatible(o1, o2) && alpha_eq(a1, a2)
155 }
156 (TypeExpr::Postfix { arg: a1, op: o1 }, TypeExpr::Postfix { arg: a2, op: o2 }) => {
157 ops_compatible(o1, o2) && alpha_eq(a1, a2)
158 }
159 (TypeExpr::Arrow(a1, b1), TypeExpr::Arrow(a2, b2)) => alpha_eq(a1, a2) && alpha_eq(b1, b2),
160 (TypeExpr::Forall { body: b1, .. }, TypeExpr::Forall { body: b2, .. }) => alpha_eq(b1, b2),
161 (TypeExpr::Exists { body: b1, .. }, TypeExpr::Exists { body: b2, .. }) => alpha_eq(b1, b2),
162 (TypeExpr::Pi { body: b1, .. }, TypeExpr::Pi { body: b2, .. }) => alpha_eq(b1, b2),
163 (TypeExpr::Proj { base: b1, field: f1 }, TypeExpr::Proj { base: b2, field: f2 }) => {
164 f1 == f2 && alpha_eq(b1, b2)
165 }
166 (TypeExpr::Sort { name: n1, level: l1 }, TypeExpr::Sort { name: n2, level: l2 }) => {
167 n1 == n2
168 && match (l1, l2) {
169 (None, None) => true,
170 (Some(a), Some(b)) => alpha_eq(a, b),
171 _ => false,
172 }
173 }
174 (TypeExpr::Raw(x), TypeExpr::Raw(y)) => x == y,
175 _ => false,
176 }
177}
178
179fn idents_compatible(a: &str, b: &str) -> bool {
180 if a == b {
181 return true;
182 }
183 let a_last = a.rsplit('.').next().unwrap_or(a);
185 let b_last = b.rsplit('.').next().unwrap_or(b);
186 a_last == b_last
187}
188
189fn ops_compatible(a: &str, b: &str) -> bool {
190 a == b
191}
192
193pub fn matches_decl(pat: &SearchPattern, decl: &Declaration) -> bool {
195 let effective = decl.effective_type();
196 if pat.conclusion_only {
197 let conc = effective.conclusion();
200 if matches_type(&pat.expr, conc) {
201 return true;
202 }
203 if matches_type(pat.expr.conclusion(), conc) {
205 return true;
206 }
207 return match_any_conclusion(&pat.expr, &effective);
209 }
210
211 if matches_type(&pat.expr, &effective) {
213 return true;
214 }
215 if matches_type(&pat.expr, effective.conclusion()) {
216 return true;
217 }
218 if matches_type(pat.expr.conclusion(), effective.conclusion()) {
220 return true;
221 }
222 match_subterm(&pat.expr, &effective)
224}
225
226fn match_any_conclusion(pat: &TypeExpr, ty: &TypeExpr) -> bool {
227 if matches_type(pat, ty.conclusion()) {
228 return true;
229 }
230 match ty {
231 TypeExpr::Arrow(_, r) | TypeExpr::Pi { body: r, .. } | TypeExpr::Forall { body: r, .. } => {
232 match_any_conclusion(pat, r)
233 }
234 _ => false,
235 }
236}
237
238fn match_subterm(pat: &TypeExpr, ty: &TypeExpr) -> bool {
239 if matches_type(pat, ty) {
240 return true;
241 }
242 match ty {
243 TypeExpr::App(f, a) => match_subterm(pat, f) || match_subterm(pat, a),
244 TypeExpr::BinOp { left, right, .. } => {
245 match_subterm(pat, left) || match_subterm(pat, right)
246 }
247 TypeExpr::UnaryOp { arg, .. } | TypeExpr::Postfix { arg, .. } => match_subterm(pat, arg),
248 TypeExpr::Arrow(a, b) => match_subterm(pat, a) || match_subterm(pat, b),
249 TypeExpr::Forall { body, .. }
250 | TypeExpr::Exists { body, .. }
251 | TypeExpr::Lambda { body, .. }
252 | TypeExpr::Pi { body, .. } => match_subterm(pat, body),
253 TypeExpr::Proj { base, .. } => match_subterm(pat, base),
254 _ => false,
255 }
256}
257
258pub fn score_hit(pat: &SearchPattern, decl: &Declaration) -> i32 {
260 let mut score = 0;
261 let effective = decl.effective_type();
262 if matches_type(&pat.expr, effective.conclusion()) {
263 score += 100;
264 }
265 if pat.conclusion_only {
266 score += 10;
267 }
268 score += 50usize.saturating_sub(decl.full_name.len()) as i32;
270 score += match decl.kind {
272 crate::ast::DeclKind::Theorem => 3,
273 crate::ast::DeclKind::Lemma => 2,
274 crate::ast::DeclKind::Axiom => 1,
275 };
276 score
277}
278
279pub fn parse_pattern(input: &str) -> anyhow::Result<SearchPattern> {
280 parse_search_pattern(input).map_err(|e| anyhow::anyhow!(e.to_string()))
281}
282
283pub fn pattern_index_keys(pat: &SearchPattern) -> Vec<String> {
285 let expr = if pat.conclusion_only {
286 pat.expr.conclusion()
287 } else {
288 &pat.expr
289 };
290 let mut keys = Vec::new();
291 let head = expr.head_key();
292 if !head.starts_with("hole") {
293 keys.push(head);
294 }
295 for op in expr.operators() {
296 keys.push(format!("op:{op}"));
297 }
298 let ch = pat.expr.conclusion().head_key();
300 if !ch.starts_with("hole") {
301 keys.push(ch);
302 }
303 keys.sort();
304 keys.dedup();
305 keys
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::ast::DeclKind;
312 use crate::parser::parse_type;
313
314 fn decl_with_type(surface: &str) -> Declaration {
315 let ty = parse_type(surface).unwrap_or(TypeExpr::Raw(surface.into()));
316 Declaration {
317 kind: DeclKind::Theorem,
318 name: "t".into(),
319 full_name: "T.t".into(),
320 binders: vec![],
321 ty,
322 type_surface: surface.into(),
323 file: "t.lean".into(),
324 line: 1,
325 module: None,
326 namespace_path: vec![],
327 attributes: vec![],
328 }
329 }
330
331 #[test]
332 fn hole_match_add_eq_zero() {
333 let d = decl_with_type("n + m = 0");
334 let p = parse_pattern("_ + _ = 0").unwrap();
335 assert!(matches_decl(&p, &d));
336 }
337
338 #[test]
339 fn named_hole_same() {
340 let d = decl_with_type("x - x = 0");
341 let p = parse_pattern("?a - ?a = 0").unwrap();
342 assert!(matches_decl(&p, &d));
343 let d2 = decl_with_type("x - y = 0");
344 assert!(!matches_decl(&p, &d2));
345 }
346
347 #[test]
348 fn turnstile_conclusion() {
349 let d = decl_with_type("∀ (n : Nat), n + 0 = n");
350 let p = parse_pattern("|- _ + 0 = _").unwrap();
351 assert!(matches_decl(&p, &d));
352 }
353
354 #[test]
355 fn no_match_different_op() {
356 let d = decl_with_type("n * m = 0");
357 let p = parse_pattern("_ + _ = 0").unwrap();
358 assert!(!matches_decl(&p, &d));
359 }
360}