use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
derive_recursor, double_check, infer_type, is_subtype, normalize, Context, DoubleCheck,
NestedDecl, Term, Universe,
};
fn g(n: &str) -> Term {
Term::Global(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 arrow(a: Term, b: Term) -> Term {
Term::Pi { param: "_".to_string(), param_type: Box::new(a), body_type: Box::new(b) }
}
fn v(n: &str) -> Term {
Term::Var(n.to_string())
}
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 ty0() -> Term {
Term::Sort(Universe::Type(0))
}
fn std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
fn rose_tree_decl() -> NestedDecl {
NestedDecl {
name: "RTree".to_string(),
sort: ty0(),
constructors: vec![("rnode".to_string(), arrow(app(g("TList"), g("RTree")), g("RTree")))],
}
}
#[test]
fn rose_tree_via_tlist_compiles_and_typechecks() {
let mut ctx = std_ctx();
let info = ctx.add_nested_inductive(&rose_tree_decl()).expect("rose tree compiles");
assert!(infer_type(&ctx, &g("RTree")).is_ok(), "RTree registered");
assert!(infer_type(&ctx, &g("rnode")).is_ok(), "rnode registered");
assert_eq!(info.siblings, vec!["RTree$TList".to_string()], "TList specialized into a sibling");
assert!(infer_type(&ctx, &g("RTree$TList")).is_ok(), "the specialized sibling is registered");
let rnode_ty = infer_type(&ctx, &g("rnode")).unwrap();
assert!(
is_subtype(&ctx, &rnode_ty, &arrow(g("RTree$TList"), g("RTree"))),
"rnode : RTree$TList → RTree, got {rnode_ty}"
);
}
#[test]
fn rose_tree_isos_are_kernel_checked_and_two_kernel() {
let mut ctx = std_ctx();
let info = ctx.add_nested_inductive(&rose_tree_decl()).expect("rose tree compiles");
let iso = &info.isos[0];
let to_ty = infer_type(&ctx, &g(&iso.to_generic)).expect("to-iso registered");
let from_ty = infer_type(&ctx, &g(&iso.from_generic)).expect("from-iso registered");
assert!(
is_subtype(&ctx, &to_ty, &arrow(g("RTree$TList"), app(g("TList"), g("RTree")))),
"to_generic : RTree$TList → TList RTree, got {to_ty}"
);
assert!(
is_subtype(&ctx, &from_ty, &arrow(app(g("TList"), g("RTree")), g("RTree$TList"))),
"from_generic : TList RTree → RTree$TList, got {from_ty}"
);
for name in [&iso.to_generic, &iso.from_generic] {
match double_check(&ctx, &g(name)) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify iso {name}, got {other:?}"),
}
}
}
#[test]
fn rose_tree_gets_a_working_two_kernel_recursor() {
let mut ctx = std_ctx();
ctx.add_nested_inductive(&rose_tree_decl()).expect("rose tree compiles");
let (_ty, rtree_rec) = derive_recursor(&ctx, "RTree").expect("RTree_rec derives");
match double_check(&ctx, &rtree_rec) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify RTree_rec, got {other:?}"),
}
}
fn deep_tree_decl() -> NestedDecl {
NestedDecl {
name: "DTree".to_string(),
sort: ty0(),
constructors: vec![(
"dnode".to_string(),
arrow(app(g("TList"), app(g("TList"), g("DTree"))), g("DTree")),
)],
}
}
#[test]
fn deeper_two_level_nesting_compiles_and_typechecks() {
let mut ctx = std_ctx();
let info = ctx.add_nested_inductive(&deep_tree_decl()).expect("deep tree compiles");
assert!(infer_type(&ctx, &g("DTree")).is_ok(), "DTree registered");
assert!(infer_type(&ctx, &g("dnode")).is_ok(), "dnode registered");
assert_eq!(
info.siblings,
vec!["DTree$TList".to_string(), "DTree$TList$TList".to_string()],
"both nested levels specialized, inner first"
);
assert!(infer_type(&ctx, &g("DTree$TList")).is_ok(), "inner sibling registered");
assert!(infer_type(&ctx, &g("DTree$TList$TList")).is_ok(), "outer sibling registered");
let dnode_ty = infer_type(&ctx, &g("dnode")).unwrap();
assert!(
is_subtype(&ctx, &dnode_ty, &arrow(g("DTree$TList$TList"), g("DTree"))),
"dnode : DTree$TList$TList → DTree, got {dnode_ty}"
);
}
#[test]
fn deeper_nesting_isos_are_two_kernel_certified() {
let mut ctx = std_ctx();
let info = ctx.add_nested_inductive(&deep_tree_decl()).expect("deep tree compiles");
assert_eq!(info.isos.len(), 2, "one iso pair per nested level");
for iso in &info.isos {
for name in [&iso.to_generic, &iso.from_generic] {
assert!(infer_type(&ctx, &g(name)).is_ok(), "iso {name} registered");
match double_check(&ctx, &g(name)) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify iso {name}, got {other:?}"),
}
}
}
}
#[test]
fn deeper_nesting_gets_a_working_recursor() {
let mut ctx = std_ctx();
ctx.add_nested_inductive(&deep_tree_decl()).expect("deep tree compiles");
let (_ty, rec) = derive_recursor(&ctx, "DTree").expect("DTree_rec derives");
match double_check(&ctx, &rec) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify DTree_rec, got {other:?}"),
}
}
#[test]
fn deeper_nesting_isos_round_trip_by_computation() {
let mut ctx = std_ctx();
let info = ctx.add_nested_inductive(&deep_tree_decl()).expect("deep tree compiles");
let outer = &info.isos[1];
assert_eq!(outer.sibling, "DTree$TList$TList");
let to_b = g(&outer.to_generic);
let from_b = g(&outer.from_generic);
let sa_nil = g("DTree$TList_TNil");
let sb = apps(g("DTree$TList$TList_TCons"), &[sa_nil, g("DTree$TList$TList_TNil")]);
let round = app(from_b.clone(), app(to_b.clone(), sb.clone()));
assert_eq!(
normalize(&ctx, &round),
normalize(&ctx, &sb),
"from_B (to_B sb) = sb through the nested element iso"
);
let gen = apps(
g("TCons"),
&[
app(g("TList"), g("DTree")),
app(g("TNil"), g("DTree")),
app(g("TNil"), app(g("TList"), g("DTree"))),
],
);
let round_gen = app(to_b, app(from_b, gen.clone()));
assert_eq!(
normalize(&ctx, &round_gen),
normalize(&ctx, &gen),
"to_B (from_B gen) = gen"
);
}
#[test]
fn higher_universe_nested_inductive_is_rejected() {
let mut ctx = std_ctx();
let decl = NestedDecl {
name: "BigTree".to_string(),
sort: Term::Sort(Universe::Type(1)),
constructors: vec![("bnode".to_string(), arrow(app(g("TList"), g("BigTree")), g("BigTree")))],
};
assert!(ctx.add_nested_inductive(&decl).is_err(), "a non-Type-0 nested inductive must be rejected");
}
#[test]
fn nesting_in_an_impure_container_is_rejected() {
let mut ctx = std_ctx();
ctx.add_inductive("LList", arrow(ty0(), ty0()));
ctx.add_constructor("LNil", "LList", pi("A", ty0(), app(g("LList"), v("A"))));
ctx.add_constructor(
"LCons",
"LList",
pi(
"A",
ty0(),
arrow(v("A"), arrow(g("Nat"), arrow(app(g("LList"), v("A")), app(g("LList"), v("A"))))),
),
);
let decl = NestedDecl {
name: "LTree".to_string(),
sort: ty0(),
constructors: vec![("lnode".to_string(), arrow(app(g("LList"), g("LTree")), g("LTree")))],
};
assert!(
ctx.add_nested_inductive(&decl).is_err(),
"nesting in a container with a non-element/non-recursive field must be rejected"
);
}
#[test]
fn rose_tree_isos_round_trip_by_computation() {
let mut ctx = std_ctx();
let info = ctx.add_nested_inductive(&rose_tree_decl()).expect("rose tree compiles");
let iso = &info.isos[0];
let to = g(&iso.to_generic);
let from = g(&iso.from_generic);
let leaf = app(g("rnode"), g("RTree$TList_TNil"));
let sib = apps(g("RTree$TList_TCons"), &[leaf.clone(), g("RTree$TList_TNil")]);
let round_sib = app(from.clone(), app(to.clone(), sib.clone()));
assert_eq!(
normalize(&ctx, &round_sib),
normalize(&ctx, &sib),
"from_generic (to_generic sib) = sib"
);
let gen = apps(g("TCons"), &[g("RTree"), leaf, app(g("TNil"), g("RTree"))]);
let round_gen = app(to, app(from, gen.clone()));
assert_eq!(
normalize(&ctx, &round_gen),
normalize(&ctx, &gen),
"to_generic (from_generic gen) = gen"
);
}