#![allow(clippy::all)]
use oxilean_kernel::instantiate::has_loose_bvars;
use oxilean_kernel::level::normalize as normalize_level;
use oxilean_kernel::{
abstract_expr, instantiate, is_def_eq_simple, whnf, BinderInfo, Expr, FVarId, Level, Literal,
Name,
};
use proptest::prelude::*;
fn lower_bvars(expr: &Expr, n: u32) -> Expr {
lower_bvars_at(expr, n, 0)
}
fn lower_bvars_at(expr: &Expr, n: u32, depth: u32) -> Expr {
match expr {
Expr::BVar(i) => {
if *i >= depth {
Expr::BVar(i.saturating_sub(n))
} else {
expr.clone()
}
}
Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
Expr::App(f, a) => Expr::App(
Box::new(lower_bvars_at(f, n, depth)),
Box::new(lower_bvars_at(a, n, depth)),
),
Expr::Lam(bi, name, dom, body) => Expr::Lam(
*bi,
name.clone(),
Box::new(lower_bvars_at(dom, n, depth)),
Box::new(lower_bvars_at(body, n, depth + 1)),
),
Expr::Pi(bi, name, dom, body) => Expr::Pi(
*bi,
name.clone(),
Box::new(lower_bvars_at(dom, n, depth)),
Box::new(lower_bvars_at(body, n, depth + 1)),
),
Expr::Let(name, ty, val, body) => Expr::Let(
name.clone(),
Box::new(lower_bvars_at(ty, n, depth)),
Box::new(lower_bvars_at(val, n, depth)),
Box::new(lower_bvars_at(body, n, depth + 1)),
),
Expr::Proj(name, idx, e) => {
Expr::Proj(name.clone(), *idx, Box::new(lower_bvars_at(e, n, depth)))
}
}
}
fn arb_name() -> impl Strategy<Value = Name> {
prop_oneof![
Just(Name::str("x")),
Just(Name::str("y")),
Just(Name::str("z")),
Just(Name::str("Nat")),
Just(Name::str("Bool")),
Just(Name::str("Nat").append_str("succ")),
Just(Name::str("Nat").append_str("zero")),
Just(Name::str("List").append_str("nil")),
Just(Name::str("f")),
Just(Name::str("g")),
]
}
fn arb_name_for_roundtrip() -> impl Strategy<Value = Name> {
prop_oneof![
"[a-z][a-z0-9]{0,4}".prop_map(|s| Name::str(s)),
("[a-z][a-z0-9]{0,3}", "[a-z][a-z0-9]{0,3}")
.prop_map(|(a, b)| { Name::str(a).append_str(b) }),
("[a-z][a-z0-9]{0,3}", 0u64..100u64).prop_map(|(s, n)| { Name::str(s).append_num(n) }),
]
}
fn arb_level(max_depth: u32) -> impl Strategy<Value = Level> {
arb_level_impl(max_depth)
}
fn arb_level_impl(depth: u32) -> impl Strategy<Value = Level> {
if depth == 0 {
prop_oneof![Just(Level::Zero), arb_name().prop_map(Level::Param),].boxed()
} else {
prop_oneof![
Just(Level::Zero),
arb_name().prop_map(Level::Param),
arb_level_impl(depth - 1).prop_map(|l| Level::Succ(Box::new(l))),
(arb_level_impl(depth - 1), arb_level_impl(depth - 1))
.prop_map(|(l1, l2)| Level::Max(Box::new(l1), Box::new(l2))),
(arb_level_impl(depth - 1), arb_level_impl(depth - 1))
.prop_map(|(l1, l2)| Level::IMax(Box::new(l1), Box::new(l2))),
]
.boxed()
}
}
fn arb_closed_expr(max_depth: u32) -> impl Strategy<Value = Expr> {
arb_closed_expr_impl(max_depth, 0)
}
fn arb_closed_expr_impl(depth: u32, binder_depth: u32) -> impl Strategy<Value = Expr> {
if depth == 0 {
prop_oneof![
arb_level(2).prop_map(Expr::Sort),
arb_name().prop_map(|n| Expr::Const(n, vec![])),
(0u64..=1000u64).prop_map(|n| Expr::Lit(Literal::Nat(n))),
]
.boxed()
} else {
prop_oneof![
arb_level(2).prop_map(Expr::Sort),
arb_name().prop_map(|n| Expr::Const(n, vec![])),
(0u64..=1000u64).prop_map(|n| Expr::Lit(Literal::Nat(n))),
(
arb_closed_expr_impl(depth - 1, binder_depth),
arb_closed_expr_impl(depth - 1, binder_depth)
)
.prop_map(|(f, a)| Expr::App(Box::new(f), Box::new(a))),
(
arb_name(),
arb_closed_expr_impl(depth - 1, binder_depth),
arb_closed_expr_impl(depth - 1, binder_depth + 1)
)
.prop_map(|(name, ty, body)| {
Expr::Lam(BinderInfo::Default, name, Box::new(ty), Box::new(body))
}),
(
arb_name(),
arb_closed_expr_impl(depth - 1, binder_depth),
arb_closed_expr_impl(depth - 1, binder_depth + 1)
)
.prop_map(|(name, ty, body)| {
Expr::Pi(BinderInfo::Default, name, Box::new(ty), Box::new(body))
}),
]
.boxed()
}
}
fn arb_fvar_expr(max_depth: u32, fvar: FVarId) -> impl Strategy<Value = Expr> {
arb_fvar_expr_impl(max_depth, fvar)
}
fn arb_fvar_expr_impl(depth: u32, fvar: FVarId) -> impl Strategy<Value = Expr> {
if depth == 0 {
prop_oneof![
arb_level(2).prop_map(Expr::Sort),
arb_name().prop_map(|n| Expr::Const(n, vec![])),
(0u64..=100u64).prop_map(|n| Expr::Lit(Literal::Nat(n))),
Just(Expr::FVar(fvar)),
]
.boxed()
} else {
prop_oneof![
arb_level(2).prop_map(Expr::Sort),
arb_name().prop_map(|n| Expr::Const(n, vec![])),
(0u64..=100u64).prop_map(|n| Expr::Lit(Literal::Nat(n))),
Just(Expr::FVar(fvar)),
(
arb_fvar_expr_impl(depth - 1, fvar),
arb_fvar_expr_impl(depth - 1, fvar)
)
.prop_map(|(f, a)| Expr::App(Box::new(f), Box::new(a))),
(
arb_name(),
arb_fvar_expr_impl(depth - 1, fvar),
arb_level(1).prop_map(Expr::Sort),
)
.prop_map(|(name, ty, body)| {
Expr::Lam(BinderInfo::Default, name, Box::new(ty), Box::new(body))
}),
(
arb_name(),
arb_fvar_expr_impl(depth - 1, fvar),
arb_level(1).prop_map(Expr::Sort),
)
.prop_map(|(name, ty, body)| {
Expr::Pi(BinderInfo::Default, name, Box::new(ty), Box::new(body))
}),
]
.boxed()
}
}
proptest! {
#[test]
fn prop_substitution_identity(
e in arb_fvar_expr(3, FVarId::new(999)),
fvar_id in 900u64..=999u64,
) {
let fvar = FVarId::new(fvar_id);
prop_assume!(!has_loose_bvars(&e));
let abstracted = abstract_expr(&e, fvar);
let fvar_expr = Expr::FVar(fvar);
let reinstated = instantiate(&abstracted, &fvar_expr);
prop_assert_eq!(reinstated, e,
"instantiate(abstract_expr(e, fvar), fvar) should equal e for closed expressions");
}
}
proptest! {
#[test]
fn prop_lift_lower_bvars_identity(
e in arb_closed_expr(3),
n in 0u32..=5u32,
) {
prop_assume!(!has_loose_bvars(&e));
let lifted = oxilean_kernel::instantiate::lift_bvars(&e, n);
let lowered = lower_bvars(&lifted, n);
prop_assert_eq!(lowered, e,
"lower_bvars(lift_bvars(e, n), n) should equal e for closed expressions");
}
}
proptest! {
#[test]
fn prop_level_normalize_idempotent(l in arb_level(4)) {
let once = normalize_level(&l);
let twice = normalize_level(&once);
prop_assert_eq!(twice, once,
"normalize(normalize(l)) should equal normalize(l)");
}
}
proptest! {
#[test]
fn prop_whnf_idempotent(e in arb_closed_expr(3)) {
let once = whnf(&e);
let twice = whnf(&once);
prop_assert_eq!(twice, once,
"whnf(whnf(e)) should equal whnf(e)");
}
}
proptest! {
#[test]
fn prop_def_eq_reflexive(e in arb_closed_expr(3)) {
let result = is_def_eq_simple(&e, &e);
prop_assert!(result,
"is_def_eq_simple(e, e) should be true for any expression e");
}
}
proptest! {
#[test]
fn prop_name_roundtrip(name in arb_name_for_roundtrip()) {
let displayed = name.to_string();
let reparsed = Name::from_str(&displayed);
prop_assert_eq!(reparsed, name,
"Name::from_str(name.to_string()) should equal name");
}
}
proptest! {
#[test]
fn prop_name_str_roundtrip(
head in "[a-z][a-z0-9]{0,7}",
tail in prop::option::of("[a-z][a-z0-9]{0,7}"),
) {
let name = match tail {
None => Name::str(&head),
Some(t) => Name::str(&head).append_str(t),
};
let displayed = name.to_string();
let reparsed = Name::from_str(&displayed);
prop_assert_eq!(reparsed, name,
"String-component names must round-trip through to_string/from_str");
}
}
proptest! {
#[test]
fn prop_numeric_level_normalize_fixed(n in 0u32..=8u32) {
let mut l = Level::Zero;
for _ in 0..n {
l = Level::Succ(Box::new(l));
}
let normed = normalize_level(&l);
prop_assert_eq!(normed, l,
"Succ^n(Zero) is already in normal form and should be unchanged by normalize");
}
}
proptest! {
#[test]
fn prop_whnf_atoms_are_fixed_points(l in arb_level(2)) {
let sort = Expr::Sort(l);
let whnf_sort = whnf(&sort);
prop_assert_eq!(whnf_sort, sort.clone(),
"Sort is always a WHNF fixed point");
}
}
proptest! {
#[test]
fn prop_whnf_lit_fixed_points(n in 0u64..=u64::MAX) {
let lit = Expr::Lit(Literal::Nat(n));
let whnf_lit = whnf(&lit);
prop_assert_eq!(whnf_lit, lit,
"Literal::Nat is always a WHNF fixed point");
}
}
proptest! {
#[test]
fn prop_def_eq_distinct_lits_false(
n in 0u64..=999u64,
m in 1000u64..=1999u64,
) {
let e1 = Expr::Lit(Literal::Nat(n));
let e2 = Expr::Lit(Literal::Nat(m));
let result = is_def_eq_simple(&e1, &e2);
prop_assert!(!result,
"Distinct literal naturals {} and {} should not be definitionally equal", n, m);
}
}
proptest! {
#[test]
fn prop_instantiate_abstract_no_fvar(
e in arb_closed_expr(3),
replacement in arb_closed_expr(2),
) {
let fvar = FVarId::new(42_000);
prop_assume!(!has_loose_bvars(&e));
let abstracted = abstract_expr(&e, fvar);
let result = instantiate(&abstracted, &replacement);
prop_assert_eq!(result, e,
"When fvar doesn't occur in e, instantiate(abstract_expr(e, fvar), _) == e");
}
}