use super::types::TypeChecker;
use crate::def_eq::types::DEF_EQ_OPENED_FVAR_BASE;
use crate::{BinderInfo, Environment, Expr, FVarId, Level, LocalDecl, Name, Node};
fn prop() -> Expr {
Expr::Sort(Level::zero())
}
fn lam(name: &str, ty: Expr, body: Expr) -> Expr {
Expr::Lam(
BinderInfo::Default,
Name::str(name),
Node::new(ty),
Node::new(body),
)
}
fn nested(names: &[&str]) -> Expr {
let outermost = u32::try_from(names.len().saturating_sub(1)).unwrap_or(u32::MAX);
names
.iter()
.rev()
.fold(Expr::BVar(outermost), |body, name| lam(name, prop(), body))
}
fn live_counts(tc: &TypeChecker<'_>) -> [usize; 3] {
let (mirrors, _) = tc.local_mirrors();
mirrors.map(|mirror| mirror.live().len())
}
#[test]
fn inferring_a_term_leaves_no_binder_in_the_mirrors() {
let env = Environment::new();
let mut tc = TypeChecker::new(&env);
let term = nested(&["x", "y", "z"]);
let ty = tc
.infer_type(&term)
.unwrap_or_else(|e| panic!("`fun x y z : Prop => x` must be typeable: {e:?}"));
assert!(ty.is_pi(), "expected a Pi type, got {ty}");
assert!(tc.local_ctx().is_empty());
assert_eq!(live_counts(&tc), [0, 0, 0]);
let (mirrors, _) = tc.local_mirrors();
for mirror in mirrors {
assert_eq!(mirror.max_own_id(), Some(2));
assert_eq!(mirror.max_opened_id(), None);
assert_eq!(mirror.closed_range_count(), 1);
for id in 0..3 {
assert_eq!(mirror.get(FVarId(id)), None);
}
}
}
#[test]
fn comparing_binders_leaves_no_opened_variable_in_the_mirrors() {
let env = Environment::new();
let mut tc = TypeChecker::new(&env);
let lhs = nested(&["x", "y"]);
let rhs = nested(&["a", "b"]);
assert_ne!(lhs, rhs);
assert!(tc.is_def_eq(&lhs, &rhs));
assert_eq!(live_counts(&tc), [0, 0, 0]);
let ([own_reducer, def_eq, def_eq_reducer], next_opened) = tc.local_mirrors();
assert_eq!(own_reducer.max_opened_id(), None);
for mirror in [def_eq, def_eq_reducer] {
assert_eq!(mirror.max_opened_id(), Some(DEF_EQ_OPENED_FVAR_BASE + 1));
assert_eq!(mirror.max_own_id(), None);
assert_eq!(mirror.closed_range_count(), 1);
}
assert_eq!(next_opened, DEF_EQ_OPENED_FVAR_BASE + 2);
}
#[test]
fn a_local_stays_in_the_mirrors_while_its_binder_is_open() {
let env = Environment::new();
let mut tc = TypeChecker::new(&env);
let h = tc.fresh_fvar(Name::str("h"), prop());
assert_eq!(live_counts(&tc), [1, 1, 1]);
let over_h = lam("x", prop(), lam("y", prop(), Expr::FVar(h)));
tc.infer_type(&over_h)
.unwrap_or_else(|e| panic!("`fun x y : Prop => h` must be typeable: {e:?}"));
assert!(tc.is_def_eq(&nested(&["x", "y"]), &nested(&["a", "b"])));
assert_eq!(live_counts(&tc), [1, 1, 1]);
let (mirrors, _) = tc.local_mirrors();
for mirror in mirrors {
assert_eq!(mirror.get(h), Some(&prop()));
}
let popped = tc.pop_local();
assert_eq!(popped.map(|decl| decl.fvar), Some(h));
assert_eq!(live_counts(&tc), [0, 0, 0]);
}
#[test]
fn a_nested_checker_mints_past_closed_binders() {
let env = Environment::new();
let mut tc = TypeChecker::new(&env);
let outer = tc.fresh_fvar(Name::str("a"), prop());
tc.fresh_fvar(Name::str("b"), prop());
tc.fresh_fvar(Name::str("c"), prop());
tc.pop_local();
tc.pop_local();
assert!(tc.is_def_eq(&nested(&["x", "y"]), &nested(&["a", "b"])));
let ([_, _, mirror], _) = tc.local_mirrors();
assert_eq!(mirror.live().len(), 1);
assert_eq!(mirror.get(outer), Some(&prop()));
let mut from_live = TypeChecker::with_fvar_types(&env, mirror.live());
assert_eq!(from_live.fresh_fvar(Name::str("z"), prop()), FVarId(1));
assert_eq!(from_live.local_mirrors().1, DEF_EQ_OPENED_FVAR_BASE);
let mut nested_tc = TypeChecker::with_local_types(&env, mirror);
assert_eq!(nested_tc.local_mirrors().1, DEF_EQ_OPENED_FVAR_BASE + 2);
assert_eq!(nested_tc.fresh_fvar(Name::str("z"), prop()), FVarId(3));
assert_eq!(
nested_tc.infer_type(&Expr::FVar(outer)).ok(),
Some(prop()),
"the seeded local must be typeable"
);
assert!(nested_tc.infer_type(&Expr::FVar(FVarId(1))).is_err());
}
#[test]
fn popping_one_of_two_declarations_of_a_variable_keeps_its_type() {
let env = Environment::new();
let mut tc = TypeChecker::new(&env);
let x = FVarId(9);
let decl = || LocalDecl {
fvar: x,
name: Name::str("x"),
ty: prop(),
val: None,
};
tc.push_local(decl());
tc.push_local(decl());
assert_eq!(live_counts(&tc), [1, 1, 1]);
tc.pop_local();
assert_eq!(tc.local_ctx().len(), 1);
assert_eq!(live_counts(&tc), [1, 1, 1]);
tc.pop_local();
assert_eq!(live_counts(&tc), [0, 0, 0]);
assert!(tc.pop_local().is_none());
}