use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{infer_type, Context, Term, Universe};
use logicaffeine_proof::certifier::{certify, CertificationContext};
use logicaffeine_proof::{
DerivationTree, InductionArg, InductionCase, InferenceRule, ProofExpr, ProofTerm,
};
fn ind(name: &str) -> Term {
Term::Global(name.to_string())
}
fn prop() -> Term {
Term::Sort(Universe::Prop)
}
fn pi(param: &str, ty: Term, body: Term) -> Term {
Term::Pi {
param: param.to_string(),
param_type: Box::new(ty),
body_type: Box::new(body),
}
}
fn app(f: Term, x: Term) -> Term {
Term::App(Box::new(f), Box::new(x))
}
fn kvar(n: &str) -> Term {
Term::Var(n.to_string())
}
fn pred(name: &str, arg: ProofTerm) -> ProofExpr {
ProofExpr::Predicate {
name: name.to_string(),
args: vec![arg],
world: None,
}
}
fn pvar(n: &str) -> ProofTerm {
ProofTerm::Variable(n.to_string())
}
fn pconst(n: &str) -> ProofTerm {
ProofTerm::Constant(n.to_string())
}
fn named(hyp: &str) -> DerivationTree {
DerivationTree::leaf(ProofExpr::Atom(hyp.to_string()), InferenceRule::PremiseMatch)
}
fn implies(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::Implies(Box::new(l), Box::new(r))
}
fn forall(var: &str, body: ProofExpr) -> ProofExpr {
ProofExpr::ForAll {
variable: var.to_string(),
body: Box::new(body),
}
}
#[test]
fn induction_over_three_constructor_enum_covers_all_cases() {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx.add_inductive("Light", Term::Sort(Universe::Type(0)));
for c in ["Red", "Yellow", "Green"] {
ctx.add_constructor(c, "Light", ind("Light"));
}
ctx.add_declaration("P", pi("_", ind("Light"), prop()));
ctx.add_declaration("hRed", app(ind("P"), ind("Red")));
ctx.add_declaration("hYellow", app(ind("P"), ind("Yellow")));
ctx.add_declaration("hGreen", app(ind("P"), ind("Green")));
let tree = DerivationTree::new(
forall("c", pred("P", pvar("c"))),
InferenceRule::InductionScheme {
variable: "c".to_string(),
ind_type: "Light".to_string(),
cases: vec![
InductionCase { constructor: "Red".to_string(), args: vec![] },
InductionCase { constructor: "Yellow".to_string(), args: vec![] },
InductionCase { constructor: "Green".to_string(), args: vec![] },
],
},
vec![named("hRed"), named("hYellow"), named("hGreen")],
);
let cert_ctx = CertificationContext::new(&ctx);
let term = certify(&tree, &cert_ctx).expect("generic induction over Light should certify");
let inferred =
infer_type(&ctx, &term).expect("the certified eliminator must type-check in the kernel");
assert!(
matches!(inferred, Term::Pi { .. }),
"expected ∀c:Light. P(c), got {}",
inferred
);
}
#[test]
fn induction_over_binary_tree_threads_both_hypotheses() {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx.add_inductive("Tree", Term::Sort(Universe::Type(0)));
ctx.add_constructor("Leaf", "Tree", ind("Tree"));
ctx.add_constructor(
"Node",
"Tree",
pi("l", ind("Tree"), pi("r", ind("Tree"), ind("Tree"))),
);
ctx.add_declaration("P", pi("_", ind("Tree"), prop()));
ctx.add_declaration("hLeaf", app(ind("P"), ind("Leaf")));
ctx.add_declaration(
"hNode",
pi(
"l",
ind("Tree"),
pi(
"r",
ind("Tree"),
pi(
"_",
app(ind("P"), kvar("l")),
pi(
"_",
app(ind("P"), kvar("r")),
app(ind("P"), app(app(ind("Node"), kvar("l")), kvar("r"))),
),
),
),
),
);
let p_node = pred(
"P",
ProofTerm::Function("Node".to_string(), vec![pvar("l"), pvar("r")]),
);
let ih_l = DerivationTree::leaf(pred("P", pvar("l")), InferenceRule::PremiseMatch);
let ih_r = DerivationTree::leaf(pred("P", pvar("r")), InferenceRule::PremiseMatch);
let hnode_l = DerivationTree::new(
forall(
"r",
implies(
pred("P", pvar("l")),
implies(pred("P", pvar("r")), p_node.clone()),
),
),
InferenceRule::UniversalInst("l".to_string()),
vec![named("hNode")],
);
let hnode_lr = DerivationTree::new(
implies(
pred("P", pvar("l")),
implies(pred("P", pvar("r")), p_node.clone()),
),
InferenceRule::UniversalInst("r".to_string()),
vec![hnode_l],
);
let app_ihl = DerivationTree::new(
implies(pred("P", pvar("r")), p_node.clone()),
InferenceRule::ModusPonens,
vec![hnode_lr, ih_l],
);
let node_proof = DerivationTree::new(
p_node.clone(),
InferenceRule::ModusPonens,
vec![app_ihl, ih_r],
);
let tree = DerivationTree::new(
forall("t", pred("P", pvar("t"))),
InferenceRule::InductionScheme {
variable: "t".to_string(),
ind_type: "Tree".to_string(),
cases: vec![
InductionCase { constructor: "Leaf".to_string(), args: vec![] },
InductionCase {
constructor: "Node".to_string(),
args: vec![
InductionArg { name: "l".to_string(), recursive: true },
InductionArg { name: "r".to_string(), recursive: true },
],
},
],
},
vec![named("hLeaf"), node_proof],
);
let cert_ctx = CertificationContext::new(&ctx);
let term = certify(&tree, &cert_ctx).expect("generic induction over Tree should certify");
let rec_calls = format!("{}", term).matches("rec_t").count();
assert!(
rec_calls >= 2,
"expected two induction-hypothesis recursive calls, found {}: {}",
rec_calls,
term
);
let inferred =
infer_type(&ctx, &term).expect("the certified Tree eliminator must type-check");
assert!(
matches!(inferred, Term::Pi { .. }),
"expected ∀t:Tree. P(t), got {}",
inferred
);
}
#[test]
fn induction_missing_a_case_fails_kernel_coverage() {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx.add_inductive("Light", Term::Sort(Universe::Type(0)));
for c in ["Red", "Yellow", "Green"] {
ctx.add_constructor(c, "Light", ind("Light"));
}
ctx.add_declaration("P", pi("_", ind("Light"), prop()));
ctx.add_declaration("hRed", app(ind("P"), ind("Red")));
ctx.add_declaration("hYellow", app(ind("P"), ind("Yellow")));
let tree = DerivationTree::new(
forall("c", pred("P", pvar("c"))),
InferenceRule::InductionScheme {
variable: "c".to_string(),
ind_type: "Light".to_string(),
cases: vec![
InductionCase { constructor: "Red".to_string(), args: vec![] },
InductionCase { constructor: "Yellow".to_string(), args: vec![] },
],
},
vec![named("hRed"), named("hYellow")],
);
let cert_ctx = CertificationContext::new(&ctx);
let term = certify(&tree, &cert_ctx).expect("the term itself builds");
assert!(
infer_type(&ctx, &term).is_err(),
"a Match missing the Green case must fail the kernel's coverage check"
);
}