use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
derive_recursor, double_check, infer_type, is_subtype, recheck, Context, DoubleCheck, Term,
Universe,
};
fn lam(p: &str, t: Term, b: Term) -> Term {
Term::Lambda { param: p.to_string(), param_type: Box::new(t), body: Box::new(b) }
}
fn fix(name: &str, body: Term) -> Term {
Term::Fix { name: name.to_string(), body: Box::new(body) }
}
fn nat() -> Term {
g("Nat")
}
fn match_(d: Term, motive: Term, cases: Vec<Term>) -> Term {
Term::Match { discriminant: Box::new(d), motive: Box::new(motive), cases }
}
fn g(n: &str) -> Term {
Term::Global(n.to_string())
}
fn v(n: &str) -> Term {
Term::Var(n.to_string())
}
fn app(f: Term, x: Term) -> Term {
Term::App(Box::new(f), Box::new(x))
}
fn apps(f: Term, xs: &[Term]) -> Term {
xs.iter().fold(f, |a, x| app(a, x.clone()))
}
fn pi(p: &str, t: Term, b: Term) -> Term {
Term::Pi { param: p.to_string(), param_type: Box::new(t), body_type: Box::new(b) }
}
fn arrow(a: Term, b: Term) -> Term {
pi("_", a, b)
}
fn prop() -> Term {
Term::Sort(Universe::Prop)
}
fn ty0() -> Term {
Term::Sort(Universe::Type(0))
}
fn std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
fn acc_ctx() -> Context {
std_ctx()
}
#[test]
fn acc_intro_passes_positivity() {
let ctx = acc_ctx();
assert!(infer_type(&ctx, &g("Acc_intro")).is_ok(), "Acc_intro must type-check");
assert!(infer_type(&ctx, &g("Acc")).is_ok(), "Acc must be registered");
}
#[test]
fn negative_occurrence_paradox_still_rejected() {
use logicaffeine_kernel::check_positivity_for_test as pos;
assert!(
pos("Bad", "mk", &arrow(arrow(g("Bad"), g("False")), g("Bad"))).is_err(),
"(Bad → False) → Bad must be rejected"
);
assert!(
pos("Bad", "mk", &arrow(arrow(g("Bad"), g("Nat")), g("Bad"))).is_err(),
"(Bad → Nat) → Bad must be rejected"
);
assert!(
pos(
"Bad",
"mk",
&arrow(arrow(arrow(g("Bad"), g("False")), g("False")), g("Bad"))
)
.is_err(),
"nested negative occurrence must be rejected"
);
}
#[test]
fn positive_function_field_is_accepted() {
let mut ctx = std_ctx();
ctx.add_inductive("Tree", ty0());
assert!(
logicaffeine_kernel::check_positivity_for_test(
"Tree",
"node",
&arrow(arrow(g("Nat"), g("Tree")), g("Tree"))
)
.is_ok(),
"(Nat → Tree) → Tree is strictly positive"
);
}
fn expected_acc_rec_type() -> Term {
let acc = |x: Term| apps(g("Acc"), &[v("A"), v("R"), x]);
let ryx = |y: Term, x: Term| apps(v("R"), &[y, x]);
let h_ty = pi("y", v("A"), arrow(ryx(v("y"), v("x")), acc(v("y"))));
let ih_ty = pi(
"y",
v("A"),
pi(
"hr",
ryx(v("y"), v("x")),
apps(v("P"), &[v("y"), apps(v("h"), &[v("y"), v("hr")])]),
),
);
let concl = apps(v("P"), &[v("x"), apps(g("Acc_intro"), &[v("A"), v("R"), v("x"), v("h")])]);
let minor = pi("x", v("A"), pi("h", h_ty, pi("ih", ih_ty, concl)));
let motive_ty = pi("x", v("A"), arrow(acc(v("x")), ty0()));
pi(
"A",
ty0(),
pi(
"R",
arrow(v("A"), arrow(v("A"), prop())),
pi(
"P",
motive_ty,
pi(
"f",
minor,
pi("x", v("A"), pi("acc", acc(v("x")), apps(v("P"), &[v("x"), v("acc")]))),
),
),
),
)
}
#[test]
fn acc_recursor_derives_with_functional_ih() {
let ctx = acc_ctx();
let (rec_ty, rec_body) = derive_recursor(&ctx, "Acc").expect("Acc recursor derives");
let expected = expected_acc_rec_type();
assert!(
is_subtype(&ctx, &rec_ty, &expected) && is_subtype(&ctx, &expected, &rec_ty),
"derived Acc_rec type must be the FULL recursor (with functional IH).\n\
derived = {rec_ty}\n\
expected = {expected}"
);
let inferred = infer_type(&ctx, &rec_body).expect("Acc_rec body type-checks");
assert!(is_subtype(&ctx, &inferred, &rec_ty), "Acc_rec body : Acc_rec type");
}
#[test]
fn acc_recursor_is_two_kernel() {
let ctx = acc_ctx();
let (_rec_ty, rec_body) = derive_recursor(&ctx, "Acc").expect("Acc recursor derives");
match double_check(&ctx, &rec_body) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must accept Acc_rec, got {other:?}"),
}
}
#[test]
fn constructor_applied_recursive_call_rejected_by_both_kernels() {
let ctx = std_ctx();
let bad = fix(
"f",
lam(
"n",
nat(),
match_(
v("n"),
lam("_", nat(), nat()),
vec![
g("Zero"),
lam("k", nat(), app(v("f"), app(g("Succ"), v("k")))),
],
),
),
);
assert!(
infer_type(&ctx, &bad).is_err(),
"main kernel must reject a recursive call on `Succ k`"
);
assert!(
recheck(&ctx, &bad).is_err(),
"de Bruijn re-checker must independently reject a recursive call on `Succ k`"
);
}
#[test]
fn genuine_structural_recursion_accepted_by_both_kernels() {
let ctx = std_ctx();
let good = fix(
"f",
lam(
"n",
nat(),
match_(
v("n"),
lam("_", nat(), nat()),
vec![g("Zero"), lam("k", nat(), app(v("f"), v("k")))],
),
),
);
assert!(infer_type(&ctx, &good).is_ok(), "main kernel must accept honest structural recursion");
match double_check(&ctx, &good) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must agree on honest structural recursion, got {other:?}"),
}
}
#[test]
fn prelude_registers_acc_wellfounded_and_recursor() {
let mut ctx = std_ctx();
for name in ["Acc", "Acc_intro", "Acc_rec", "WellFounded"] {
assert!(infer_type(&ctx, &g(name)).is_ok(), "{name} must be registered and type-check");
}
ctx.add_declaration("lt", arrow(nat(), arrow(nat(), prop())));
let wf = apps(g("WellFounded"), &[nat(), g("lt")]);
let wf_sort = infer_type(&ctx, &wf).expect("WellFounded Nat lt type-checks");
assert!(
is_subtype(&ctx, &wf_sort, &prop()) && is_subtype(&ctx, &prop(), &wf_sort),
"WellFounded Nat lt must be a Prop, got {wf_sort}"
);
}
#[test]
fn acc_rec_builds_a_well_founded_recursion_over_nat() {
let mut ctx = std_ctx();
ctx.add_declaration("lt", arrow(nat(), arrow(nat(), prop())));
let lt = g("lt");
let acc = |x: Term| apps(g("Acc"), &[nat(), lt.clone(), x]);
let motive = lam("x", nat(), lam("_", acc(v("x")), nat()));
let h_ty = |x: Term| pi("y", nat(), arrow(apps(lt.clone(), &[v("y"), x]), acc(v("y"))));
let ih_ty = |x: Term| pi("y", nat(), pi("hr", apps(lt.clone(), &[v("y"), x]), nat()));
let f = lam("x", nat(), lam("h", h_ty(v("x")), lam("ih", ih_ty(v("x")), g("Zero"))));
let applied = apps(g("Acc_rec"), &[nat(), lt.clone(), motive, f]);
let got = infer_type(&ctx, &applied).expect("Acc_rec application type-checks");
let expected = pi("x", nat(), arrow(acc(v("x")), nat()));
assert!(
is_subtype(&ctx, &got, &expected) && is_subtype(&ctx, &expected, &got),
"Acc_rec application must have the well-founded recursion type.\n\
got = {got}\n\
expected = {expected}"
);
match double_check(&ctx, &applied) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify the Acc_rec application, got {other:?}"),
}
}