use logicaffeine_kernel::{Context, KernelError, KernelResult, Term};
use crate::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
struct InductionState {
fix_name: String,
ihs: Vec<(String, ProofExpr)>,
}
pub struct CertificationContext<'a> {
kernel_ctx: &'a Context,
locals: Vec<String>,
local_hyps: Vec<std::sync::Arc<(ProofExpr, Term)>>,
induction_state: Option<InductionState>,
}
impl<'a> CertificationContext<'a> {
pub fn new(kernel_ctx: &'a Context) -> Self {
Self {
kernel_ctx,
locals: Vec::new(),
local_hyps: Vec::new(),
induction_state: None,
}
}
fn with_local(&self, name: &str) -> Self {
let mut new_locals = self.locals.clone();
new_locals.push(name.to_string());
Self {
kernel_ctx: self.kernel_ctx,
locals: new_locals,
local_hyps: self.local_hyps.clone(),
induction_state: self.induction_state.clone(),
}
}
fn with_local_hyp(&self, prop: &ProofExpr, var: &str) -> Self {
self.with_local_hyp_term(prop, Term::Var(var.to_string()))
}
fn with_local_hyp_term(&self, prop: &ProofExpr, witness: Term) -> Self {
let mut new_hyps = self.local_hyps.clone();
new_hyps.push(std::sync::Arc::new((prop.clone(), witness)));
Self {
kernel_ctx: self.kernel_ctx,
locals: self.locals.clone(),
local_hyps: new_hyps,
induction_state: self.induction_state.clone(),
}
}
fn fresh_hyp_name(&self) -> String {
format!("_hyp{}", self.local_hyps.len())
}
fn get_local_hyp(&self, conclusion: &ProofExpr) -> Option<Term> {
self.local_hyps
.iter()
.rev()
.find(|h| h.0 == *conclusion)
.map(|h| h.1.clone())
}
fn with_induction(&self, fix_name: &str, step_var: &str, ih: ProofExpr) -> Self {
self.with_induction_multi(fix_name, vec![(step_var.to_string(), ih)])
}
fn with_induction_multi(&self, fix_name: &str, ihs: Vec<(String, ProofExpr)>) -> Self {
Self {
kernel_ctx: self.kernel_ctx,
locals: self.locals.clone(),
local_hyps: self.local_hyps.clone(),
induction_state: Some(InductionState {
fix_name: fix_name.to_string(),
ihs,
}),
}
}
fn is_local(&self, name: &str) -> bool {
self.locals.iter().any(|n| n == name)
}
fn get_ih_term(&self, conclusion: &ProofExpr) -> Option<Term> {
let state = self.induction_state.as_ref()?;
for (var, ih) in &state.ihs {
if conclusions_match(conclusion, ih) {
return Some(Term::App(
Box::new(Term::Var(state.fix_name.clone())),
Box::new(Term::Var(var.clone())),
));
}
}
None
}
}
impl Clone for InductionState {
fn clone(&self) -> Self {
Self {
fix_name: self.fix_name.clone(),
ihs: self.ihs.clone(),
}
}
}
fn conclusions_match(a: &ProofExpr, b: &ProofExpr) -> bool {
a == b
}
pub fn certify(tree: &DerivationTree, ctx: &CertificationContext) -> KernelResult<Term> {
match &tree.rule {
InferenceRule::Axiom | InferenceRule::PremiseMatch => {
certify_hypothesis(&tree.conclusion, ctx)
}
InferenceRule::ModusPonens => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ModusPonens requires exactly 2 premises".to_string(),
));
}
let impl_term = certify(&tree.premises[0], ctx)?;
let arg_term = certify(&tree.premises[1], ctx)?;
Ok(Term::App(Box::new(impl_term), Box::new(arg_term)))
}
InferenceRule::ModusTollens => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ModusTollens requires exactly 2 premises (implication, negated consequent)"
.to_string(),
));
}
let p = match &tree.conclusion {
ProofExpr::Not(p) => p.as_ref().clone(),
_ => {
return Err(KernelError::CertificationError(
"ModusTollens conclusion must be a negation".to_string(),
))
}
};
let p_type = proof_expr_to_type(&p)?;
let impl_proof = certify(&tree.premises[0], ctx)?;
let neg_q_proof = certify(&tree.premises[1], ctx)?;
let hp = "__mt_hp".to_string();
Ok(Term::Lambda {
param: hp.clone(),
param_type: Box::new(p_type),
body: Box::new(Term::App(
Box::new(neg_q_proof),
Box::new(Term::App(
Box::new(impl_proof),
Box::new(Term::Var(hp)),
)),
)),
})
}
InferenceRule::Reflexivity => {
let (l, r) = match &tree.conclusion {
ProofExpr::Identity(l, r) => (l, r),
_ => {
return Err(KernelError::CertificationError(
"Reflexivity conclusion must be an Identity".to_string(),
))
}
};
let domain = Term::Global(identity_domain(l, r).to_string());
let term = proof_term_to_kernel_term(l)?;
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("refl".to_string())),
Box::new(domain),
)),
Box::new(term),
))
}
InferenceRule::ExFalso => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"ExFalso requires exactly 1 premise (a proof of False)".to_string(),
));
}
let false_proof = certify(&tree.premises[0], ctx)?;
let goal_type = proof_expr_to_type(&tree.conclusion)?;
Ok(Term::Match {
discriminant: Box::new(false_proof),
motive: Box::new(Term::Lambda {
param: "_".to_string(),
param_type: Box::new(Term::Global("False".to_string())),
body: Box::new(goal_type),
}),
cases: vec![],
})
}
InferenceRule::ConjunctionIntro => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ConjunctionIntro requires exactly 2 premises".to_string(),
));
}
let (p_type, q_type) = extract_and_types(&tree.conclusion)?;
let p_term = certify(&tree.premises[0], ctx)?;
let q_term = certify(&tree.premises[1], ctx)?;
let conj = Term::Global("conj".to_string());
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(conj), Box::new(p_type))),
Box::new(q_type),
)),
Box::new(p_term),
)),
Box::new(q_term),
);
Ok(applied)
}
InferenceRule::DisjunctionIntro => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"DisjunctionIntro requires exactly 1 premise".to_string(),
));
}
let (p, q) = match &tree.conclusion {
ProofExpr::Or(p, q) => (p.as_ref().clone(), q.as_ref().clone()),
_ => {
return Err(KernelError::CertificationError(
"DisjunctionIntro conclusion must be Or".to_string(),
))
}
};
let proved = &tree.premises[0].conclusion;
let ctor = if proved == &p {
"left"
} else if proved == &q {
"right"
} else {
return Err(KernelError::CertificationError(
"DisjunctionIntro premise proves neither disjunct".to_string(),
));
};
let p_type = proof_expr_to_type(&p)?;
let q_type = proof_expr_to_type(&q)?;
let proof_term = certify(&tree.premises[0], ctx)?;
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::Global(ctor.to_string())),
Box::new(p_type),
)),
Box::new(q_type),
)),
Box::new(proof_term),
);
Ok(applied)
}
InferenceRule::UniversalInst(witness) => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"UniversalInst requires exactly 1 premise".to_string(),
));
}
let forall_proof = certify(&tree.premises[0], ctx)?;
let witness_term = if ctx.is_local(witness) {
Term::Var(witness.clone())
} else {
Term::Global(witness.clone())
};
Ok(Term::App(Box::new(forall_proof), Box::new(witness_term)))
}
InferenceRule::UniversalInstTerm(witness) => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"UniversalInstTerm requires exactly 1 premise".to_string(),
));
}
let forall_proof = certify(&tree.premises[0], ctx)?;
let witness_term = match witness {
ProofTerm::Constant(name) | ProofTerm::Variable(name)
if ctx.is_local(name) =>
{
Term::Var(name.clone())
}
_ => proof_term_to_kernel_term(witness)?,
};
Ok(Term::App(Box::new(forall_proof), Box::new(witness_term)))
}
InferenceRule::UniversalIntro { variable, var_type } => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"UniversalIntro requires exactly 1 premise".to_string(),
));
}
let type_term = Term::Global(var_type.clone());
let extended_ctx = ctx.with_local(variable);
let body_term = certify(&tree.premises[0], &extended_ctx)?;
Ok(Term::Lambda {
param: variable.clone(),
param_type: Box::new(type_term),
body: Box::new(body_term),
})
}
InferenceRule::StructuralInduction {
variable: var_name,
ind_type,
step_var,
} => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"StructuralInduction requires exactly 2 premises (base, step)".to_string(),
));
}
let motive_body = extract_motive_body(&tree.conclusion, var_name)?;
let fix_name = format!("rec_{}", var_name);
let base_term = certify(&tree.premises[0], ctx)?;
let ih_conclusion = compute_ih_conclusion(&tree.conclusion, var_name, step_var)?;
let step_ctx = ctx
.with_local(step_var)
.with_induction(&fix_name, step_var, ih_conclusion);
let step_body = certify(&tree.premises[1], &step_ctx)?;
let step_term = Term::Lambda {
param: step_var.clone(),
param_type: Box::new(Term::Global(ind_type.clone())),
body: Box::new(step_body),
};
let match_term = Term::Match {
discriminant: Box::new(Term::Var(var_name.clone())),
motive: Box::new(build_motive(ind_type, &motive_body, var_name)),
cases: vec![base_term, step_term],
};
let lambda_term = Term::Lambda {
param: var_name.clone(),
param_type: Box::new(Term::Global(ind_type.clone())),
body: Box::new(match_term),
};
Ok(Term::Fix {
name: fix_name,
body: Box::new(lambda_term),
})
}
InferenceRule::InductionScheme {
variable,
ind_type,
cases,
} => {
if tree.premises.len() != cases.len() {
return Err(KernelError::CertificationError(format!(
"InductionScheme requires one premise per constructor: {} cases, {} premises",
cases.len(),
tree.premises.len()
)));
}
let motive_body = extract_motive_body(&tree.conclusion, variable)?;
let fix_name = format!("rec_{}", variable);
let mut case_terms = Vec::with_capacity(cases.len());
for (case, premise) in cases.iter().zip(tree.premises.iter()) {
let arg_types = constructor_arg_types(ctx.kernel_ctx, &case.constructor)?;
if arg_types.len() != case.args.len() {
return Err(KernelError::CertificationError(format!(
"constructor {} takes {} arguments, case binds {}",
case.constructor,
arg_types.len(),
case.args.len()
)));
}
let mut ihs = Vec::new();
for arg in &case.args {
if arg.recursive {
let ih = compute_ih_conclusion(&tree.conclusion, variable, &arg.name)?;
ihs.push((arg.name.clone(), ih));
}
}
let mut case_ctx = ctx.with_induction_multi(&fix_name, ihs);
for arg in &case.args {
case_ctx = case_ctx.with_local(&arg.name);
}
let case_body = certify(premise, &case_ctx)?;
let mut term = case_body;
for (arg, ty) in case.args.iter().zip(arg_types.iter()).rev() {
term = Term::Lambda {
param: arg.name.clone(),
param_type: Box::new(ty.clone()),
body: Box::new(term),
};
}
case_terms.push(term);
}
let match_term = Term::Match {
discriminant: Box::new(Term::Var(variable.clone())),
motive: Box::new(build_motive(ind_type, &motive_body, variable)),
cases: case_terms,
};
let lambda_term = Term::Lambda {
param: variable.clone(),
param_type: Box::new(Term::Global(ind_type.clone())),
body: Box::new(match_term),
};
Ok(Term::Fix {
name: fix_name,
body: Box::new(lambda_term),
})
}
InferenceRule::ExistentialIntro {
witness: witness_str,
witness_type,
} => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"ExistentialIntro requires exactly 1 premise".to_string(),
));
}
let (variable, body) = match &tree.conclusion {
ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref().clone()),
_ => {
return Err(KernelError::CertificationError(
"ExistentialIntro conclusion must be Exists".to_string(),
))
}
};
let witness_term = if ctx.is_local(witness_str) {
Term::Var(witness_str.clone())
} else {
Term::Global(witness_str.clone())
};
let proof_term = certify(&tree.premises[0], ctx)?;
let type_a = Term::Global(witness_type.clone());
let predicate = Term::Lambda {
param: variable.clone(),
param_type: Box::new(type_a.clone()),
body: Box::new(proof_expr_to_type(&body)?),
};
let witness_ctor = Term::Global("witness".to_string());
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(witness_ctor), Box::new(type_a))),
Box::new(predicate),
)),
Box::new(witness_term),
)),
Box::new(proof_term),
);
Ok(applied)
}
InferenceRule::ExistentialElim { witness } => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ExistentialElim requires exactly 2 premises (existential, body)".to_string(),
));
}
let exist_premise = &tree.premises[0];
let body = &tree.premises[1];
let (var, p_body) = match &exist_premise.conclusion {
ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref().clone()),
_ => {
return Err(KernelError::CertificationError(
"ExistentialElim first premise must conclude an Exists".to_string(),
))
}
};
let mut subst = crate::unify::Substitution::new();
subst.insert(var.clone(), ProofTerm::Constant(witness.clone()));
let p_c_expr = crate::unify::apply_subst_to_expr(&p_body, &subst);
let disc = certify(exist_premise, ctx)?;
let h = format!("_exh_{}", witness);
let h_var = Term::Var(h.clone());
let mut body_ctx = ctx.with_local_hyp_term(&p_c_expr, h_var.clone());
for (conjunct, proj) in collect_conjunct_hyps(&p_c_expr, &h_var)? {
body_ctx = body_ctx.with_local_hyp_term(&conjunct, proj);
}
let body_raw = certify(body, &body_ctx)?;
let c_global = Term::Global(witness.clone());
let c_var = Term::Var(witness.clone());
let body_term = substitute_term_in_kernel(&body_raw, &c_global, &c_var);
let p_c_type =
substitute_term_in_kernel(&proof_expr_to_type(&p_c_expr)?, &c_global, &c_var);
let motive = proof_expr_to_type(&tree.conclusion)?;
let case = Term::Lambda {
param: witness.clone(),
param_type: Box::new(Term::Global("Entity".to_string())),
body: Box::new(Term::Lambda {
param: h,
param_type: Box::new(p_c_type),
body: Box::new(body_term),
}),
};
Ok(Term::Match {
discriminant: Box::new(disc),
motive: Box::new(motive),
cases: vec![case],
})
}
InferenceRule::Rewrite { from, to } => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"Rewrite requires exactly 2 premises (equality, source)".to_string(),
));
}
let eq_proof = certify(&tree.premises[0], ctx)?;
let source_proof = certify(&tree.premises[1], ctx)?;
let from_term = proof_term_to_kernel_term(from)?;
let to_term = proof_term_to_kernel_term(to)?;
let predicate = build_equality_predicate(&tree.conclusion, to)?;
let eq_rec = Term::Global("Eq_rec".to_string());
let domain = Term::Global(term_domain(from).to_string());
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(eq_rec), Box::new(domain))),
Box::new(from_term),
)),
Box::new(predicate),
)),
Box::new(source_proof),
)),
Box::new(to_term),
)),
Box::new(eq_proof),
);
Ok(applied)
}
InferenceRule::EqualitySymmetry => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"EqualitySymmetry requires exactly 1 premise".to_string(),
));
}
let premise_proof = certify(&tree.premises[0], ctx)?;
let (x, y, domain) = match &tree.premises[0].conclusion {
ProofExpr::Identity(l, r) => (
proof_term_to_kernel_term(l)?,
proof_term_to_kernel_term(r)?,
Term::Global(identity_domain(l, r).to_string()),
),
_ => {
return Err(KernelError::CertificationError(
"EqualitySymmetry premise must be an Identity".to_string(),
))
}
};
let eq_sym = Term::Global("Eq_sym".to_string());
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(eq_sym), Box::new(domain))),
Box::new(x),
)),
Box::new(y),
)),
Box::new(premise_proof),
))
}
InferenceRule::EqualityTransitivity => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"EqualityTransitivity requires exactly 2 premises".to_string(),
));
}
let proof1 = certify(&tree.premises[0], ctx)?;
let proof2 = certify(&tree.premises[1], ctx)?;
let (x, y, domain) = match &tree.premises[0].conclusion {
ProofExpr::Identity(l, r) => (
proof_term_to_kernel_term(l)?,
proof_term_to_kernel_term(r)?,
Term::Global(identity_domain(l, r).to_string()),
),
_ => {
return Err(KernelError::CertificationError(
"EqualityTransitivity first premise must be Identity".to_string(),
))
}
};
let z = match &tree.premises[1].conclusion {
ProofExpr::Identity(_, r) => proof_term_to_kernel_term(r)?,
_ => {
return Err(KernelError::CertificationError(
"EqualityTransitivity second premise must be Identity".to_string(),
))
}
};
let eq_trans = Term::Global("Eq_trans".to_string());
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(eq_trans), Box::new(domain))),
Box::new(x),
)),
Box::new(y),
)),
Box::new(z),
)),
Box::new(proof1),
)),
Box::new(proof2),
))
}
InferenceRule::LeTrans => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"LeTrans requires exactly 2 premises".to_string(),
));
}
let (a, b) = le_terms(&tree.conclusion)?;
let (_, mid) = le_terms(&tree.premises[0].conclusion)?;
let p0 = certify(&tree.premises[0], ctx)?;
let p1 = certify(&tree.premises[1], ctx)?;
let mut t = Term::Global("le_trans".to_string());
for arg in [
proof_term_to_kernel_term(&a)?,
proof_term_to_kernel_term(&mid)?,
proof_term_to_kernel_term(&b)?,
p0,
p1,
] {
t = Term::App(Box::new(t), Box::new(arg));
}
Ok(t)
}
InferenceRule::LeRefl => {
let (a, _) = le_terms(&tree.conclusion)?;
Ok(Term::App(
Box::new(Term::Global("le_refl".to_string())),
Box::new(proof_term_to_kernel_term(&a)?),
))
}
InferenceRule::LeAddMono => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"LeAddMono requires exactly 2 premises".to_string(),
));
}
let (lhs, rhs) = le_terms(&tree.conclusion)?;
let (a, c) = add_terms(&lhs)?;
let (b, d) = add_terms(&rhs)?;
let p0 = certify(&tree.premises[0], ctx)?;
let p1 = certify(&tree.premises[1], ctx)?;
let mut t = Term::Global("le_add_mono".to_string());
for arg in [
proof_term_to_kernel_term(&a)?,
proof_term_to_kernel_term(&b)?,
proof_term_to_kernel_term(&c)?,
proof_term_to_kernel_term(&d)?,
p0,
p1,
] {
t = Term::App(Box::new(t), Box::new(arg));
}
Ok(t)
}
InferenceRule::LinFalse => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"LinFalse requires exactly 1 premise".to_string(),
));
}
let le_proof = certify(&tree.premises[0], ctx)?;
Ok(build_bool_discriminator(le_proof))
}
InferenceRule::LeMulNonneg => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"LeMulNonneg requires exactly 2 premises".to_string(),
));
}
let (lhs, rhs) = le_terms(&tree.conclusion)?;
let (k, a) = binop_terms("mul", &lhs)?;
let (_, b) = binop_terms("mul", &rhs)?;
let p0 = certify(&tree.premises[0], ctx)?;
let p1 = certify(&tree.premises[1], ctx)?;
let mut t = Term::Global("le_mul_nonneg".to_string());
for arg in [
proof_term_to_kernel_term(&k)?,
proof_term_to_kernel_term(&a)?,
proof_term_to_kernel_term(&b)?,
p0,
p1,
] {
t = Term::App(Box::new(t), Box::new(arg));
}
Ok(t)
}
InferenceRule::LeSub => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"LeSub requires exactly 1 premise".to_string(),
));
}
let (_zero, diff) = le_terms(&tree.conclusion)?;
let (b, neg_a) = binop_terms("add", &diff)?;
let (_neg_one, a) = binop_terms("mul", &neg_a)?;
let p0 = certify(&tree.premises[0], ctx)?;
let mut t = Term::Global("le_sub".to_string());
for arg in [proof_term_to_kernel_term(&a)?, proof_term_to_kernel_term(&b)?, p0] {
t = Term::App(Box::new(t), Box::new(arg));
}
Ok(t)
}
InferenceRule::LtSuccLe => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"LtSuccLe requires exactly 1 premise".to_string(),
));
}
let (succ_a, b) = le_terms(&tree.conclusion)?;
let (a, _one) = binop_terms("add", &succ_a)?;
let p0 = certify(&tree.premises[0], ctx)?;
let mut t = Term::Global("lt_succ_le".to_string());
for arg in [proof_term_to_kernel_term(&a)?, proof_term_to_kernel_term(&b)?, p0] {
t = Term::App(Box::new(t), Box::new(arg));
}
Ok(t)
}
InferenceRule::LtAdd1Le => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"LtAdd1Le requires exactly 1 premise".to_string(),
));
}
let (a, b) = le_terms(&tree.conclusion)?;
let p0 = certify(&tree.premises[0], ctx)?;
let mut t = Term::Global("lt_add1_le".to_string());
for arg in [proof_term_to_kernel_term(&a)?, proof_term_to_kernel_term(&b)?, p0] {
t = Term::App(Box::new(t), Box::new(arg));
}
Ok(t)
}
InferenceRule::ArithDecision => match &tree.conclusion {
ProofExpr::Identity(l, r) => {
let kl = proof_term_to_kernel_term(l)?;
let kr = proof_term_to_kernel_term(r)?;
crate::arith::prove_int_eq(ctx.kernel_ctx, &kl, &kr).ok_or_else(|| {
KernelError::CertificationError(
"ArithDecision: arithmetic oracle found no proof".to_string(),
)
})
}
_ => Err(KernelError::CertificationError(
"ArithDecision conclusion must be an Identity".to_string(),
)),
},
InferenceRule::NativeDecide => {
if !tree.premises.is_empty() {
return Err(KernelError::CertificationError(
"NativeDecide is a leaf (no premises)".to_string(),
));
}
let prop = proof_expr_to_type_ctx(&tree.conclusion, ctx.kernel_ctx)?;
let inst = decidable_instance_for(&prop).ok_or_else(|| {
KernelError::CertificationError(
"NativeDecide: no Decidable instance for this proposition".to_string(),
)
})?;
logicaffeine_kernel::native_decide(ctx.kernel_ctx, &prop, &inst).ok_or_else(|| {
KernelError::CertificationError(
"NativeDecide: evaluation did not decide the goal true".to_string(),
)
})
}
InferenceRule::Contradiction => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"Contradiction requires exactly 2 premises (P and ¬P)".to_string(),
));
}
let a = &tree.premises[0];
let b = &tree.premises[1];
let (pos, neg) = if is_negation_of(&b.conclusion, &a.conclusion) {
(a, b)
} else if is_negation_of(&a.conclusion, &b.conclusion) {
(b, a)
} else {
return Err(KernelError::CertificationError(format!(
"Contradiction premises are not a proposition and its negation: {:?} vs {:?}",
a.conclusion, b.conclusion
)));
};
let pos_term = certify(pos, ctx)?;
let neg_term = certify(neg, ctx)?;
Ok(Term::App(Box::new(neg_term), Box::new(pos_term)))
}
InferenceRule::ReductioAdAbsurdum => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ReductioAdAbsurdum requires exactly 2 premises (assumption, ⊥-derivation)"
.to_string(),
));
}
let assumed = &tree.premises[0].conclusion;
let contradiction = &tree.premises[1];
let hyp_name = ctx.fresh_hyp_name();
let branch_ctx = ctx.with_local_hyp(assumed, &hyp_name);
let body = certify(contradiction, &branch_ctx)?;
Ok(Term::Lambda {
param: hyp_name,
param_type: Box::new(proof_expr_to_type(assumed)?),
body: Box::new(body),
})
}
InferenceRule::CaseAnalysis { case_formula } => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"CaseAnalysis requires exactly 2 premises (C-branch, ¬C-branch)".to_string(),
));
}
let c = case_formula.as_ref();
let not_c = ProofExpr::Not(Box::new(c.clone()));
let branch_c = unwrap_case_branch(&tree.premises[0]);
let branch_nc = unwrap_case_branch(&tree.premises[1]);
let h1 = ctx.fresh_hyp_name();
let ctx_c = ctx.with_local_hyp(c, &h1);
let neg_c = Term::Lambda {
param: h1,
param_type: Box::new(proof_expr_to_type(c)?),
body: Box::new(certify(branch_c, &ctx_c)?),
};
let h2 = ctx_c.fresh_hyp_name();
let ctx_nc = ctx.with_local_hyp(¬_c, &h2);
let neg_neg_c = Term::Lambda {
param: h2,
param_type: Box::new(proof_expr_to_type(¬_c)?),
body: Box::new(certify(branch_nc, &ctx_nc)?),
};
Ok(Term::App(Box::new(neg_neg_c), Box::new(neg_c)))
}
InferenceRule::DisjunctionElim => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"DisjunctionElim requires exactly 2 premises (disjunction, negation)"
.to_string(),
));
}
let disj_premise = &tree.premises[0];
let neg_premise = &tree.premises[1];
let (left, right) = match &disj_premise.conclusion {
ProofExpr::Or(l, r) => (l.as_ref().clone(), r.as_ref().clone()),
other => {
return Err(KernelError::CertificationError(format!(
"DisjunctionElim first premise must conclude a disjunction, got {:?}",
other
)))
}
};
let refuted = match &neg_premise.conclusion {
ProofExpr::Not(inner) => inner.as_ref().clone(),
other => {
return Err(KernelError::CertificationError(format!(
"DisjunctionElim second premise must conclude a negation, got {:?}",
other
)))
}
};
let left_is_refuted = conclusions_match(&refuted, &left);
let right_is_refuted = conclusions_match(&refuted, &right);
if left_is_refuted == right_is_refuted {
return Err(KernelError::CertificationError(format!(
"DisjunctionElim negation {:?} matches neither (or both) disjuncts of {:?}",
refuted, disj_premise.conclusion
)));
}
let goal_type = proof_expr_to_type(&tree.conclusion)?;
let disc = certify(disj_premise, ctx)?;
let neg_term = certify(neg_premise, ctx)?;
let motive = goal_type.clone();
let return_case = |disjunct: &ProofExpr, binder: &str| -> KernelResult<Term> {
Ok(Term::Lambda {
param: binder.to_string(),
param_type: Box::new(proof_expr_to_type(disjunct)?),
body: Box::new(Term::Var(binder.to_string())),
})
};
let absurd_case = |disjunct: &ProofExpr, binder: &str| -> KernelResult<Term> {
let falsum = Term::App(
Box::new(neg_term.clone()),
Box::new(Term::Var(binder.to_string())),
);
let ex_falso = Term::Match {
discriminant: Box::new(falsum),
motive: Box::new(Term::Lambda {
param: "_".to_string(),
param_type: Box::new(Term::Global("False".to_string())),
body: Box::new(goal_type.clone()),
}),
cases: vec![],
};
Ok(Term::Lambda {
param: binder.to_string(),
param_type: Box::new(proof_expr_to_type(disjunct)?),
body: Box::new(ex_falso),
})
};
let left_case = if left_is_refuted {
absurd_case(&left, "__dl")?
} else {
return_case(&left, "__dl")?
};
let right_case = if right_is_refuted {
absurd_case(&right, "__dr")?
} else {
return_case(&right, "__dr")?
};
Ok(Term::Match {
discriminant: Box::new(disc),
motive: Box::new(motive),
cases: vec![left_case, right_case],
})
}
InferenceRule::DisjunctionCases => {
if tree.premises.len() != 3 {
return Err(KernelError::CertificationError(
"DisjunctionCases requires exactly 3 premises (disjunction, A-branch, B-branch)"
.to_string(),
));
}
let disj_premise = &tree.premises[0];
let (left, right) = match &disj_premise.conclusion {
ProofExpr::Or(l, r) => (l.as_ref().clone(), r.as_ref().clone()),
other => {
return Err(KernelError::CertificationError(format!(
"DisjunctionCases first premise must conclude a disjunction, got {:?}",
other
)))
}
};
let disc = certify(disj_premise, ctx)?;
let motive = proof_expr_to_type(&tree.conclusion)?;
let build_arm = |disjunct: &ProofExpr,
branch: &DerivationTree,
binder: &str|
-> KernelResult<Term> {
let h_var = Term::Var(binder.to_string());
let mut bctx = ctx.with_local_hyp_term(disjunct, h_var.clone());
for (conjunct, proj) in collect_conjunct_hyps(disjunct, &h_var)? {
bctx = bctx.with_local_hyp_term(&conjunct, proj);
}
let body = certify(branch, &bctx)?;
Ok(Term::Lambda {
param: binder.to_string(),
param_type: Box::new(proof_expr_to_type(disjunct)?),
body: Box::new(body),
})
};
let lname = ctx.fresh_hyp_name();
let rname = ctx.fresh_hyp_name();
let left_case = build_arm(&left, &tree.premises[1], &lname)?;
let right_case = build_arm(&right, &tree.premises[2], &rname)?;
Ok(Term::Match {
discriminant: Box::new(disc),
motive: Box::new(motive),
cases: vec![left_case, right_case],
})
}
InferenceRule::BicondIntro => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"BicondIntro requires exactly 2 premises (P→Q, Q→P)".to_string(),
));
}
let (p, q) = match &tree.conclusion {
ProofExpr::Iff(p, q) => (p.as_ref().clone(), q.as_ref().clone()),
other => {
return Err(KernelError::CertificationError(format!(
"BicondIntro conclusion must be a biconditional, got {:?}",
other
)))
}
};
let pq_type =
proof_expr_to_type(&ProofExpr::Implies(Box::new(p.clone()), Box::new(q.clone())))?;
let qp_type =
proof_expr_to_type(&ProofExpr::Implies(Box::new(q), Box::new(p)))?;
let pq_proof = certify(&tree.premises[0], ctx)?;
let qp_proof = certify(&tree.premises[1], ctx)?;
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::Global("conj".to_string())),
Box::new(pq_type),
)),
Box::new(qp_type),
)),
Box::new(pq_proof),
)),
Box::new(qp_proof),
))
}
InferenceRule::DoubleNegation => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"DoubleNegation requires exactly 1 premise (a proof of P)".to_string(),
));
}
let p = match &tree.conclusion {
ProofExpr::Not(inner) => match inner.as_ref() {
ProofExpr::Not(core) => core.as_ref().clone(),
other => {
return Err(KernelError::CertificationError(format!(
"DoubleNegation conclusion must be ¬¬P, got ¬{:?}",
other
)))
}
},
other => {
return Err(KernelError::CertificationError(format!(
"DoubleNegation conclusion must be ¬¬P, got {:?}",
other
)))
}
};
let p_type = proof_expr_to_type(&p)?;
let p_proof = certify(&tree.premises[0], ctx)?;
let not_p_type = Term::Pi {
param: "_".to_string(),
param_type: Box::new(p_type),
body_type: Box::new(Term::Global("False".to_string())),
};
let hnp = "__dn_hnp".to_string();
Ok(Term::Lambda {
param: hnp.clone(),
param_type: Box::new(not_p_type),
body: Box::new(Term::App(Box::new(Term::Var(hnp)), Box::new(p_proof))),
})
}
InferenceRule::ClassicalReductio => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"ClassicalReductio requires exactly 1 premise (a proof of False)".to_string(),
));
}
let g = tree.conclusion.clone();
let g_type = proof_expr_to_type(&g)?;
let neg_g = ProofExpr::Not(Box::new(g));
let neg_g_type = proof_expr_to_type(&neg_g)?; let binder = ctx.fresh_hyp_name();
let bctx = ctx.with_local_hyp_term(&neg_g, Term::Var(binder.clone()));
let false_proof = certify(&tree.premises[0], &bctx)?;
let nn_g = Term::Lambda {
param: binder,
param_type: Box::new(neg_g_type),
body: Box::new(false_proof),
};
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("dne".to_string())),
Box::new(g_type),
)),
Box::new(nn_g),
))
}
InferenceRule::ImpliesIntro => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"ImpliesIntro requires exactly 1 premise (the consequent proof)".to_string(),
));
}
let ant = match &tree.conclusion {
ProofExpr::Implies(a, _) => a.as_ref().clone(),
other => {
return Err(KernelError::CertificationError(format!(
"ImpliesIntro conclusion must be an implication, got {:?}",
other
)))
}
};
let binder = ctx.fresh_hyp_name();
let h_var = Term::Var(binder.clone());
let mut bctx = ctx.with_local_hyp_term(&ant, h_var.clone());
for (conjunct, proj) in collect_conjunct_hyps(&ant, &h_var)? {
bctx = bctx.with_local_hyp_term(&conjunct, proj);
}
let body = certify(&tree.premises[0], &bctx)?;
Ok(Term::Lambda {
param: binder,
param_type: Box::new(proof_expr_to_type(&ant)?),
body: Box::new(body),
})
}
InferenceRule::ConjunctionElim => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"ConjunctionElim requires exactly 1 premise (the conjunction)".to_string(),
));
}
let conj_premise = &tree.premises[0];
let (a, b) = match &conj_premise.conclusion {
ProofExpr::And(l, r) => (l.as_ref().clone(), r.as_ref().clone()),
ProofExpr::Iff(l, r) => (
ProofExpr::Implies(l.clone(), r.clone()),
ProofExpr::Implies(r.clone(), l.clone()),
),
other => {
return Err(KernelError::CertificationError(format!(
"ConjunctionElim premise must conclude a conjunction, got {:?}",
other
)))
}
};
let take_left = conclusions_match(&tree.conclusion, &a);
if !take_left && !conclusions_match(&tree.conclusion, &b) {
return Err(KernelError::CertificationError(format!(
"ConjunctionElim conclusion {:?} matches neither conjunct of {:?}",
tree.conclusion, conj_premise.conclusion
)));
}
let disc = certify(conj_premise, ctx)?;
let left_type = proof_expr_to_type(&a)?;
let right_type = proof_expr_to_type(&b)?;
Ok(project_conjunct(&disc, &left_type, &right_type, take_left))
}
InferenceRule::OracleVerification(detail) => Err(KernelError::CertificationError(format!(
"oracle (Z3) results are not kernel-certifiable — they attest \
satisfiability but produce no checkable proof term ({})",
detail
))),
rule => Err(KernelError::CertificationError(format!(
"Certification not implemented for {:?}",
rule
))),
}
}
fn is_negation_of(neg: &ProofExpr, pos: &ProofExpr) -> bool {
matches!(neg, ProofExpr::Not(inner) if inner.as_ref() == pos)
}
fn unwrap_case_branch(tree: &DerivationTree) -> &DerivationTree {
if matches!(tree.rule, InferenceRule::PremiseMatch) && tree.premises.len() == 1 {
&tree.premises[0]
} else {
tree
}
}
fn build_equality_predicate(goal: &ProofExpr, replace_term: &ProofTerm) -> KernelResult<Term> {
if let ProofExpr::Predicate { name, args, .. } = goal {
if args.len() == 1 && &args[0] == replace_term {
return Ok(Term::Global(name.clone()));
}
}
let goal_type = proof_expr_to_type(goal)?;
let param_name = "_eq_var".to_string();
let substituted = substitute_term_in_kernel(
&goal_type,
&proof_term_to_kernel_term(replace_term)?,
&Term::Var(param_name.clone()),
);
Ok(Term::Lambda {
param: param_name,
param_type: Box::new(Term::Global(term_domain(replace_term).to_string())),
body: Box::new(substituted),
})
}
fn substitute_term_in_kernel(term: &Term, from: &Term, to: &Term) -> Term {
if term == from {
return to.clone();
}
match term {
Term::App(f, a) => Term::App(
Box::new(substitute_term_in_kernel(f, from, to)),
Box::new(substitute_term_in_kernel(a, from, to)),
),
Term::Pi {
param,
param_type,
body_type,
} => Term::Pi {
param: param.clone(),
param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
body_type: Box::new(substitute_term_in_kernel(body_type, from, to)),
},
Term::Lambda {
param,
param_type,
body,
} => Term::Lambda {
param: param.clone(),
param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
body: Box::new(substitute_term_in_kernel(body, from, to)),
},
Term::Match {
discriminant,
motive,
cases,
} => Term::Match {
discriminant: Box::new(substitute_term_in_kernel(discriminant, from, to)),
motive: Box::new(substitute_term_in_kernel(motive, from, to)),
cases: cases
.iter()
.map(|c| substitute_term_in_kernel(c, from, to))
.collect(),
},
Term::Fix { name, body } => Term::Fix {
name: name.clone(),
body: Box::new(substitute_term_in_kernel(body, from, to)),
},
other => other.clone(),
}
}
fn project_conjunct(
disc: &Term,
left_type: &Term,
right_type: &Term,
take_left: bool,
) -> Term {
let chosen = if take_left { left_type } else { right_type };
let result = if take_left { "__l" } else { "__r" };
let case = Term::Lambda {
param: "__l".to_string(),
param_type: Box::new(left_type.clone()),
body: Box::new(Term::Lambda {
param: "__r".to_string(),
param_type: Box::new(right_type.clone()),
body: Box::new(Term::Var(result.to_string())),
}),
};
Term::Match {
discriminant: Box::new(disc.clone()),
motive: Box::new(chosen.clone()),
cases: vec![case],
}
}
fn collect_conjunct_hyps(expr: &ProofExpr, witness: &Term) -> KernelResult<Vec<(ProofExpr, Term)>> {
match expr {
ProofExpr::And(l, r) => {
let left_type = proof_expr_to_type(l)?;
let right_type = proof_expr_to_type(r)?;
let left_proj = project_conjunct(witness, &left_type, &right_type, true);
let right_proj = project_conjunct(witness, &left_type, &right_type, false);
let mut hyps = collect_conjunct_hyps(l, &left_proj)?;
hyps.extend(collect_conjunct_hyps(r, &right_proj)?);
Ok(hyps)
}
_ => Ok(vec![(expr.clone(), witness.clone())]),
}
}
fn certify_hypothesis(conclusion: &ProofExpr, ctx: &CertificationContext) -> KernelResult<Term> {
if let Some(ih_term) = ctx.get_ih_term(conclusion) {
return Ok(ih_term);
}
if let Some(hyp) = ctx.get_local_hyp(conclusion) {
return Ok(hyp);
}
match conclusion {
ProofExpr::Atom(name) => {
if ctx.is_local(name) {
return Ok(Term::Var(name.clone()));
}
if ctx.kernel_ctx.get_global(name).is_some() {
Ok(Term::Global(name.clone()))
} else {
Err(KernelError::CertificationError(format!(
"Unknown hypothesis: {}",
name
)))
}
}
ProofExpr::Predicate { name, args, .. } => {
let mut target_type = Term::Global(name.clone());
for arg in args {
let arg_term = proof_term_to_kernel_term_entity(arg)?;
target_type = Term::App(Box::new(target_type), Box::new(arg_term));
}
for (decl_name, decl_type) in ctx.kernel_ctx.iter_declarations() {
if types_structurally_match(&target_type, decl_type) {
return Ok(Term::Global(decl_name.to_string()));
}
}
Err(KernelError::CertificationError(format!(
"Cannot find hypothesis with type: {:?}",
conclusion
)))
}
_ => {
let target_type = proof_expr_to_type_ctx(conclusion, ctx.kernel_ctx).map_err(|_| {
KernelError::CertificationError(format!(
"Cannot certify hypothesis: {:?}",
conclusion
))
})?;
for (name, decl_type) in ctx.kernel_ctx.iter_declarations() {
if types_structurally_match(&target_type, decl_type) {
return Ok(Term::Global(name.to_string()));
}
}
Err(KernelError::CertificationError(format!(
"Cannot find hypothesis with type matching: {:?}",
conclusion
)))
}
}
}
fn types_structurally_match(a: &Term, b: &Term) -> bool {
types_alpha_equiv(a, b, &mut Vec::new())
}
fn types_alpha_equiv(a: &Term, b: &Term, bindings: &mut Vec<(String, String)>) -> bool {
match (a, b) {
(Term::Sort(u1), Term::Sort(u2)) => u1 == u2,
(Term::Var(v1), Term::Var(v2)) => {
for (bound_a, bound_b) in bindings.iter().rev() {
if v1 == bound_a {
return v2 == bound_b;
}
if v2 == bound_b {
return false; }
}
v1 == v2
}
(Term::Global(g1), Term::Global(g2)) => g1 == g2,
(Term::Lit(l1), Term::Lit(l2)) => l1 == l2,
(Term::App(f1, a1), Term::App(f2, a2)) => {
types_alpha_equiv(f1, f2, bindings) && types_alpha_equiv(a1, a2, bindings)
}
(
Term::Pi {
param: p1,
param_type: pt1,
body_type: bt1,
},
Term::Pi {
param: p2,
param_type: pt2,
body_type: bt2,
},
) => {
if !types_alpha_equiv(pt1, pt2, bindings) {
return false;
}
bindings.push((p1.clone(), p2.clone()));
let result = types_alpha_equiv(bt1, bt2, bindings);
bindings.pop();
result
}
(
Term::Lambda {
param: p1,
param_type: pt1,
body: b1,
},
Term::Lambda {
param: p2,
param_type: pt2,
body: b2,
},
) => {
if !types_alpha_equiv(pt1, pt2, bindings) {
return false;
}
bindings.push((p1.clone(), p2.clone()));
let result = types_alpha_equiv(b1, b2, bindings);
bindings.pop();
result
}
_ => false,
}
}
fn extract_and_types(conclusion: &ProofExpr) -> KernelResult<(Term, Term)> {
match conclusion {
ProofExpr::And(p, q) => {
let p_term = proof_expr_to_type(p)?;
let q_term = proof_expr_to_type(q)?;
Ok((p_term, q_term))
}
_ => Err(KernelError::CertificationError(format!(
"Expected And, got {:?}",
conclusion
))),
}
}
fn param_domain(ctx: &Context, name: &str, pos: usize) -> Option<String> {
let mut ty = ctx.get_global(name)?;
let mut i = 0;
while let Term::Pi { param_type, body_type, .. } = ty {
if i == pos {
return match param_type.as_ref() {
Term::Global(g) => Some(g.clone()),
_ => None,
};
}
ty = body_type;
i += 1;
}
None
}
fn collect_var_domains_term(t: &ProofTerm, variable: &str, ctx: &Context, out: &mut Vec<String>) {
match t {
ProofTerm::Function(name, args) => {
for (j, a) in args.iter().enumerate() {
if matches!(a, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == variable) {
if let Some(g) = param_domain(ctx, name, j) {
out.push(g);
}
}
collect_var_domains_term(a, variable, ctx, out);
}
}
ProofTerm::Group(args) => {
for a in args {
collect_var_domains_term(a, variable, ctx, out);
}
}
_ => {}
}
}
fn collect_var_domains_expr(e: &ProofExpr, variable: &str, ctx: &Context, out: &mut Vec<String>) {
match e {
ProofExpr::Predicate { name, args, .. } => {
for (j, a) in args.iter().enumerate() {
if matches!(a, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == variable) {
if let Some(g) = param_domain(ctx, name, j) {
out.push(g);
}
}
collect_var_domains_term(a, variable, ctx, out);
}
}
ProofExpr::Identity(l, r) => {
collect_var_domains_term(l, variable, ctx, out);
collect_var_domains_term(r, variable, ctx, out);
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_var_domains_expr(l, variable, ctx, out);
collect_var_domains_expr(r, variable, ctx, out);
}
ProofExpr::Not(p)
| ProofExpr::ForAll { body: p, .. }
| ProofExpr::Exists { body: p, .. } => collect_var_domains_expr(p, variable, ctx, out),
_ => {}
}
}
fn binder_domain(variable: &str, body: &ProofExpr, ctx: &Context) -> String {
let mut domains = Vec::new();
collect_var_domains_expr(body, variable, ctx, &mut domains);
domains
.into_iter()
.find(|d| d != "Entity")
.unwrap_or_else(|| "Entity".to_string())
}
pub(crate) fn proof_expr_to_type_ctx(expr: &ProofExpr, ctx: &Context) -> KernelResult<Term> {
if let ProofExpr::ForAll { variable, body } = expr {
let dom = binder_domain(variable, body, ctx);
let body_type = proof_expr_to_type_ctx(body, ctx)?;
return Ok(Term::Pi {
param: variable.clone(),
param_type: Box::new(Term::Global(dom)),
body_type: Box::new(body_type),
});
}
proof_expr_to_type(expr)
}
pub(crate) fn proof_expr_to_type(expr: &ProofExpr) -> KernelResult<Term> {
match expr {
ProofExpr::Atom(name) if name == "⊥" || name == "False" || name == "false" => {
Ok(Term::Global("False".to_string()))
}
ProofExpr::Atom(name) => Ok(Term::Global(name.clone())),
ProofExpr::Not(p) => Ok(Term::Pi {
param: "_".to_string(),
param_type: Box::new(proof_expr_to_type(p)?),
body_type: Box::new(Term::Global("False".to_string())),
}),
ProofExpr::And(p, q) => Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("And".to_string())),
Box::new(proof_expr_to_type(p)?),
)),
Box::new(proof_expr_to_type(q)?),
)),
ProofExpr::Or(p, q) => Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("Or".to_string())),
Box::new(proof_expr_to_type(p)?),
)),
Box::new(proof_expr_to_type(q)?),
)),
ProofExpr::Implies(p, q) => Ok(Term::Pi {
param: "_".to_string(),
param_type: Box::new(proof_expr_to_type(p)?),
body_type: Box::new(proof_expr_to_type(q)?),
}),
ProofExpr::Iff(p, q) => {
let pt = proof_expr_to_type(p)?;
let qt = proof_expr_to_type(q)?;
let pq = Term::Pi {
param: "_".to_string(),
param_type: Box::new(pt.clone()),
body_type: Box::new(qt.clone()),
};
let qp = Term::Pi {
param: "_".to_string(),
param_type: Box::new(qt),
body_type: Box::new(pt),
};
Ok(Term::App(
Box::new(Term::App(Box::new(Term::Global("And".to_string())), Box::new(pq))),
Box::new(qp),
))
}
ProofExpr::ForAll { variable, body } => {
let body_type = proof_expr_to_type(body)?;
Ok(Term::Pi {
param: variable.clone(),
param_type: Box::new(Term::Global("Entity".to_string())),
body_type: Box::new(body_type),
})
}
ProofExpr::Predicate { name, args, .. } => {
let mut result = Term::Global(name.clone());
for arg in args {
let arg_term = proof_term_to_kernel_term_entity(arg)?;
result = Term::App(Box::new(result), Box::new(arg_term));
}
Ok(result)
}
ProofExpr::Identity(l, r) => {
let l_term = proof_term_to_kernel_term(l)?;
let r_term = proof_term_to_kernel_term(r)?;
let domain = Term::Global(identity_domain(l, r).to_string());
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::Global("Eq".to_string())),
Box::new(domain),
)),
Box::new(l_term),
)),
Box::new(r_term),
))
}
ProofExpr::Exists { variable, body } => {
let var_type = Term::Global("Entity".to_string());
let body_type = proof_expr_to_type(body)?;
let predicate = Term::Lambda {
param: variable.clone(),
param_type: Box::new(var_type.clone()),
body: Box::new(body_type),
};
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("Ex".to_string())),
Box::new(var_type),
)),
Box::new(predicate),
))
}
ProofExpr::Temporal { operator, body } => Ok(Term::App(
Box::new(Term::Global(operator.clone())),
Box::new(proof_expr_to_type(body)?),
)),
_ => Err(KernelError::CertificationError(format!(
"Cannot convert {:?} to kernel type",
expr
))),
}
}
fn kernel_arith_name(name: &str) -> &str {
match name {
"Add" => "add",
"Sub" => "sub",
"Mul" => "mul",
"Div" => "div",
other => other,
}
}
fn is_arith_builtin(name: &str) -> bool {
matches!(
name,
"le" | "lt" | "ge" | "gt" | "add" | "sub" | "mul" | "div" | "mod" | "Add" | "Sub"
| "Mul" | "Div"
)
}
fn proof_term_to_kernel_term_entity(term: &ProofTerm) -> KernelResult<Term> {
match term {
ProofTerm::Constant(name) => Ok(Term::Global(name.clone())),
ProofTerm::Variable(name) | ProofTerm::BoundVarRef(name) => Ok(Term::Var(name.clone())),
ProofTerm::Function(name, args) => {
let arith = is_arith_builtin(name);
let mut result = Term::Global(kernel_arith_name(name).to_string());
for arg in args {
let arg_term = if arith {
proof_term_to_kernel_term(arg)?
} else {
proof_term_to_kernel_term_entity(arg)?
};
result = Term::App(Box::new(result), Box::new(arg_term));
}
Ok(result)
}
ProofTerm::Group(_) => Err(KernelError::CertificationError(
"Cannot convert Group to kernel term".to_string(),
)),
}
}
pub(crate) fn proof_term_to_kernel_term(term: &ProofTerm) -> KernelResult<Term> {
match term {
ProofTerm::Constant(name) => match name.parse::<i64>() {
Ok(n) => Ok(Term::Lit(logicaffeine_kernel::Literal::Int(n))),
Err(_) => Ok(Term::Global(name.clone())),
},
ProofTerm::Variable(name) => Ok(Term::Var(name.clone())),
ProofTerm::BoundVarRef(name) => Ok(Term::Var(name.clone())),
ProofTerm::Function(name, args) => {
let mut result = Term::Global(kernel_arith_name(name).to_string());
for arg in args {
let arg_term = proof_term_to_kernel_term(arg)?;
result = Term::App(Box::new(result), Box::new(arg_term));
}
Ok(result)
}
ProofTerm::Group(_) => Err(KernelError::CertificationError(
"Cannot convert Group to kernel term".to_string(),
)),
}
}
fn decidable_instance_for(prop: &Term) -> Option<Term> {
let Term::App(f1, b) = prop else { return None };
let Term::App(f2, a) = f1.as_ref() else { return None };
let Term::App(eq, dom) = f2.as_ref() else { return None };
let Term::Global(eq_name) = eq.as_ref() else { return None };
if eq_name != "Eq" {
return None;
}
let Term::Global(domain) = dom.as_ref() else { return None };
let inst = match domain.as_str() {
"Bool" => "decEqBool",
"Nat" => "decEqNat",
_ => return None,
};
Some(Term::App(
Box::new(Term::App(
Box::new(Term::Global(inst.to_string())),
a.clone(),
)),
b.clone(),
))
}
fn identity_domain(l: &ProofTerm, r: &ProofTerm) -> &'static str {
match (term_domain(l), term_domain(r)) {
("Bool", _) | (_, "Bool") => "Bool",
("Int", _) | (_, "Int") => "Int",
_ => "Entity",
}
}
fn term_domain(t: &ProofTerm) -> &'static str {
match t {
ProofTerm::Function(n, _) if matches!(n.as_str(), "le" | "lt" | "ge" | "gt") => "Bool",
ProofTerm::Function(n, _)
if matches!(
n.as_str(),
"add" | "sub" | "mul" | "div" | "mod" | "Add" | "Sub" | "Mul" | "Div"
) =>
{
"Int"
}
ProofTerm::Constant(s) if s == "true" || s == "false" => "Bool",
ProofTerm::Constant(s) if s.parse::<i64>().is_ok() => "Int",
_ => "Entity",
}
}
fn le_terms(expr: &ProofExpr) -> KernelResult<(ProofTerm, ProofTerm)> {
if let ProofExpr::Identity(lhs, _) = expr {
if let ProofTerm::Function(name, args) = lhs {
if name == "le" && args.len() == 2 {
return Ok((args[0].clone(), args[1].clone()));
}
}
}
Err(KernelError::CertificationError(format!(
"expected an `le(a, b) = true` inequality, got {:?}",
expr
)))
}
fn add_terms(t: &ProofTerm) -> KernelResult<(ProofTerm, ProofTerm)> {
binop_terms("add", t)
}
fn binop_terms(op: &str, t: &ProofTerm) -> KernelResult<(ProofTerm, ProofTerm)> {
if let ProofTerm::Function(name, args) = t {
if name == op && args.len() == 2 {
return Ok((args[0].clone(), args[1].clone()));
}
}
Err(KernelError::CertificationError(format!(
"expected `{}(x, y)`, got {:?}",
op, t
)))
}
fn build_bool_discriminator(h: Term) -> Term {
let g = |s: &str| Term::Global(s.to_string());
let motive = Term::Lambda {
param: "b".to_string(),
param_type: Box::new(g("Bool")),
body: Box::new(Term::Match {
discriminant: Box::new(Term::Var("b".to_string())),
motive: Box::new(Term::Lambda {
param: "_".to_string(),
param_type: Box::new(g("Bool")),
body: Box::new(Term::Sort(logicaffeine_kernel::Universe::Prop)),
}),
cases: vec![g("False"), g("True")],
}),
};
[g("Bool"), g("false"), motive, g("I"), g("true"), h]
.into_iter()
.fold(g("Eq_rec"), |acc, arg| Term::App(Box::new(acc), Box::new(arg)))
}
fn extract_motive_body(conclusion: &ProofExpr, var_name: &str) -> KernelResult<Term> {
match conclusion {
ProofExpr::ForAll { variable, body } if variable == var_name => {
proof_expr_to_type(body)
}
_ => Err(KernelError::CertificationError(format!(
"StructuralInduction conclusion must be ForAll over {}, got {:?}",
var_name, conclusion
))),
}
}
fn compute_ih_conclusion(
conclusion: &ProofExpr,
orig_var: &str,
step_var: &str,
) -> KernelResult<ProofExpr> {
match conclusion {
ProofExpr::ForAll { body, .. } => Ok(substitute_var_in_expr(body, orig_var, step_var)),
_ => Err(KernelError::CertificationError(
"Expected ForAll for IH computation".to_string(),
)),
}
}
fn constructor_arg_types(ctx: &Context, constructor: &str) -> KernelResult<Vec<Term>> {
let mut ty = ctx
.get_global(constructor)
.ok_or_else(|| {
KernelError::CertificationError(format!("unknown constructor {}", constructor))
})?
.clone();
let mut args = Vec::new();
while let Term::Pi {
param_type,
body_type,
..
} = ty
{
args.push(*param_type);
ty = *body_type;
}
Ok(args)
}
fn build_motive(ind_type: &str, result_type: &Term, motive_param: &str) -> Term {
Term::Lambda {
param: motive_param.to_string(),
param_type: Box::new(Term::Global(ind_type.to_string())),
body: Box::new(result_type.clone()),
}
}
fn substitute_var_in_expr(expr: &ProofExpr, from: &str, to: &str) -> ProofExpr {
match expr {
ProofExpr::Atom(s) if s == from => ProofExpr::Atom(to.to_string()),
ProofExpr::Atom(s) => ProofExpr::Atom(s.clone()),
ProofExpr::TypedVar { name, typename } => ProofExpr::TypedVar {
name: if name == from {
to.to_string()
} else {
name.clone()
},
typename: typename.clone(),
},
ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_var_in_term(a, from, to))
.collect(),
world: world.clone(),
},
ProofExpr::Identity(l, r) => ProofExpr::Identity(
substitute_var_in_term(l, from, to),
substitute_var_in_term(r, from, to),
),
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Not(inner) => {
ProofExpr::Not(Box::new(substitute_var_in_expr(inner, from, to)))
}
ProofExpr::ForAll { variable, body } if variable != from => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(substitute_var_in_expr(body, from, to)),
},
ProofExpr::Exists { variable, body } if variable != from => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(substitute_var_in_expr(body, from, to)),
},
ProofExpr::Lambda { variable, body } if variable != from => ProofExpr::Lambda {
variable: variable.clone(),
body: Box::new(substitute_var_in_expr(body, from, to)),
},
ProofExpr::App(f, a) => ProofExpr::App(
Box::new(substitute_var_in_expr(f, from, to)),
Box::new(substitute_var_in_expr(a, from, to)),
),
ProofExpr::Term(t) => ProofExpr::Term(substitute_var_in_term(t, from, to)),
ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_var_in_expr(a, from, to))
.collect(),
},
other => other.clone(),
}
}
fn substitute_var_in_term(term: &ProofTerm, from: &str, to: &str) -> ProofTerm {
match term {
ProofTerm::Variable(s) if s == from => ProofTerm::Variable(to.to_string()),
ProofTerm::Variable(s) => ProofTerm::Variable(s.clone()),
ProofTerm::Constant(s) => ProofTerm::Constant(s.clone()),
ProofTerm::BoundVarRef(s) if s == from => ProofTerm::BoundVarRef(to.to_string()),
ProofTerm::BoundVarRef(s) => ProofTerm::BoundVarRef(s.clone()),
ProofTerm::Function(name, args) => ProofTerm::Function(
name.clone(),
args.iter()
.map(|a| substitute_var_in_term(a, from, to))
.collect(),
),
ProofTerm::Group(terms) => ProofTerm::Group(
terms
.iter()
.map(|t| substitute_var_in_term(t, from, to))
.collect(),
),
}
}
#[cfg(test)]
mod canonical_arith_boundary_tests {
use super::*;
#[test]
fn kernel_arith_name_maps_canonical_to_ring() {
assert_eq!(kernel_arith_name("Add"), "add");
assert_eq!(kernel_arith_name("Sub"), "sub");
assert_eq!(kernel_arith_name("Mul"), "mul");
assert_eq!(kernel_arith_name("Div"), "div");
assert_eq!(kernel_arith_name("Score"), "Score");
assert_eq!(kernel_arith_name("add"), "add");
}
#[test]
fn canonical_add_lowers_to_kernel_ring_head() {
let t = proof_term_to_kernel_term(&ProofTerm::Function(
"Add".to_string(),
vec![
ProofTerm::Variable("x".to_string()),
ProofTerm::Variable("y".to_string()),
],
))
.expect("Add term converts to a kernel term");
match t {
Term::App(f, _) => match *f {
Term::App(g, _) => match *g {
Term::Global(name) => assert_eq!(
name, "add",
"canonical Add must lower to the kernel ring name 'add'; got {name}"
),
other => panic!("expected a Global head, got {:?}", other),
},
other => panic!("expected a nested App, got {:?}", other),
},
other => panic!("expected an App, got {:?}", other),
}
}
#[test]
fn numeric_predicate_argument_lowers_to_entity_label() {
let in_beta_2002 = ProofExpr::Predicate {
name: "in".to_string(),
args: vec![
ProofTerm::Constant("Beta".to_string()),
ProofTerm::Constant("2002".to_string()),
],
world: None,
};
let t = proof_expr_to_type(&in_beta_2002).expect("predicate lowers to a kernel type");
match t {
Term::App(_, year) => assert!(
matches!(*year, Term::Global(ref n) if n == "2002"),
"year `2002` in a relation must be an Entity Global, got {:?}",
year
),
other => panic!("expected `(in Beta) 2002` application, got {:?}", other),
}
}
#[test]
fn numeric_arithmetic_operand_stays_int_literal() {
use logicaffeine_kernel::Literal;
let le_2_5 = ProofTerm::Function(
"le".to_string(),
vec![
ProofTerm::Constant("2".to_string()),
ProofTerm::Constant("5".to_string()),
],
);
let t = proof_term_to_kernel_term(&le_2_5).expect("le term converts");
match t {
Term::App(_, five) => assert!(
matches!(*five, Term::Lit(Literal::Int(5))),
"an arithmetic operand must stay an Int literal, got {:?}",
five
),
other => panic!("expected `(le 2) 5` application, got {:?}", other),
}
}
#[test]
fn entity_lowering_is_numeric_aware_but_arith_preserving() {
use logicaffeine_kernel::Literal;
assert!(
matches!(
proof_term_to_kernel_term_entity(&ProofTerm::Constant("2004".to_string())).unwrap(),
Term::Global(ref n) if n == "2004"
),
"a bare numeric in an entity position is an Entity label"
);
let add = ProofTerm::Function(
"add".to_string(),
vec![
ProofTerm::Constant("2".to_string()),
ProofTerm::Constant("3".to_string()),
],
);
match proof_term_to_kernel_term_entity(&add).unwrap() {
Term::App(_, three) => assert!(
matches!(*three, Term::Lit(Literal::Int(3))),
"an arithmetic operand stays Int even inside an entity position, got {:?}",
three
),
other => panic!("expected `(add 2) 3`, got {:?}", other),
}
}
}