use crate::declaration::{ConstantInfo, RecursorRule, RecursorVal};
use crate::instantiate::instantiate_type_lparams;
use crate::{Environment, Expr, Level, Node, TypeChecker};
pub fn same_up_to_binders(a: &Expr, b: &Expr) -> bool {
match (a, b) {
(Expr::BVar(i), Expr::BVar(j)) => i == j,
(Expr::FVar(x), Expr::FVar(y)) => x == y,
(Expr::Sort(l1), Expr::Sort(l2)) => l1 == l2,
(Expr::Const(n1, ls1), Expr::Const(n2, ls2)) => n1 == n2 && ls1 == ls2,
(Expr::Lit(l1), Expr::Lit(l2)) => l1 == l2,
(Expr::App(f1, a1), Expr::App(f2, a2)) => same_node(f1, f2) && same_node(a1, a2),
(Expr::Lam(_, _, t1, b1), Expr::Lam(_, _, t2, b2))
| (Expr::Pi(_, _, t1, b1), Expr::Pi(_, _, t2, b2)) => {
same_node(t1, t2) && same_node(b1, b2)
}
(Expr::Let(_, t1, v1, b1), Expr::Let(_, t2, v2, b2)) => {
same_node(t1, t2) && same_node(v1, v2) && same_node(b1, b2)
}
(Expr::Proj(n1, i1, e1), Expr::Proj(n2, i2, e2)) => {
n1 == n2 && i1 == i2 && same_node(e1, e2)
}
_ => false,
}
}
fn same_node(a: &Node, b: &Node) -> bool {
Node::ptr_eq(a, b) || same_up_to_binders(a, b)
}
fn may_be_checked(supplied: &Expr) -> bool {
let name = crate::Name::default();
crate::check::check_term_depth(&name, supplied).is_ok()
&& crate::check::check_closed_term(&name, supplied).is_ok()
}
fn type_matches(tc: &mut TypeChecker<'_>, kernel: &Expr, supplied: &Expr) -> bool {
same_up_to_binders(kernel, supplied)
|| (may_be_checked(supplied)
&& tc.ensure_sort(supplied).is_ok()
&& tc.is_def_eq(kernel, supplied))
}
fn value_matches(tc: &mut TypeChecker<'_>, kernel: &Expr, supplied: &Expr) -> bool {
if same_up_to_binders(kernel, supplied) {
return true;
}
if !may_be_checked(supplied) {
return false;
}
let Ok(supplied_ty) = tc.infer_type(supplied) else {
return false;
};
let Ok(kernel_ty) = tc.infer_type(kernel) else {
return false;
};
tc.is_def_eq(&kernel_ty, &supplied_ty) && tc.is_def_eq(kernel, supplied)
}
pub fn supplied_type_matches(env: &Environment, kernel: &Expr, supplied: &Expr) -> bool {
same_up_to_binders(kernel, supplied)
|| type_matches(&mut TypeChecker::new(env), kernel, supplied)
}
pub(crate) fn derived_under_supplied_names(
d: &RecursorVal,
rv: &RecursorVal,
) -> Option<RecursorVal> {
let from = &d.common.level_params;
let to = &rv.common.level_params;
if from.len() != to.len() {
return None;
}
if to.iter().enumerate().any(|(i, n)| to[..i].contains(n)) {
return None;
}
let mut installed = d.clone();
installed.is_unsafe = rv.is_unsafe;
if from != to {
let levels: Vec<Level> = to.iter().cloned().map(Level::param).collect();
installed.common.ty = instantiate_type_lparams(&d.common.ty, from, &levels);
installed.rules = d
.rules
.iter()
.map(|rule| RecursorRule {
ctor: rule.ctor.clone(),
nfields: rule.nfields,
rhs: instantiate_type_lparams(&rule.rhs, from, &levels),
})
.collect();
installed.common.level_params = to.clone();
}
Some(installed)
}
enum AsWritten {
Same,
Differs,
Unsettled,
}
fn as_written(d: &RecursorVal, rv: &RecursorVal) -> AsWritten {
if d.common.name != rv.common.name
|| d.common.level_params != rv.common.level_params
|| d.num_params != rv.num_params
|| d.num_indices != rv.num_indices
|| d.num_motives != rv.num_motives
|| d.num_minors != rv.num_minors
|| d.k != rv.k
|| d.rules.len() != rv.rules.len()
{
return AsWritten::Differs;
}
if d.rules
.iter()
.zip(&rv.rules)
.any(|(drule, srule)| srule.ctor != drule.ctor || srule.nfields != drule.nfields)
{
return AsWritten::Differs;
}
let same = same_up_to_binders(&d.common.ty, &rv.common.ty)
&& d.rules
.iter()
.zip(&rv.rules)
.all(|(drule, srule)| same_up_to_binders(&drule.rhs, &srule.rhs));
if same {
AsWritten::Same
} else {
AsWritten::Unsettled
}
}
pub(crate) fn recursor_matches(
env: &Environment,
family: &[&[ConstantInfo]],
d: &RecursorVal,
rv: &RecursorVal,
) -> bool {
match as_written(d, rv) {
AsWritten::Same => true,
AsWritten::Differs => false,
AsWritten::Unsettled => {
let mut work = env.clone();
let staged = std::iter::once(ConstantInfo::Recursor(d.clone()))
.chain(family.iter().flat_map(|group| group.iter().cloned()));
for ci in staged {
if !work.contains(ci.name()) && work.add_constant(ci).is_err() {
return false;
}
}
let mut tc = TypeChecker::new(&work);
type_matches(&mut tc, &d.common.ty, &rv.common.ty)
&& d.rules
.iter()
.zip(&rv.rules)
.all(|(drule, srule)| value_matches(&mut tc, &drule.rhs, &srule.rhs))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BinderInfo, Name};
fn sort(n: u32) -> Expr {
let mut l = Level::zero();
for _ in 0..n {
l = Level::succ(l);
}
Expr::Sort(l)
}
fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
Expr::Pi(bi, Name::str(name), Node::new(dom), Node::new(body))
}
#[test]
fn binder_names_and_binder_information_are_not_compared() {
let a = pi(BinderInfo::Default, "x", sort(1), Expr::BVar(0));
let b = pi(BinderInfo::Implicit, "y", sort(1), Expr::BVar(0));
assert!(same_up_to_binders(&a, &b));
}
#[test]
fn levels_are_compared_as_written() {
let m = Expr::Sort(Level::max(Level::zero(), Level::zero()));
assert!(!same_up_to_binders(&m, &sort(0)));
let c0 = Expr::Const(Name::str("C"), vec![Level::zero()]);
let c1 = Expr::Const(Name::str("C"), vec![Level::succ(Level::zero())]);
assert!(!same_up_to_binders(&c0, &c1));
assert!(same_up_to_binders(&c0, &c0.clone()));
}
#[test]
fn a_binder_is_not_a_lambda_and_a_shape_is_compared_throughout() {
let p = pi(BinderInfo::Default, "x", sort(1), Expr::BVar(0));
let l = Expr::Lam(
BinderInfo::Default,
Name::str("x"),
Node::new(sort(1)),
Node::new(Expr::BVar(0)),
);
assert!(!same_up_to_binders(&p, &l));
let q = pi(BinderInfo::Default, "x", sort(1), sort(0));
assert!(!same_up_to_binders(&p, &q));
}
#[test]
fn a_supplied_type_that_is_not_closed_is_never_type_checked() {
let env = Environment::new();
assert!(!supplied_type_matches(&env, &sort(1), &Expr::BVar(0)));
let kernel = pi(BinderInfo::Default, "α", sort(1), Expr::BVar(0));
for id in 0..4 {
let free = pi(
BinderInfo::Default,
"α",
sort(1),
Expr::FVar(crate::FVarId(id)),
);
assert!(!supplied_type_matches(&env, &kernel, &free), "FVar {id}");
}
let a = pi(BinderInfo::Default, "x", sort(1), sort(0));
let b = pi(BinderInfo::Default, "α", sort(1), sort(0));
assert!(supplied_type_matches(&env, &a, &b));
}
fn recursor(level_params: &[&str]) -> RecursorVal {
let first = Level::param(Name::str(level_params[0]));
RecursorVal {
common: crate::declaration::ConstantVal {
name: Name::str("T.rec"),
level_params: level_params.iter().map(|p| Name::str(*p)).collect(),
ty: Expr::Sort(first.clone()),
},
all: vec![Name::str("T")],
num_params: 0,
num_indices: 0,
num_motives: 1,
num_minors: 1,
rules: vec![RecursorRule {
ctor: Name::str("T.mk"),
nfields: 0,
rhs: Expr::Sort(first),
}],
k: false,
is_unsafe: false,
}
}
#[test]
fn the_derived_recursor_is_put_under_the_supplied_universe_parameter_names() {
let derived = recursor(&["u", "w"]);
let mut supplied = recursor(&["u", "w"]);
supplied.is_unsafe = true;
let installed = derived_under_supplied_names(&derived, &supplied);
assert_eq!(
installed,
Some(RecursorVal {
is_unsafe: true,
..derived.clone()
})
);
let installed = derived_under_supplied_names(&derived, &recursor(&["v", "w"]));
assert_eq!(installed, Some(recursor(&["v", "w"])));
assert_eq!(
derived_under_supplied_names(&derived, &recursor(&["u"])),
None
);
assert_eq!(
derived_under_supplied_names(&derived, &recursor(&["w", "w"])),
None
);
}
}