use crate::ast::{Declaration, TypeExpr};
use crate::parser::{parse_search_pattern, SearchPattern};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchHit {
pub name: String,
pub full_name: String,
pub kind: String,
pub type_surface: String,
pub file: String,
pub line: usize,
pub score: i32,
}
pub fn matches_type(pattern: &TypeExpr, target: &TypeExpr) -> bool {
let mut env: HashMap<String, TypeExpr> = HashMap::new();
unify(pattern, target, &mut env)
}
fn unify(pat: &TypeExpr, target: &TypeExpr, env: &mut HashMap<String, TypeExpr>) -> bool {
match pat {
TypeExpr::Hole => true,
TypeExpr::NamedHole(name) => {
if let Some(bound) = env.get(name) {
alpha_eq(bound, target)
} else {
env.insert(name.clone(), target.clone());
true
}
}
TypeExpr::Ident(a) => match target {
TypeExpr::Ident(b) => idents_compatible(a, b),
_ => false,
},
TypeExpr::NatLit(a) => matches!(target, TypeExpr::NatLit(b) if a == b),
TypeExpr::Literal(a) => matches!(target, TypeExpr::Literal(b) if a == b),
TypeExpr::App(pf, pa) => match target {
TypeExpr::App(tf, ta) => unify(pf, tf, env) && unify(pa, ta, env),
_ => false,
},
TypeExpr::BinOp {
op: po,
left: pl,
right: pr,
} => match target {
TypeExpr::BinOp {
op: to,
left: tl,
right: tr,
} if ops_compatible(po, to) => unify(pl, tl, env) && unify(pr, tr, env),
_ => false,
},
TypeExpr::UnaryOp { op: po, arg: pa } => match target {
TypeExpr::UnaryOp { op: to, arg: ta } if ops_compatible(po, to) => {
unify(pa, ta, env)
}
_ => false,
},
TypeExpr::Postfix { arg: pa, op: po } => match target {
TypeExpr::Postfix { arg: ta, op: to } if ops_compatible(po, to) => {
unify(pa, ta, env)
}
_ => false,
},
TypeExpr::Arrow(pa, pb) => match target {
TypeExpr::Arrow(ta, tb) => unify(pa, ta, env) && unify(pb, tb, env),
TypeExpr::Pi { binder, body } => {
let domain_ok = match &binder.ty {
Some(ty) => unify(pa, ty, env),
None => true,
};
domain_ok && unify(pb, body, env)
}
_ => false,
},
TypeExpr::Forall { body: pb, .. } => {
match target {
TypeExpr::Forall { body: tb, .. } => unify(pb, tb, env) || unify(pb, target, env),
_ => unify(pb, target, env),
}
}
TypeExpr::Exists { body: pb, .. } => match target {
TypeExpr::Exists { body: tb, .. } => unify(pb, tb, env),
_ => unify(pb, target, env),
},
TypeExpr::Lambda { body: pb, .. } => match target {
TypeExpr::Lambda { body: tb, .. } => unify(pb, tb, env),
_ => false,
},
TypeExpr::Pi {
binder: _,
body: pb,
} => match target {
TypeExpr::Pi { body: tb, .. } => unify(pb, tb, env),
TypeExpr::Arrow(_, tb) => unify(pb, tb, env),
_ => unify(pb, target, env),
},
TypeExpr::Proj {
base: pb,
field: pf,
} => match target {
TypeExpr::Proj {
base: tb,
field: tf,
} if pf == tf => unify(pb, tb, env),
_ => false,
},
TypeExpr::Sort { name: pn, level: pl } => match target {
TypeExpr::Sort { name: tn, level: tl } if pn == tn => match (pl, tl) {
(None, _) => true,
(Some(a), Some(b)) => unify(a, b, env),
(Some(_), None) => false,
},
_ => false,
},
TypeExpr::Raw(a) => match target {
TypeExpr::Raw(b) => a == b,
other => other.surface().contains(a.as_str()) || a == &other.surface(),
},
}
}
fn alpha_eq(a: &TypeExpr, b: &TypeExpr) -> bool {
match (a, b) {
(TypeExpr::Hole, _) | (_, TypeExpr::Hole) => true,
(TypeExpr::NamedHole(x), TypeExpr::NamedHole(y)) => x == y,
(TypeExpr::Ident(x), TypeExpr::Ident(y)) => idents_compatible(x, y),
(TypeExpr::NatLit(x), TypeExpr::NatLit(y)) => x == y,
(TypeExpr::Literal(x), TypeExpr::Literal(y)) => x == y,
(TypeExpr::App(f1, a1), TypeExpr::App(f2, a2)) => alpha_eq(f1, f2) && alpha_eq(a1, a2),
(
TypeExpr::BinOp {
op: o1,
left: l1,
right: r1,
},
TypeExpr::BinOp {
op: o2,
left: l2,
right: r2,
},
) => ops_compatible(o1, o2) && alpha_eq(l1, l2) && alpha_eq(r1, r2),
(TypeExpr::UnaryOp { op: o1, arg: a1 }, TypeExpr::UnaryOp { op: o2, arg: a2 }) => {
ops_compatible(o1, o2) && alpha_eq(a1, a2)
}
(TypeExpr::Postfix { arg: a1, op: o1 }, TypeExpr::Postfix { arg: a2, op: o2 }) => {
ops_compatible(o1, o2) && alpha_eq(a1, a2)
}
(TypeExpr::Arrow(a1, b1), TypeExpr::Arrow(a2, b2)) => alpha_eq(a1, a2) && alpha_eq(b1, b2),
(TypeExpr::Forall { body: b1, .. }, TypeExpr::Forall { body: b2, .. }) => alpha_eq(b1, b2),
(TypeExpr::Exists { body: b1, .. }, TypeExpr::Exists { body: b2, .. }) => alpha_eq(b1, b2),
(TypeExpr::Pi { body: b1, .. }, TypeExpr::Pi { body: b2, .. }) => alpha_eq(b1, b2),
(TypeExpr::Proj { base: b1, field: f1 }, TypeExpr::Proj { base: b2, field: f2 }) => {
f1 == f2 && alpha_eq(b1, b2)
}
(TypeExpr::Sort { name: n1, level: l1 }, TypeExpr::Sort { name: n2, level: l2 }) => {
n1 == n2
&& match (l1, l2) {
(None, None) => true,
(Some(a), Some(b)) => alpha_eq(a, b),
_ => false,
}
}
(TypeExpr::Raw(x), TypeExpr::Raw(y)) => x == y,
_ => false,
}
}
fn idents_compatible(a: &str, b: &str) -> bool {
if a == b {
return true;
}
let a_last = a.rsplit('.').next().unwrap_or(a);
let b_last = b.rsplit('.').next().unwrap_or(b);
a_last == b_last
}
fn ops_compatible(a: &str, b: &str) -> bool {
a == b
}
pub fn matches_decl(pat: &SearchPattern, decl: &Declaration) -> bool {
let effective = decl.effective_type();
if pat.conclusion_only {
let conc = effective.conclusion();
if matches_type(&pat.expr, conc) {
return true;
}
if matches_type(pat.expr.conclusion(), conc) {
return true;
}
return match_any_conclusion(&pat.expr, &effective);
}
if matches_type(&pat.expr, &effective) {
return true;
}
if matches_type(&pat.expr, effective.conclusion()) {
return true;
}
if matches_type(pat.expr.conclusion(), effective.conclusion()) {
return true;
}
match_subterm(&pat.expr, &effective)
}
fn match_any_conclusion(pat: &TypeExpr, ty: &TypeExpr) -> bool {
if matches_type(pat, ty.conclusion()) {
return true;
}
match ty {
TypeExpr::Arrow(_, r) | TypeExpr::Pi { body: r, .. } | TypeExpr::Forall { body: r, .. } => {
match_any_conclusion(pat, r)
}
_ => false,
}
}
fn match_subterm(pat: &TypeExpr, ty: &TypeExpr) -> bool {
if matches_type(pat, ty) {
return true;
}
match ty {
TypeExpr::App(f, a) => match_subterm(pat, f) || match_subterm(pat, a),
TypeExpr::BinOp { left, right, .. } => {
match_subterm(pat, left) || match_subterm(pat, right)
}
TypeExpr::UnaryOp { arg, .. } | TypeExpr::Postfix { arg, .. } => match_subterm(pat, arg),
TypeExpr::Arrow(a, b) => match_subterm(pat, a) || match_subterm(pat, b),
TypeExpr::Forall { body, .. }
| TypeExpr::Exists { body, .. }
| TypeExpr::Lambda { body, .. }
| TypeExpr::Pi { body, .. } => match_subterm(pat, body),
TypeExpr::Proj { base, .. } => match_subterm(pat, base),
_ => false,
}
}
pub fn score_hit(pat: &SearchPattern, decl: &Declaration) -> i32 {
let mut score = 0;
let effective = decl.effective_type();
if matches_type(&pat.expr, effective.conclusion()) {
score += 100;
}
if pat.conclusion_only {
score += 10;
}
score += 50usize.saturating_sub(decl.full_name.len()) as i32;
score += match decl.kind {
crate::ast::DeclKind::Theorem => 3,
crate::ast::DeclKind::Lemma => 2,
crate::ast::DeclKind::Axiom => 1,
};
score
}
pub fn parse_pattern(input: &str) -> anyhow::Result<SearchPattern> {
parse_search_pattern(input).map_err(|e| anyhow::anyhow!(e.to_string()))
}
pub fn pattern_index_keys(pat: &SearchPattern) -> Vec<String> {
let expr = if pat.conclusion_only {
pat.expr.conclusion()
} else {
&pat.expr
};
let mut keys = Vec::new();
let head = expr.head_key();
if !head.starts_with("hole") {
keys.push(head);
}
for op in expr.operators() {
keys.push(format!("op:{op}"));
}
let ch = pat.expr.conclusion().head_key();
if !ch.starts_with("hole") {
keys.push(ch);
}
keys.sort();
keys.dedup();
keys
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::DeclKind;
use crate::parser::parse_type;
fn decl_with_type(surface: &str) -> Declaration {
let ty = parse_type(surface).unwrap_or(TypeExpr::Raw(surface.into()));
Declaration {
kind: DeclKind::Theorem,
name: "t".into(),
full_name: "T.t".into(),
binders: vec![],
ty,
type_surface: surface.into(),
file: "t.lean".into(),
line: 1,
module: None,
namespace_path: vec![],
attributes: vec![],
}
}
#[test]
fn hole_match_add_eq_zero() {
let d = decl_with_type("n + m = 0");
let p = parse_pattern("_ + _ = 0").unwrap();
assert!(matches_decl(&p, &d));
}
#[test]
fn named_hole_same() {
let d = decl_with_type("x - x = 0");
let p = parse_pattern("?a - ?a = 0").unwrap();
assert!(matches_decl(&p, &d));
let d2 = decl_with_type("x - y = 0");
assert!(!matches_decl(&p, &d2));
}
#[test]
fn turnstile_conclusion() {
let d = decl_with_type("∀ (n : Nat), n + 0 = n");
let p = parse_pattern("|- _ + 0 = _").unwrap();
assert!(matches_decl(&p, &d));
}
#[test]
fn no_match_different_op() {
let d = decl_with_type("n * m = 0");
let p = parse_pattern("_ + _ = 0").unwrap();
assert!(!matches_decl(&p, &d));
}
}