1use logicaffeine_kernel::{Context, KernelError, KernelResult, Term};
13
14use crate::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
15
16struct InductionState {
25 fix_name: String,
27 ihs: Vec<(String, ProofExpr)>,
31}
32
33pub struct CertificationContext<'a> {
65 kernel_ctx: &'a Context,
66 locals: Vec<String>,
68 local_hyps: Vec<std::sync::Arc<(ProofExpr, Term)>>,
81 induction_state: Option<InductionState>,
83}
84
85impl<'a> CertificationContext<'a> {
86 pub fn new(kernel_ctx: &'a Context) -> Self {
87 Self {
88 kernel_ctx,
89 locals: Vec::new(),
90 local_hyps: Vec::new(),
91 induction_state: None,
92 }
93 }
94
95 fn with_local(&self, name: &str) -> Self {
97 let mut new_locals = self.locals.clone();
98 new_locals.push(name.to_string());
99 Self {
100 kernel_ctx: self.kernel_ctx,
101 locals: new_locals,
102 local_hyps: self.local_hyps.clone(),
103 induction_state: self.induction_state.clone(),
104 }
105 }
106
107 fn with_local_hyp(&self, prop: &ProofExpr, var: &str) -> Self {
110 self.with_local_hyp_term(prop, Term::Var(var.to_string()))
111 }
112
113 fn with_local_hyp_term(&self, prop: &ProofExpr, witness: Term) -> Self {
118 let mut new_hyps = self.local_hyps.clone();
119 new_hyps.push(std::sync::Arc::new((prop.clone(), witness)));
120 Self {
121 kernel_ctx: self.kernel_ctx,
122 locals: self.locals.clone(),
123 local_hyps: new_hyps,
124 induction_state: self.induction_state.clone(),
125 }
126 }
127
128 fn fresh_hyp_name(&self) -> String {
130 format!("_hyp{}", self.local_hyps.len())
131 }
132
133 fn get_local_hyp(&self, conclusion: &ProofExpr) -> Option<Term> {
135 self.local_hyps
136 .iter()
137 .rev()
138 .find(|h| h.0 == *conclusion)
139 .map(|h| h.1.clone())
140 }
141
142 fn with_induction(&self, fix_name: &str, step_var: &str, ih: ProofExpr) -> Self {
144 self.with_induction_multi(fix_name, vec![(step_var.to_string(), ih)])
145 }
146
147 fn with_induction_multi(&self, fix_name: &str, ihs: Vec<(String, ProofExpr)>) -> Self {
150 Self {
151 kernel_ctx: self.kernel_ctx,
152 locals: self.locals.clone(),
153 local_hyps: self.local_hyps.clone(),
154 induction_state: Some(InductionState {
155 fix_name: fix_name.to_string(),
156 ihs,
157 }),
158 }
159 }
160
161 fn is_local(&self, name: &str) -> bool {
163 self.locals.iter().any(|n| n == name)
164 }
165
166 fn get_ih_term(&self, conclusion: &ProofExpr) -> Option<Term> {
169 let state = self.induction_state.as_ref()?;
170 for (var, ih) in &state.ihs {
171 if conclusions_match(conclusion, ih) {
172 return Some(Term::App(
174 Box::new(Term::Var(state.fix_name.clone())),
175 Box::new(Term::Var(var.clone())),
176 ));
177 }
178 }
179 None
180 }
181}
182
183impl Clone for InductionState {
184 fn clone(&self) -> Self {
185 Self {
186 fix_name: self.fix_name.clone(),
187 ihs: self.ihs.clone(),
188 }
189 }
190}
191
192fn conclusions_match(a: &ProofExpr, b: &ProofExpr) -> bool {
195 a == b
196}
197
198pub fn certify(tree: &DerivationTree, ctx: &CertificationContext) -> KernelResult<Term> {
238 match &tree.rule {
239 InferenceRule::Axiom | InferenceRule::PremiseMatch => {
241 certify_hypothesis(&tree.conclusion, ctx)
242 }
243
244 InferenceRule::ModusPonens => {
247 if tree.premises.len() != 2 {
248 return Err(KernelError::CertificationError(
249 "ModusPonens requires exactly 2 premises".to_string(),
250 ));
251 }
252 let impl_term = certify(&tree.premises[0], ctx)?;
253 let arg_term = certify(&tree.premises[1], ctx)?;
254 Ok(Term::App(Box::new(impl_term), Box::new(arg_term)))
255 }
256
257 InferenceRule::ModusTollens => {
261 if tree.premises.len() != 2 {
262 return Err(KernelError::CertificationError(
263 "ModusTollens requires exactly 2 premises (implication, negated consequent)"
264 .to_string(),
265 ));
266 }
267 let p = match &tree.conclusion {
268 ProofExpr::Not(p) => p.as_ref().clone(),
269 _ => {
270 return Err(KernelError::CertificationError(
271 "ModusTollens conclusion must be a negation".to_string(),
272 ))
273 }
274 };
275 let p_type = proof_expr_to_type(&p)?;
276 let impl_proof = certify(&tree.premises[0], ctx)?;
277 let neg_q_proof = certify(&tree.premises[1], ctx)?;
278 let hp = "__mt_hp".to_string();
279 Ok(Term::Lambda {
281 param: hp.clone(),
282 param_type: Box::new(p_type),
283 body: Box::new(Term::App(
284 Box::new(neg_q_proof),
285 Box::new(Term::App(
286 Box::new(impl_proof),
287 Box::new(Term::Var(hp)),
288 )),
289 )),
290 })
291 }
292
293 InferenceRule::Reflexivity => {
299 let (l, r) = match &tree.conclusion {
300 ProofExpr::Identity(l, r) => (l, r),
301 _ => {
302 return Err(KernelError::CertificationError(
303 "Reflexivity conclusion must be an Identity".to_string(),
304 ))
305 }
306 };
307 let domain = Term::Global(identity_domain(l, r).to_string());
308 let term = proof_term_to_kernel_term(l)?;
309 Ok(Term::App(
310 Box::new(Term::App(
311 Box::new(Term::Global("refl".to_string())),
312 Box::new(domain),
313 )),
314 Box::new(term),
315 ))
316 }
317
318 InferenceRule::ExFalso => {
321 if tree.premises.len() != 1 {
322 return Err(KernelError::CertificationError(
323 "ExFalso requires exactly 1 premise (a proof of False)".to_string(),
324 ));
325 }
326 let false_proof = certify(&tree.premises[0], ctx)?;
327 let goal_type = proof_expr_to_type(&tree.conclusion)?;
328 Ok(Term::Match {
329 discriminant: Box::new(false_proof),
330 motive: Box::new(Term::Lambda {
331 param: "_".to_string(),
332 param_type: Box::new(Term::Global("False".to_string())),
333 body: Box::new(goal_type),
334 }),
335 cases: vec![],
336 })
337 }
338
339 InferenceRule::ConjunctionIntro => {
342 if tree.premises.len() != 2 {
343 return Err(KernelError::CertificationError(
344 "ConjunctionIntro requires exactly 2 premises".to_string(),
345 ));
346 }
347 let (p_type, q_type) = extract_and_types(&tree.conclusion)?;
348 let p_term = certify(&tree.premises[0], ctx)?;
349 let q_term = certify(&tree.premises[1], ctx)?;
350
351 let conj = Term::Global("conj".to_string());
353 let applied = Term::App(
354 Box::new(Term::App(
355 Box::new(Term::App(
356 Box::new(Term::App(Box::new(conj), Box::new(p_type))),
357 Box::new(q_type),
358 )),
359 Box::new(p_term),
360 )),
361 Box::new(q_term),
362 );
363 Ok(applied)
364 }
365
366 InferenceRule::DisjunctionIntro => {
370 if tree.premises.len() != 1 {
371 return Err(KernelError::CertificationError(
372 "DisjunctionIntro requires exactly 1 premise".to_string(),
373 ));
374 }
375 let (p, q) = match &tree.conclusion {
376 ProofExpr::Or(p, q) => (p.as_ref().clone(), q.as_ref().clone()),
377 _ => {
378 return Err(KernelError::CertificationError(
379 "DisjunctionIntro conclusion must be Or".to_string(),
380 ))
381 }
382 };
383 let proved = &tree.premises[0].conclusion;
384 let ctor = if proved == &p {
385 "left"
386 } else if proved == &q {
387 "right"
388 } else {
389 return Err(KernelError::CertificationError(
390 "DisjunctionIntro premise proves neither disjunct".to_string(),
391 ));
392 };
393 let p_type = proof_expr_to_type(&p)?;
394 let q_type = proof_expr_to_type(&q)?;
395 let proof_term = certify(&tree.premises[0], ctx)?;
396
397 let applied = Term::App(
399 Box::new(Term::App(
400 Box::new(Term::App(
401 Box::new(Term::Global(ctor.to_string())),
402 Box::new(p_type),
403 )),
404 Box::new(q_type),
405 )),
406 Box::new(proof_term),
407 );
408 Ok(applied)
409 }
410
411 InferenceRule::UniversalInst(witness) => {
414 if tree.premises.len() != 1 {
415 return Err(KernelError::CertificationError(
416 "UniversalInst requires exactly 1 premise".to_string(),
417 ));
418 }
419 let forall_proof = certify(&tree.premises[0], ctx)?;
420 let witness_term = if ctx.is_local(witness) {
422 Term::Var(witness.clone())
423 } else {
424 Term::Global(witness.clone())
425 };
426 Ok(Term::App(Box::new(forall_proof), Box::new(witness_term)))
427 }
428
429 InferenceRule::UniversalInstTerm(witness) => {
433 if tree.premises.len() != 1 {
434 return Err(KernelError::CertificationError(
435 "UniversalInstTerm requires exactly 1 premise".to_string(),
436 ));
437 }
438 let forall_proof = certify(&tree.premises[0], ctx)?;
439 let witness_term = match witness {
440 ProofTerm::Constant(name) | ProofTerm::Variable(name)
441 if ctx.is_local(name) =>
442 {
443 Term::Var(name.clone())
444 }
445 _ => proof_term_to_kernel_term(witness)?,
446 };
447 Ok(Term::App(Box::new(forall_proof), Box::new(witness_term)))
448 }
449
450 InferenceRule::UniversalIntro { variable, var_type } => {
453 if tree.premises.len() != 1 {
454 return Err(KernelError::CertificationError(
455 "UniversalIntro requires exactly 1 premise".to_string(),
456 ));
457 }
458
459 let type_term = Term::Global(var_type.clone());
461
462 let extended_ctx = ctx.with_local(variable);
464 let body_term = certify(&tree.premises[0], &extended_ctx)?;
465
466 Ok(Term::Lambda {
468 param: variable.clone(),
469 param_type: Box::new(type_term),
470 body: Box::new(body_term),
471 })
472 }
473
474 InferenceRule::StructuralInduction {
477 variable: var_name,
478 ind_type,
479 step_var,
480 } => {
481 if tree.premises.len() != 2 {
482 return Err(KernelError::CertificationError(
483 "StructuralInduction requires exactly 2 premises (base, step)".to_string(),
484 ));
485 }
486
487 let motive_body = extract_motive_body(&tree.conclusion, var_name)?;
489
490 let fix_name = format!("rec_{}", var_name);
492
493 let base_term = certify(&tree.premises[0], ctx)?;
496
497 let ih_conclusion = compute_ih_conclusion(&tree.conclusion, var_name, step_var)?;
500
501 let step_ctx = ctx
505 .with_local(step_var)
506 .with_induction(&fix_name, step_var, ih_conclusion);
507
508 let step_body = certify(&tree.premises[1], &step_ctx)?;
509
510 let step_term = Term::Lambda {
512 param: step_var.clone(),
513 param_type: Box::new(Term::Global(ind_type.clone())),
514 body: Box::new(step_body),
515 };
516
517 let match_term = Term::Match {
521 discriminant: Box::new(Term::Var(var_name.clone())),
522 motive: Box::new(build_motive(ind_type, &motive_body, var_name)),
523 cases: vec![base_term, step_term],
524 };
525
526 let lambda_term = Term::Lambda {
529 param: var_name.clone(),
530 param_type: Box::new(Term::Global(ind_type.clone())),
531 body: Box::new(match_term),
532 };
533
534 Ok(Term::Fix {
537 name: fix_name,
538 body: Box::new(lambda_term),
539 })
540 }
541
542 InferenceRule::InductionScheme {
548 variable,
549 ind_type,
550 cases,
551 } => {
552 if tree.premises.len() != cases.len() {
553 return Err(KernelError::CertificationError(format!(
554 "InductionScheme requires one premise per constructor: {} cases, {} premises",
555 cases.len(),
556 tree.premises.len()
557 )));
558 }
559
560 let motive_body = extract_motive_body(&tree.conclusion, variable)?;
561 let fix_name = format!("rec_{}", variable);
562
563 let mut case_terms = Vec::with_capacity(cases.len());
564 for (case, premise) in cases.iter().zip(tree.premises.iter()) {
565 let arg_types = constructor_arg_types(ctx.kernel_ctx, &case.constructor)?;
567 if arg_types.len() != case.args.len() {
568 return Err(KernelError::CertificationError(format!(
569 "constructor {} takes {} arguments, case binds {}",
570 case.constructor,
571 arg_types.len(),
572 case.args.len()
573 )));
574 }
575
576 let mut ihs = Vec::new();
579 for arg in &case.args {
580 if arg.recursive {
581 let ih = compute_ih_conclusion(&tree.conclusion, variable, &arg.name)?;
582 ihs.push((arg.name.clone(), ih));
583 }
584 }
585
586 let mut case_ctx = ctx.with_induction_multi(&fix_name, ihs);
589 for arg in &case.args {
590 case_ctx = case_ctx.with_local(&arg.name);
591 }
592 let case_body = certify(premise, &case_ctx)?;
593
594 let mut term = case_body;
597 for (arg, ty) in case.args.iter().zip(arg_types.iter()).rev() {
598 term = Term::Lambda {
599 param: arg.name.clone(),
600 param_type: Box::new(ty.clone()),
601 body: Box::new(term),
602 };
603 }
604 case_terms.push(term);
605 }
606
607 let match_term = Term::Match {
609 discriminant: Box::new(Term::Var(variable.clone())),
610 motive: Box::new(build_motive(ind_type, &motive_body, variable)),
611 cases: case_terms,
612 };
613
614 let lambda_term = Term::Lambda {
616 param: variable.clone(),
617 param_type: Box::new(Term::Global(ind_type.clone())),
618 body: Box::new(match_term),
619 };
620
621 Ok(Term::Fix {
622 name: fix_name,
623 body: Box::new(lambda_term),
624 })
625 }
626
627 InferenceRule::ExistentialIntro {
630 witness: witness_str,
631 witness_type,
632 } => {
633 if tree.premises.len() != 1 {
634 return Err(KernelError::CertificationError(
635 "ExistentialIntro requires exactly 1 premise".to_string(),
636 ));
637 }
638
639 let (variable, body) = match &tree.conclusion {
641 ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref().clone()),
642 _ => {
643 return Err(KernelError::CertificationError(
644 "ExistentialIntro conclusion must be Exists".to_string(),
645 ))
646 }
647 };
648
649 let witness_term = if ctx.is_local(witness_str) {
651 Term::Var(witness_str.clone())
652 } else {
653 Term::Global(witness_str.clone())
654 };
655
656 let proof_term = certify(&tree.premises[0], ctx)?;
658
659 let type_a = Term::Global(witness_type.clone());
661
662 let predicate = Term::Lambda {
667 param: variable.clone(),
668 param_type: Box::new(type_a.clone()),
669 body: Box::new(proof_expr_to_type(&body)?),
670 };
671
672 let witness_ctor = Term::Global("witness".to_string());
675
676 let applied = Term::App(
677 Box::new(Term::App(
678 Box::new(Term::App(
679 Box::new(Term::App(Box::new(witness_ctor), Box::new(type_a))),
680 Box::new(predicate),
681 )),
682 Box::new(witness_term),
683 )),
684 Box::new(proof_term),
685 );
686
687 Ok(applied)
688 }
689
690 InferenceRule::ExistentialElim { witness } => {
704 if tree.premises.len() != 2 {
705 return Err(KernelError::CertificationError(
706 "ExistentialElim requires exactly 2 premises (existential, body)".to_string(),
707 ));
708 }
709 let exist_premise = &tree.premises[0];
710 let body = &tree.premises[1];
711
712 let (var, p_body) = match &exist_premise.conclusion {
713 ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref().clone()),
714 _ => {
715 return Err(KernelError::CertificationError(
716 "ExistentialElim first premise must conclude an Exists".to_string(),
717 ))
718 }
719 };
720
721 let mut subst = crate::unify::Substitution::new();
723 subst.insert(var.clone(), ProofTerm::Constant(witness.clone()));
724 let p_c_expr = crate::unify::apply_subst_to_expr(&p_body, &subst);
725
726 let disc = certify(exist_premise, ctx)?;
728
729 let h = format!("_exh_{}", witness);
734 let h_var = Term::Var(h.clone());
735 let mut body_ctx = ctx.with_local_hyp_term(&p_c_expr, h_var.clone());
736 for (conjunct, proj) in collect_conjunct_hyps(&p_c_expr, &h_var)? {
737 body_ctx = body_ctx.with_local_hyp_term(&conjunct, proj);
738 }
739
740 let body_raw = certify(body, &body_ctx)?;
741 let c_global = Term::Global(witness.clone());
742 let c_var = Term::Var(witness.clone());
743 let body_term = substitute_term_in_kernel(&body_raw, &c_global, &c_var);
744 let p_c_type =
745 substitute_term_in_kernel(&proof_expr_to_type(&p_c_expr)?, &c_global, &c_var);
746
747 let motive = proof_expr_to_type(&tree.conclusion)?;
752
753 let case = Term::Lambda {
755 param: witness.clone(),
756 param_type: Box::new(Term::Global("Entity".to_string())),
757 body: Box::new(Term::Lambda {
758 param: h,
759 param_type: Box::new(p_c_type),
760 body: Box::new(body_term),
761 }),
762 };
763
764 Ok(Term::Match {
765 discriminant: Box::new(disc),
766 motive: Box::new(motive),
767 cases: vec![case],
768 })
769 }
770
771 InferenceRule::Rewrite { from, to } => {
775 if tree.premises.len() != 2 {
776 return Err(KernelError::CertificationError(
777 "Rewrite requires exactly 2 premises (equality, source)".to_string(),
778 ));
779 }
780
781 let eq_proof = certify(&tree.premises[0], ctx)?;
783 let source_proof = certify(&tree.premises[1], ctx)?;
784
785 let from_term = proof_term_to_kernel_term(from)?;
787 let to_term = proof_term_to_kernel_term(to)?;
788
789 let predicate = build_equality_predicate(&tree.conclusion, to)?;
792
793 let eq_rec = Term::Global("Eq_rec".to_string());
797 let domain = Term::Global(term_domain(from).to_string());
798
799 let applied = Term::App(
800 Box::new(Term::App(
801 Box::new(Term::App(
802 Box::new(Term::App(
803 Box::new(Term::App(
804 Box::new(Term::App(Box::new(eq_rec), Box::new(domain))),
805 Box::new(from_term),
806 )),
807 Box::new(predicate),
808 )),
809 Box::new(source_proof),
810 )),
811 Box::new(to_term),
812 )),
813 Box::new(eq_proof),
814 );
815
816 Ok(applied)
817 }
818
819 InferenceRule::EqualitySymmetry => {
822 if tree.premises.len() != 1 {
823 return Err(KernelError::CertificationError(
824 "EqualitySymmetry requires exactly 1 premise".to_string(),
825 ));
826 }
827
828 let premise_proof = certify(&tree.premises[0], ctx)?;
829
830 let (x, y, domain) = match &tree.premises[0].conclusion {
832 ProofExpr::Identity(l, r) => (
833 proof_term_to_kernel_term(l)?,
834 proof_term_to_kernel_term(r)?,
835 Term::Global(identity_domain(l, r).to_string()),
836 ),
837 _ => {
838 return Err(KernelError::CertificationError(
839 "EqualitySymmetry premise must be an Identity".to_string(),
840 ))
841 }
842 };
843
844 let eq_sym = Term::Global("Eq_sym".to_string());
848
849 Ok(Term::App(
850 Box::new(Term::App(
851 Box::new(Term::App(
852 Box::new(Term::App(Box::new(eq_sym), Box::new(domain))),
853 Box::new(x),
854 )),
855 Box::new(y),
856 )),
857 Box::new(premise_proof),
858 ))
859 }
860
861 InferenceRule::EqualityTransitivity => {
864 if tree.premises.len() != 2 {
865 return Err(KernelError::CertificationError(
866 "EqualityTransitivity requires exactly 2 premises".to_string(),
867 ));
868 }
869
870 let proof1 = certify(&tree.premises[0], ctx)?;
871 let proof2 = certify(&tree.premises[1], ctx)?;
872
873 let (x, y, domain) = match &tree.premises[0].conclusion {
875 ProofExpr::Identity(l, r) => (
876 proof_term_to_kernel_term(l)?,
877 proof_term_to_kernel_term(r)?,
878 Term::Global(identity_domain(l, r).to_string()),
879 ),
880 _ => {
881 return Err(KernelError::CertificationError(
882 "EqualityTransitivity first premise must be Identity".to_string(),
883 ))
884 }
885 };
886
887 let z = match &tree.premises[1].conclusion {
889 ProofExpr::Identity(_, r) => proof_term_to_kernel_term(r)?,
890 _ => {
891 return Err(KernelError::CertificationError(
892 "EqualityTransitivity second premise must be Identity".to_string(),
893 ))
894 }
895 };
896
897 let eq_trans = Term::Global("Eq_trans".to_string());
901
902 Ok(Term::App(
903 Box::new(Term::App(
904 Box::new(Term::App(
905 Box::new(Term::App(
906 Box::new(Term::App(
907 Box::new(Term::App(Box::new(eq_trans), Box::new(domain))),
908 Box::new(x),
909 )),
910 Box::new(y),
911 )),
912 Box::new(z),
913 )),
914 Box::new(proof1),
915 )),
916 Box::new(proof2),
917 ))
918 }
919
920 InferenceRule::LeTrans => {
923 if tree.premises.len() != 2 {
924 return Err(KernelError::CertificationError(
925 "LeTrans requires exactly 2 premises".to_string(),
926 ));
927 }
928 let (a, b) = le_terms(&tree.conclusion)?;
929 let (_, mid) = le_terms(&tree.premises[0].conclusion)?;
930 let p0 = certify(&tree.premises[0], ctx)?;
931 let p1 = certify(&tree.premises[1], ctx)?;
932 let mut t = Term::Global("le_trans".to_string());
933 for arg in [
934 proof_term_to_kernel_term(&a)?,
935 proof_term_to_kernel_term(&mid)?,
936 proof_term_to_kernel_term(&b)?,
937 p0,
938 p1,
939 ] {
940 t = Term::App(Box::new(t), Box::new(arg));
941 }
942 Ok(t)
943 }
944
945 InferenceRule::LeRefl => {
947 let (a, _) = le_terms(&tree.conclusion)?;
948 Ok(Term::App(
949 Box::new(Term::Global("le_refl".to_string())),
950 Box::new(proof_term_to_kernel_term(&a)?),
951 ))
952 }
953
954 InferenceRule::LeAddMono => {
956 if tree.premises.len() != 2 {
957 return Err(KernelError::CertificationError(
958 "LeAddMono requires exactly 2 premises".to_string(),
959 ));
960 }
961 let (lhs, rhs) = le_terms(&tree.conclusion)?;
962 let (a, c) = add_terms(&lhs)?;
963 let (b, d) = add_terms(&rhs)?;
964 let p0 = certify(&tree.premises[0], ctx)?;
965 let p1 = certify(&tree.premises[1], ctx)?;
966 let mut t = Term::Global("le_add_mono".to_string());
967 for arg in [
968 proof_term_to_kernel_term(&a)?,
969 proof_term_to_kernel_term(&b)?,
970 proof_term_to_kernel_term(&c)?,
971 proof_term_to_kernel_term(&d)?,
972 p0,
973 p1,
974 ] {
975 t = Term::App(Box::new(t), Box::new(arg));
976 }
977 Ok(t)
978 }
979
980 InferenceRule::LinFalse => {
984 if tree.premises.len() != 1 {
985 return Err(KernelError::CertificationError(
986 "LinFalse requires exactly 1 premise".to_string(),
987 ));
988 }
989 let le_proof = certify(&tree.premises[0], ctx)?;
990 Ok(build_bool_discriminator(le_proof))
991 }
992
993 InferenceRule::LeMulNonneg => {
995 if tree.premises.len() != 2 {
996 return Err(KernelError::CertificationError(
997 "LeMulNonneg requires exactly 2 premises".to_string(),
998 ));
999 }
1000 let (lhs, rhs) = le_terms(&tree.conclusion)?;
1001 let (k, a) = binop_terms("mul", &lhs)?;
1002 let (_, b) = binop_terms("mul", &rhs)?;
1003 let p0 = certify(&tree.premises[0], ctx)?;
1004 let p1 = certify(&tree.premises[1], ctx)?;
1005 let mut t = Term::Global("le_mul_nonneg".to_string());
1006 for arg in [
1007 proof_term_to_kernel_term(&k)?,
1008 proof_term_to_kernel_term(&a)?,
1009 proof_term_to_kernel_term(&b)?,
1010 p0,
1011 p1,
1012 ] {
1013 t = Term::App(Box::new(t), Box::new(arg));
1014 }
1015 Ok(t)
1016 }
1017
1018 InferenceRule::LeSub => {
1020 if tree.premises.len() != 1 {
1021 return Err(KernelError::CertificationError(
1022 "LeSub requires exactly 1 premise".to_string(),
1023 ));
1024 }
1025 let (_zero, diff) = le_terms(&tree.conclusion)?;
1026 let (b, neg_a) = binop_terms("add", &diff)?;
1027 let (_neg_one, a) = binop_terms("mul", &neg_a)?;
1028 let p0 = certify(&tree.premises[0], ctx)?;
1029 let mut t = Term::Global("le_sub".to_string());
1030 for arg in [proof_term_to_kernel_term(&a)?, proof_term_to_kernel_term(&b)?, p0] {
1031 t = Term::App(Box::new(t), Box::new(arg));
1032 }
1033 Ok(t)
1034 }
1035
1036 InferenceRule::LtSuccLe => {
1041 if tree.premises.len() != 1 {
1042 return Err(KernelError::CertificationError(
1043 "LtSuccLe requires exactly 1 premise".to_string(),
1044 ));
1045 }
1046 let (succ_a, b) = le_terms(&tree.conclusion)?;
1047 let (a, _one) = binop_terms("add", &succ_a)?;
1048 let p0 = certify(&tree.premises[0], ctx)?;
1049 let mut t = Term::Global("lt_succ_le".to_string());
1050 for arg in [proof_term_to_kernel_term(&a)?, proof_term_to_kernel_term(&b)?, p0] {
1051 t = Term::App(Box::new(t), Box::new(arg));
1052 }
1053 Ok(t)
1054 }
1055
1056 InferenceRule::LtAdd1Le => {
1059 if tree.premises.len() != 1 {
1060 return Err(KernelError::CertificationError(
1061 "LtAdd1Le requires exactly 1 premise".to_string(),
1062 ));
1063 }
1064 let (a, b) = le_terms(&tree.conclusion)?;
1065 let p0 = certify(&tree.premises[0], ctx)?;
1066 let mut t = Term::Global("lt_add1_le".to_string());
1067 for arg in [proof_term_to_kernel_term(&a)?, proof_term_to_kernel_term(&b)?, p0] {
1068 t = Term::App(Box::new(t), Box::new(arg));
1069 }
1070 Ok(t)
1071 }
1072
1073 InferenceRule::ArithDecision => match &tree.conclusion {
1078 ProofExpr::Identity(l, r) => {
1079 let kl = proof_term_to_kernel_term(l)?;
1080 let kr = proof_term_to_kernel_term(r)?;
1081 crate::arith::prove_int_eq(ctx.kernel_ctx, &kl, &kr).ok_or_else(|| {
1082 KernelError::CertificationError(
1083 "ArithDecision: arithmetic oracle found no proof".to_string(),
1084 )
1085 })
1086 }
1087 _ => Err(KernelError::CertificationError(
1088 "ArithDecision conclusion must be an Identity".to_string(),
1089 )),
1090 },
1091
1092 InferenceRule::NativeDecide => {
1098 if !tree.premises.is_empty() {
1099 return Err(KernelError::CertificationError(
1100 "NativeDecide is a leaf (no premises)".to_string(),
1101 ));
1102 }
1103 let prop = proof_expr_to_type_ctx(&tree.conclusion, ctx.kernel_ctx)?;
1104 let inst = decidable_instance_for(&prop).ok_or_else(|| {
1105 KernelError::CertificationError(
1106 "NativeDecide: no Decidable instance for this proposition".to_string(),
1107 )
1108 })?;
1109 logicaffeine_kernel::native_decide(ctx.kernel_ctx, &prop, &inst).ok_or_else(|| {
1110 KernelError::CertificationError(
1111 "NativeDecide: evaluation did not decide the goal true".to_string(),
1112 )
1113 })
1114 }
1115
1116 InferenceRule::Contradiction => {
1120 if tree.premises.len() != 2 {
1121 return Err(KernelError::CertificationError(
1122 "Contradiction requires exactly 2 premises (P and ¬P)".to_string(),
1123 ));
1124 }
1125 let a = &tree.premises[0];
1126 let b = &tree.premises[1];
1127 let (pos, neg) = if is_negation_of(&b.conclusion, &a.conclusion) {
1128 (a, b)
1129 } else if is_negation_of(&a.conclusion, &b.conclusion) {
1130 (b, a)
1131 } else {
1132 return Err(KernelError::CertificationError(format!(
1133 "Contradiction premises are not a proposition and its negation: {:?} vs {:?}",
1134 a.conclusion, b.conclusion
1135 )));
1136 };
1137 let pos_term = certify(pos, ctx)?;
1138 let neg_term = certify(neg, ctx)?;
1139 Ok(Term::App(Box::new(neg_term), Box::new(pos_term)))
1141 }
1142
1143 InferenceRule::ReductioAdAbsurdum => {
1147 if tree.premises.len() != 2 {
1148 return Err(KernelError::CertificationError(
1149 "ReductioAdAbsurdum requires exactly 2 premises (assumption, ⊥-derivation)"
1150 .to_string(),
1151 ));
1152 }
1153 let assumed = &tree.premises[0].conclusion;
1154 let contradiction = &tree.premises[1];
1155 let hyp_name = ctx.fresh_hyp_name();
1156 let branch_ctx = ctx.with_local_hyp(assumed, &hyp_name);
1157 let body = certify(contradiction, &branch_ctx)?;
1158 Ok(Term::Lambda {
1159 param: hyp_name,
1160 param_type: Box::new(proof_expr_to_type(assumed)?),
1161 body: Box::new(body),
1162 })
1163 }
1164
1165 InferenceRule::CaseAnalysis { case_formula } => {
1169 if tree.premises.len() != 2 {
1170 return Err(KernelError::CertificationError(
1171 "CaseAnalysis requires exactly 2 premises (C-branch, ¬C-branch)".to_string(),
1172 ));
1173 }
1174 let c = case_formula.as_ref();
1175 let not_c = ProofExpr::Not(Box::new(c.clone()));
1176
1177 let branch_c = unwrap_case_branch(&tree.premises[0]);
1178 let branch_nc = unwrap_case_branch(&tree.premises[1]);
1179
1180 let h1 = ctx.fresh_hyp_name();
1182 let ctx_c = ctx.with_local_hyp(c, &h1);
1183 let neg_c = Term::Lambda {
1184 param: h1,
1185 param_type: Box::new(proof_expr_to_type(c)?),
1186 body: Box::new(certify(branch_c, &ctx_c)?),
1187 };
1188
1189 let h2 = ctx_c.fresh_hyp_name();
1191 let ctx_nc = ctx.with_local_hyp(¬_c, &h2);
1192 let neg_neg_c = Term::Lambda {
1193 param: h2,
1194 param_type: Box::new(proof_expr_to_type(¬_c)?),
1195 body: Box::new(certify(branch_nc, &ctx_nc)?),
1196 };
1197
1198 Ok(Term::App(Box::new(neg_neg_c), Box::new(neg_c)))
1200 }
1201
1202 InferenceRule::DisjunctionElim => {
1210 if tree.premises.len() != 2 {
1211 return Err(KernelError::CertificationError(
1212 "DisjunctionElim requires exactly 2 premises (disjunction, negation)"
1213 .to_string(),
1214 ));
1215 }
1216 let disj_premise = &tree.premises[0];
1217 let neg_premise = &tree.premises[1];
1218
1219 let (left, right) = match &disj_premise.conclusion {
1220 ProofExpr::Or(l, r) => (l.as_ref().clone(), r.as_ref().clone()),
1221 other => {
1222 return Err(KernelError::CertificationError(format!(
1223 "DisjunctionElim first premise must conclude a disjunction, got {:?}",
1224 other
1225 )))
1226 }
1227 };
1228 let refuted = match &neg_premise.conclusion {
1229 ProofExpr::Not(inner) => inner.as_ref().clone(),
1230 other => {
1231 return Err(KernelError::CertificationError(format!(
1232 "DisjunctionElim second premise must conclude a negation, got {:?}",
1233 other
1234 )))
1235 }
1236 };
1237
1238 let left_is_refuted = conclusions_match(&refuted, &left);
1239 let right_is_refuted = conclusions_match(&refuted, &right);
1240 if left_is_refuted == right_is_refuted {
1241 return Err(KernelError::CertificationError(format!(
1242 "DisjunctionElim negation {:?} matches neither (or both) disjuncts of {:?}",
1243 refuted, disj_premise.conclusion
1244 )));
1245 }
1246
1247 let goal_type = proof_expr_to_type(&tree.conclusion)?;
1248 let disc = certify(disj_premise, ctx)?;
1249 let neg_term = certify(neg_premise, ctx)?;
1250
1251 let motive = goal_type.clone();
1254
1255 let return_case = |disjunct: &ProofExpr, binder: &str| -> KernelResult<Term> {
1258 Ok(Term::Lambda {
1259 param: binder.to_string(),
1260 param_type: Box::new(proof_expr_to_type(disjunct)?),
1261 body: Box::new(Term::Var(binder.to_string())),
1262 })
1263 };
1264 let absurd_case = |disjunct: &ProofExpr, binder: &str| -> KernelResult<Term> {
1266 let falsum = Term::App(
1267 Box::new(neg_term.clone()),
1268 Box::new(Term::Var(binder.to_string())),
1269 );
1270 let ex_falso = Term::Match {
1271 discriminant: Box::new(falsum),
1272 motive: Box::new(Term::Lambda {
1273 param: "_".to_string(),
1274 param_type: Box::new(Term::Global("False".to_string())),
1275 body: Box::new(goal_type.clone()),
1276 }),
1277 cases: vec![],
1278 };
1279 Ok(Term::Lambda {
1280 param: binder.to_string(),
1281 param_type: Box::new(proof_expr_to_type(disjunct)?),
1282 body: Box::new(ex_falso),
1283 })
1284 };
1285
1286 let left_case = if left_is_refuted {
1287 absurd_case(&left, "__dl")?
1288 } else {
1289 return_case(&left, "__dl")?
1290 };
1291 let right_case = if right_is_refuted {
1292 absurd_case(&right, "__dr")?
1293 } else {
1294 return_case(&right, "__dr")?
1295 };
1296
1297 Ok(Term::Match {
1298 discriminant: Box::new(disc),
1299 motive: Box::new(motive),
1300 cases: vec![left_case, right_case],
1301 })
1302 }
1303
1304 InferenceRule::DisjunctionCases => {
1313 if tree.premises.len() != 3 {
1314 return Err(KernelError::CertificationError(
1315 "DisjunctionCases requires exactly 3 premises (disjunction, A-branch, B-branch)"
1316 .to_string(),
1317 ));
1318 }
1319 let disj_premise = &tree.premises[0];
1320 let (left, right) = match &disj_premise.conclusion {
1321 ProofExpr::Or(l, r) => (l.as_ref().clone(), r.as_ref().clone()),
1322 other => {
1323 return Err(KernelError::CertificationError(format!(
1324 "DisjunctionCases first premise must conclude a disjunction, got {:?}",
1325 other
1326 )))
1327 }
1328 };
1329 let disc = certify(disj_premise, ctx)?;
1330 let motive = proof_expr_to_type(&tree.conclusion)?;
1335
1336 let build_arm = |disjunct: &ProofExpr,
1337 branch: &DerivationTree,
1338 binder: &str|
1339 -> KernelResult<Term> {
1340 let h_var = Term::Var(binder.to_string());
1341 let mut bctx = ctx.with_local_hyp_term(disjunct, h_var.clone());
1342 for (conjunct, proj) in collect_conjunct_hyps(disjunct, &h_var)? {
1343 bctx = bctx.with_local_hyp_term(&conjunct, proj);
1344 }
1345 let body = certify(branch, &bctx)?;
1346 Ok(Term::Lambda {
1347 param: binder.to_string(),
1348 param_type: Box::new(proof_expr_to_type(disjunct)?),
1349 body: Box::new(body),
1350 })
1351 };
1352
1353 let lname = ctx.fresh_hyp_name();
1358 let rname = ctx.fresh_hyp_name();
1359 let left_case = build_arm(&left, &tree.premises[1], &lname)?;
1360 let right_case = build_arm(&right, &tree.premises[2], &rname)?;
1361
1362 Ok(Term::Match {
1363 discriminant: Box::new(disc),
1364 motive: Box::new(motive),
1365 cases: vec![left_case, right_case],
1366 })
1367 }
1368
1369 InferenceRule::BicondIntro => {
1372 if tree.premises.len() != 2 {
1373 return Err(KernelError::CertificationError(
1374 "BicondIntro requires exactly 2 premises (P→Q, Q→P)".to_string(),
1375 ));
1376 }
1377 let (p, q) = match &tree.conclusion {
1378 ProofExpr::Iff(p, q) => (p.as_ref().clone(), q.as_ref().clone()),
1379 other => {
1380 return Err(KernelError::CertificationError(format!(
1381 "BicondIntro conclusion must be a biconditional, got {:?}",
1382 other
1383 )))
1384 }
1385 };
1386 let pq_type =
1387 proof_expr_to_type(&ProofExpr::Implies(Box::new(p.clone()), Box::new(q.clone())))?;
1388 let qp_type =
1389 proof_expr_to_type(&ProofExpr::Implies(Box::new(q), Box::new(p)))?;
1390 let pq_proof = certify(&tree.premises[0], ctx)?;
1391 let qp_proof = certify(&tree.premises[1], ctx)?;
1392 Ok(Term::App(
1393 Box::new(Term::App(
1394 Box::new(Term::App(
1395 Box::new(Term::App(
1396 Box::new(Term::Global("conj".to_string())),
1397 Box::new(pq_type),
1398 )),
1399 Box::new(qp_type),
1400 )),
1401 Box::new(pq_proof),
1402 )),
1403 Box::new(qp_proof),
1404 ))
1405 }
1406
1407 InferenceRule::DoubleNegation => {
1410 if tree.premises.len() != 1 {
1411 return Err(KernelError::CertificationError(
1412 "DoubleNegation requires exactly 1 premise (a proof of P)".to_string(),
1413 ));
1414 }
1415 let p = match &tree.conclusion {
1416 ProofExpr::Not(inner) => match inner.as_ref() {
1417 ProofExpr::Not(core) => core.as_ref().clone(),
1418 other => {
1419 return Err(KernelError::CertificationError(format!(
1420 "DoubleNegation conclusion must be ¬¬P, got ¬{:?}",
1421 other
1422 )))
1423 }
1424 },
1425 other => {
1426 return Err(KernelError::CertificationError(format!(
1427 "DoubleNegation conclusion must be ¬¬P, got {:?}",
1428 other
1429 )))
1430 }
1431 };
1432 let p_type = proof_expr_to_type(&p)?;
1433 let p_proof = certify(&tree.premises[0], ctx)?;
1434 let not_p_type = Term::Pi {
1435 param: "_".to_string(),
1436 param_type: Box::new(p_type),
1437 body_type: Box::new(Term::Global("False".to_string())),
1438 };
1439 let hnp = "__dn_hnp".to_string();
1440 Ok(Term::Lambda {
1441 param: hnp.clone(),
1442 param_type: Box::new(not_p_type),
1443 body: Box::new(Term::App(Box::new(Term::Var(hnp)), Box::new(p_proof))),
1444 })
1445 }
1446
1447 InferenceRule::ClassicalReductio => {
1450 if tree.premises.len() != 1 {
1451 return Err(KernelError::CertificationError(
1452 "ClassicalReductio requires exactly 1 premise (a proof of False)".to_string(),
1453 ));
1454 }
1455 let g = tree.conclusion.clone();
1456 let g_type = proof_expr_to_type(&g)?;
1457 let neg_g = ProofExpr::Not(Box::new(g));
1458 let neg_g_type = proof_expr_to_type(&neg_g)?; let binder = ctx.fresh_hyp_name();
1460 let bctx = ctx.with_local_hyp_term(&neg_g, Term::Var(binder.clone()));
1461 let false_proof = certify(&tree.premises[0], &bctx)?;
1462 let nn_g = Term::Lambda {
1463 param: binder,
1464 param_type: Box::new(neg_g_type),
1465 body: Box::new(false_proof),
1466 };
1467 Ok(Term::App(
1468 Box::new(Term::App(
1469 Box::new(Term::Global("dne".to_string())),
1470 Box::new(g_type),
1471 )),
1472 Box::new(nn_g),
1473 ))
1474 }
1475
1476 InferenceRule::ImpliesIntro => {
1480 if tree.premises.len() != 1 {
1481 return Err(KernelError::CertificationError(
1482 "ImpliesIntro requires exactly 1 premise (the consequent proof)".to_string(),
1483 ));
1484 }
1485 let ant = match &tree.conclusion {
1486 ProofExpr::Implies(a, _) => a.as_ref().clone(),
1487 other => {
1488 return Err(KernelError::CertificationError(format!(
1489 "ImpliesIntro conclusion must be an implication, got {:?}",
1490 other
1491 )))
1492 }
1493 };
1494 let binder = ctx.fresh_hyp_name();
1495 let h_var = Term::Var(binder.clone());
1496 let mut bctx = ctx.with_local_hyp_term(&ant, h_var.clone());
1497 for (conjunct, proj) in collect_conjunct_hyps(&ant, &h_var)? {
1498 bctx = bctx.with_local_hyp_term(&conjunct, proj);
1499 }
1500 let body = certify(&tree.premises[0], &bctx)?;
1501 Ok(Term::Lambda {
1502 param: binder,
1503 param_type: Box::new(proof_expr_to_type(&ant)?),
1504 body: Box::new(body),
1505 })
1506 }
1507
1508 InferenceRule::ConjunctionElim => {
1511 if tree.premises.len() != 1 {
1512 return Err(KernelError::CertificationError(
1513 "ConjunctionElim requires exactly 1 premise (the conjunction)".to_string(),
1514 ));
1515 }
1516 let conj_premise = &tree.premises[0];
1517 let (a, b) = match &conj_premise.conclusion {
1518 ProofExpr::And(l, r) => (l.as_ref().clone(), r.as_ref().clone()),
1519 ProofExpr::Iff(l, r) => (
1522 ProofExpr::Implies(l.clone(), r.clone()),
1523 ProofExpr::Implies(r.clone(), l.clone()),
1524 ),
1525 other => {
1526 return Err(KernelError::CertificationError(format!(
1527 "ConjunctionElim premise must conclude a conjunction, got {:?}",
1528 other
1529 )))
1530 }
1531 };
1532 let take_left = conclusions_match(&tree.conclusion, &a);
1533 if !take_left && !conclusions_match(&tree.conclusion, &b) {
1534 return Err(KernelError::CertificationError(format!(
1535 "ConjunctionElim conclusion {:?} matches neither conjunct of {:?}",
1536 tree.conclusion, conj_premise.conclusion
1537 )));
1538 }
1539 let disc = certify(conj_premise, ctx)?;
1540 let left_type = proof_expr_to_type(&a)?;
1541 let right_type = proof_expr_to_type(&b)?;
1542 Ok(project_conjunct(&disc, &left_type, &right_type, take_left))
1543 }
1544
1545 InferenceRule::OracleVerification(detail) => Err(KernelError::CertificationError(format!(
1553 "oracle (Z3) results are not kernel-certifiable — they attest \
1554 satisfiability but produce no checkable proof term ({})",
1555 detail
1556 ))),
1557
1558 rule => Err(KernelError::CertificationError(format!(
1560 "Certification not implemented for {:?}",
1561 rule
1562 ))),
1563 }
1564}
1565
1566fn is_negation_of(neg: &ProofExpr, pos: &ProofExpr) -> bool {
1568 matches!(neg, ProofExpr::Not(inner) if inner.as_ref() == pos)
1569}
1570
1571fn unwrap_case_branch(tree: &DerivationTree) -> &DerivationTree {
1575 if matches!(tree.rule, InferenceRule::PremiseMatch) && tree.premises.len() == 1 {
1576 &tree.premises[0]
1577 } else {
1578 tree
1579 }
1580}
1581
1582fn build_equality_predicate(goal: &ProofExpr, replace_term: &ProofTerm) -> KernelResult<Term> {
1588 if let ProofExpr::Predicate { name, args, .. } = goal {
1591 if args.len() == 1 && &args[0] == replace_term {
1592 return Ok(Term::Global(name.clone()));
1593 }
1594 }
1595
1596 let goal_type = proof_expr_to_type(goal)?;
1598 let param_name = "_eq_var".to_string();
1599 let substituted = substitute_term_in_kernel(
1600 &goal_type,
1601 &proof_term_to_kernel_term(replace_term)?,
1602 &Term::Var(param_name.clone()),
1603 );
1604
1605 Ok(Term::Lambda {
1606 param: param_name,
1607 param_type: Box::new(Term::Global(term_domain(replace_term).to_string())),
1610 body: Box::new(substituted),
1611 })
1612}
1613
1614fn substitute_term_in_kernel(term: &Term, from: &Term, to: &Term) -> Term {
1616 if term == from {
1617 return to.clone();
1618 }
1619 match term {
1620 Term::App(f, a) => Term::App(
1621 Box::new(substitute_term_in_kernel(f, from, to)),
1622 Box::new(substitute_term_in_kernel(a, from, to)),
1623 ),
1624 Term::Pi {
1625 param,
1626 param_type,
1627 body_type,
1628 } => Term::Pi {
1629 param: param.clone(),
1630 param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
1631 body_type: Box::new(substitute_term_in_kernel(body_type, from, to)),
1632 },
1633 Term::Lambda {
1634 param,
1635 param_type,
1636 body,
1637 } => Term::Lambda {
1638 param: param.clone(),
1639 param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
1640 body: Box::new(substitute_term_in_kernel(body, from, to)),
1641 },
1642 Term::Match {
1643 discriminant,
1644 motive,
1645 cases,
1646 } => Term::Match {
1647 discriminant: Box::new(substitute_term_in_kernel(discriminant, from, to)),
1648 motive: Box::new(substitute_term_in_kernel(motive, from, to)),
1649 cases: cases
1650 .iter()
1651 .map(|c| substitute_term_in_kernel(c, from, to))
1652 .collect(),
1653 },
1654 Term::Fix { name, body } => Term::Fix {
1655 name: name.clone(),
1656 body: Box::new(substitute_term_in_kernel(body, from, to)),
1657 },
1658 other => other.clone(),
1659 }
1660}
1661
1662fn project_conjunct(
1671 disc: &Term,
1672 left_type: &Term,
1673 right_type: &Term,
1674 take_left: bool,
1675) -> Term {
1676 let chosen = if take_left { left_type } else { right_type };
1677 let result = if take_left { "__l" } else { "__r" };
1678 let case = Term::Lambda {
1679 param: "__l".to_string(),
1680 param_type: Box::new(left_type.clone()),
1681 body: Box::new(Term::Lambda {
1682 param: "__r".to_string(),
1683 param_type: Box::new(right_type.clone()),
1684 body: Box::new(Term::Var(result.to_string())),
1685 }),
1686 };
1687 Term::Match {
1688 discriminant: Box::new(disc.clone()),
1689 motive: Box::new(chosen.clone()),
1694 cases: vec![case],
1695 }
1696}
1697
1698fn collect_conjunct_hyps(expr: &ProofExpr, witness: &Term) -> KernelResult<Vec<(ProofExpr, Term)>> {
1707 match expr {
1708 ProofExpr::And(l, r) => {
1709 let left_type = proof_expr_to_type(l)?;
1710 let right_type = proof_expr_to_type(r)?;
1711
1712 let left_proj = project_conjunct(witness, &left_type, &right_type, true);
1713 let right_proj = project_conjunct(witness, &left_type, &right_type, false);
1714
1715 let mut hyps = collect_conjunct_hyps(l, &left_proj)?;
1716 hyps.extend(collect_conjunct_hyps(r, &right_proj)?);
1717 Ok(hyps)
1718 }
1719 _ => Ok(vec![(expr.clone(), witness.clone())]),
1720 }
1721}
1722
1723fn certify_hypothesis(conclusion: &ProofExpr, ctx: &CertificationContext) -> KernelResult<Term> {
1725 if let Some(ih_term) = ctx.get_ih_term(conclusion) {
1727 return Ok(ih_term);
1728 }
1729
1730 if let Some(hyp) = ctx.get_local_hyp(conclusion) {
1733 return Ok(hyp);
1734 }
1735
1736 match conclusion {
1737 ProofExpr::Atom(name) => {
1738 if ctx.is_local(name) {
1740 return Ok(Term::Var(name.clone()));
1741 }
1742 if ctx.kernel_ctx.get_global(name).is_some() {
1744 Ok(Term::Global(name.clone()))
1745 } else {
1746 Err(KernelError::CertificationError(format!(
1747 "Unknown hypothesis: {}",
1748 name
1749 )))
1750 }
1751 }
1752 ProofExpr::Predicate { name, args, .. } => {
1754 let mut target_type = Term::Global(name.clone());
1758 for arg in args {
1759 let arg_term = proof_term_to_kernel_term_entity(arg)?;
1760 target_type = Term::App(Box::new(target_type), Box::new(arg_term));
1761 }
1762
1763 for (decl_name, decl_type) in ctx.kernel_ctx.iter_declarations() {
1765 if types_structurally_match(&target_type, decl_type) {
1766 return Ok(Term::Global(decl_name.to_string()));
1767 }
1768 }
1769
1770 Err(KernelError::CertificationError(format!(
1771 "Cannot find hypothesis with type: {:?}",
1772 conclusion
1773 )))
1774 }
1775 _ => {
1779 let target_type = proof_expr_to_type_ctx(conclusion, ctx.kernel_ctx).map_err(|_| {
1782 KernelError::CertificationError(format!(
1783 "Cannot certify hypothesis: {:?}",
1784 conclusion
1785 ))
1786 })?;
1787
1788 for (name, decl_type) in ctx.kernel_ctx.iter_declarations() {
1789 if types_structurally_match(&target_type, decl_type) {
1790 return Ok(Term::Global(name.to_string()));
1791 }
1792 }
1793
1794 Err(KernelError::CertificationError(format!(
1795 "Cannot find hypothesis with type matching: {:?}",
1796 conclusion
1797 )))
1798 }
1799 }
1800}
1801
1802fn types_structurally_match(a: &Term, b: &Term) -> bool {
1805 types_alpha_equiv(a, b, &mut Vec::new())
1807}
1808
1809fn types_alpha_equiv(a: &Term, b: &Term, bindings: &mut Vec<(String, String)>) -> bool {
1812 match (a, b) {
1813 (Term::Sort(u1), Term::Sort(u2)) => u1 == u2,
1814 (Term::Var(v1), Term::Var(v2)) => {
1815 for (bound_a, bound_b) in bindings.iter().rev() {
1817 if v1 == bound_a {
1818 return v2 == bound_b;
1819 }
1820 if v2 == bound_b {
1821 return false; }
1823 }
1824 v1 == v2
1826 }
1827 (Term::Global(g1), Term::Global(g2)) => g1 == g2,
1828 (Term::Lit(l1), Term::Lit(l2)) => l1 == l2,
1829 (Term::App(f1, a1), Term::App(f2, a2)) => {
1830 types_alpha_equiv(f1, f2, bindings) && types_alpha_equiv(a1, a2, bindings)
1831 }
1832 (
1833 Term::Pi {
1834 param: p1,
1835 param_type: pt1,
1836 body_type: bt1,
1837 },
1838 Term::Pi {
1839 param: p2,
1840 param_type: pt2,
1841 body_type: bt2,
1842 },
1843 ) => {
1844 if !types_alpha_equiv(pt1, pt2, bindings) {
1846 return false;
1847 }
1848 bindings.push((p1.clone(), p2.clone()));
1850 let result = types_alpha_equiv(bt1, bt2, bindings);
1851 bindings.pop();
1852 result
1853 }
1854 (
1855 Term::Lambda {
1856 param: p1,
1857 param_type: pt1,
1858 body: b1,
1859 },
1860 Term::Lambda {
1861 param: p2,
1862 param_type: pt2,
1863 body: b2,
1864 },
1865 ) => {
1866 if !types_alpha_equiv(pt1, pt2, bindings) {
1868 return false;
1869 }
1870 bindings.push((p1.clone(), p2.clone()));
1872 let result = types_alpha_equiv(b1, b2, bindings);
1873 bindings.pop();
1874 result
1875 }
1876 _ => false,
1877 }
1878}
1879
1880fn extract_and_types(conclusion: &ProofExpr) -> KernelResult<(Term, Term)> {
1882 match conclusion {
1883 ProofExpr::And(p, q) => {
1884 let p_term = proof_expr_to_type(p)?;
1885 let q_term = proof_expr_to_type(q)?;
1886 Ok((p_term, q_term))
1887 }
1888 _ => Err(KernelError::CertificationError(format!(
1889 "Expected And, got {:?}",
1890 conclusion
1891 ))),
1892 }
1893}
1894
1895fn param_domain(ctx: &Context, name: &str, pos: usize) -> Option<String> {
1900 let mut ty = ctx.get_global(name)?;
1901 let mut i = 0;
1902 while let Term::Pi { param_type, body_type, .. } = ty {
1903 if i == pos {
1904 return match param_type.as_ref() {
1905 Term::Global(g) => Some(g.clone()),
1906 _ => None,
1907 };
1908 }
1909 ty = body_type;
1910 i += 1;
1911 }
1912 None
1913}
1914
1915fn collect_var_domains_term(t: &ProofTerm, variable: &str, ctx: &Context, out: &mut Vec<String>) {
1920 match t {
1921 ProofTerm::Function(name, args) => {
1922 for (j, a) in args.iter().enumerate() {
1923 if matches!(a, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == variable) {
1924 if let Some(g) = param_domain(ctx, name, j) {
1925 out.push(g);
1926 }
1927 }
1928 collect_var_domains_term(a, variable, ctx, out);
1929 }
1930 }
1931 ProofTerm::Group(args) => {
1932 for a in args {
1933 collect_var_domains_term(a, variable, ctx, out);
1934 }
1935 }
1936 _ => {}
1937 }
1938}
1939
1940fn collect_var_domains_expr(e: &ProofExpr, variable: &str, ctx: &Context, out: &mut Vec<String>) {
1944 match e {
1945 ProofExpr::Predicate { name, args, .. } => {
1946 for (j, a) in args.iter().enumerate() {
1947 if matches!(a, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == variable) {
1948 if let Some(g) = param_domain(ctx, name, j) {
1949 out.push(g);
1950 }
1951 }
1952 collect_var_domains_term(a, variable, ctx, out);
1953 }
1954 }
1955 ProofExpr::Identity(l, r) => {
1956 collect_var_domains_term(l, variable, ctx, out);
1957 collect_var_domains_term(r, variable, ctx, out);
1958 }
1959 ProofExpr::And(l, r)
1960 | ProofExpr::Or(l, r)
1961 | ProofExpr::Implies(l, r)
1962 | ProofExpr::Iff(l, r) => {
1963 collect_var_domains_expr(l, variable, ctx, out);
1964 collect_var_domains_expr(r, variable, ctx, out);
1965 }
1966 ProofExpr::Not(p)
1967 | ProofExpr::ForAll { body: p, .. }
1968 | ProofExpr::Exists { body: p, .. } => collect_var_domains_expr(p, variable, ctx, out),
1969 _ => {}
1970 }
1971}
1972
1973fn binder_domain(variable: &str, body: &ProofExpr, ctx: &Context) -> String {
1980 let mut domains = Vec::new();
1981 collect_var_domains_expr(body, variable, ctx, &mut domains);
1982 domains
1983 .into_iter()
1984 .find(|d| d != "Entity")
1985 .unwrap_or_else(|| "Entity".to_string())
1986}
1987
1988pub(crate) fn proof_expr_to_type_ctx(expr: &ProofExpr, ctx: &Context) -> KernelResult<Term> {
1993 if let ProofExpr::ForAll { variable, body } = expr {
1994 let dom = binder_domain(variable, body, ctx);
1995 let body_type = proof_expr_to_type_ctx(body, ctx)?;
1996 return Ok(Term::Pi {
1997 param: variable.clone(),
1998 param_type: Box::new(Term::Global(dom)),
1999 body_type: Box::new(body_type),
2000 });
2001 }
2002 proof_expr_to_type(expr)
2003}
2004
2005pub(crate) fn proof_expr_to_type(expr: &ProofExpr) -> KernelResult<Term> {
2006 match expr {
2007 ProofExpr::Atom(name) if name == "⊥" || name == "False" || name == "false" => {
2010 Ok(Term::Global("False".to_string()))
2011 }
2012 ProofExpr::Atom(name) => Ok(Term::Global(name.clone())),
2013 ProofExpr::Not(p) => Ok(Term::Pi {
2018 param: "_".to_string(),
2019 param_type: Box::new(proof_expr_to_type(p)?),
2020 body_type: Box::new(Term::Global("False".to_string())),
2021 }),
2022 ProofExpr::And(p, q) => Ok(Term::App(
2023 Box::new(Term::App(
2024 Box::new(Term::Global("And".to_string())),
2025 Box::new(proof_expr_to_type(p)?),
2026 )),
2027 Box::new(proof_expr_to_type(q)?),
2028 )),
2029 ProofExpr::Or(p, q) => Ok(Term::App(
2030 Box::new(Term::App(
2031 Box::new(Term::Global("Or".to_string())),
2032 Box::new(proof_expr_to_type(p)?),
2033 )),
2034 Box::new(proof_expr_to_type(q)?),
2035 )),
2036 ProofExpr::Implies(p, q) => Ok(Term::Pi {
2037 param: "_".to_string(),
2038 param_type: Box::new(proof_expr_to_type(p)?),
2039 body_type: Box::new(proof_expr_to_type(q)?),
2040 }),
2041 ProofExpr::Iff(p, q) => {
2044 let pt = proof_expr_to_type(p)?;
2045 let qt = proof_expr_to_type(q)?;
2046 let pq = Term::Pi {
2047 param: "_".to_string(),
2048 param_type: Box::new(pt.clone()),
2049 body_type: Box::new(qt.clone()),
2050 };
2051 let qp = Term::Pi {
2052 param: "_".to_string(),
2053 param_type: Box::new(qt),
2054 body_type: Box::new(pt),
2055 };
2056 Ok(Term::App(
2057 Box::new(Term::App(Box::new(Term::Global("And".to_string())), Box::new(pq))),
2058 Box::new(qp),
2059 ))
2060 }
2061 ProofExpr::ForAll { variable, body } => {
2063 let body_type = proof_expr_to_type(body)?;
2064 Ok(Term::Pi {
2065 param: variable.clone(),
2066 param_type: Box::new(Term::Global("Entity".to_string())),
2067 body_type: Box::new(body_type),
2068 })
2069 }
2070 ProofExpr::Predicate { name, args, .. } => {
2074 let mut result = Term::Global(name.clone());
2075 for arg in args {
2076 let arg_term = proof_term_to_kernel_term_entity(arg)?;
2077 result = Term::App(Box::new(result), Box::new(arg_term));
2078 }
2079 Ok(result)
2080 }
2081 ProofExpr::Identity(l, r) => {
2086 let l_term = proof_term_to_kernel_term(l)?;
2087 let r_term = proof_term_to_kernel_term(r)?;
2088 let domain = Term::Global(identity_domain(l, r).to_string());
2089 Ok(Term::App(
2090 Box::new(Term::App(
2091 Box::new(Term::App(
2092 Box::new(Term::Global("Eq".to_string())),
2093 Box::new(domain),
2094 )),
2095 Box::new(l_term),
2096 )),
2097 Box::new(r_term),
2098 ))
2099 }
2100 ProofExpr::Exists { variable, body } => {
2104 let var_type = Term::Global("Entity".to_string());
2105 let body_type = proof_expr_to_type(body)?;
2106
2107 let predicate = Term::Lambda {
2109 param: variable.clone(),
2110 param_type: Box::new(var_type.clone()),
2111 body: Box::new(body_type),
2112 };
2113
2114 Ok(Term::App(
2115 Box::new(Term::App(
2116 Box::new(Term::Global("Ex".to_string())),
2117 Box::new(var_type),
2118 )),
2119 Box::new(predicate),
2120 ))
2121 }
2122 ProofExpr::Temporal { operator, body } => Ok(Term::App(
2128 Box::new(Term::Global(operator.clone())),
2129 Box::new(proof_expr_to_type(body)?),
2130 )),
2131 _ => Err(KernelError::CertificationError(format!(
2132 "Cannot convert {:?} to kernel type",
2133 expr
2134 ))),
2135 }
2136}
2137
2138fn kernel_arith_name(name: &str) -> &str {
2148 match name {
2149 "Add" => "add",
2150 "Sub" => "sub",
2151 "Mul" => "mul",
2152 "Div" => "div",
2153 other => other,
2154 }
2155}
2156
2157fn is_arith_builtin(name: &str) -> bool {
2162 matches!(
2163 name,
2164 "le" | "lt" | "ge" | "gt" | "add" | "sub" | "mul" | "div" | "mod" | "Add" | "Sub"
2165 | "Mul" | "Div"
2166 )
2167}
2168
2169fn proof_term_to_kernel_term_entity(term: &ProofTerm) -> KernelResult<Term> {
2180 match term {
2181 ProofTerm::Constant(name) => Ok(Term::Global(name.clone())),
2182 ProofTerm::Variable(name) | ProofTerm::BoundVarRef(name) => Ok(Term::Var(name.clone())),
2183 ProofTerm::Function(name, args) => {
2184 let arith = is_arith_builtin(name);
2185 let mut result = Term::Global(kernel_arith_name(name).to_string());
2186 for arg in args {
2187 let arg_term = if arith {
2188 proof_term_to_kernel_term(arg)?
2189 } else {
2190 proof_term_to_kernel_term_entity(arg)?
2191 };
2192 result = Term::App(Box::new(result), Box::new(arg_term));
2193 }
2194 Ok(result)
2195 }
2196 ProofTerm::Group(_) => Err(KernelError::CertificationError(
2197 "Cannot convert Group to kernel term".to_string(),
2198 )),
2199 }
2200}
2201
2202pub(crate) fn proof_term_to_kernel_term(term: &ProofTerm) -> KernelResult<Term> {
2203 match term {
2204 ProofTerm::Constant(name) => match name.parse::<i64>() {
2208 Ok(n) => Ok(Term::Lit(logicaffeine_kernel::Literal::Int(n))),
2209 Err(_) => Ok(Term::Global(name.clone())),
2210 },
2211 ProofTerm::Variable(name) => Ok(Term::Var(name.clone())),
2212 ProofTerm::BoundVarRef(name) => Ok(Term::Var(name.clone())),
2213 ProofTerm::Function(name, args) => {
2214 let mut result = Term::Global(kernel_arith_name(name).to_string());
2217 for arg in args {
2218 let arg_term = proof_term_to_kernel_term(arg)?;
2219 result = Term::App(Box::new(result), Box::new(arg_term));
2220 }
2221 Ok(result)
2222 }
2223 ProofTerm::Group(_) => Err(KernelError::CertificationError(
2224 "Cannot convert Group to kernel term".to_string(),
2225 )),
2226 }
2227}
2228
2229fn decidable_instance_for(prop: &Term) -> Option<Term> {
2233 let Term::App(f1, b) = prop else { return None };
2234 let Term::App(f2, a) = f1.as_ref() else { return None };
2235 let Term::App(eq, dom) = f2.as_ref() else { return None };
2236 let Term::Global(eq_name) = eq.as_ref() else { return None };
2237 if eq_name != "Eq" {
2238 return None;
2239 }
2240 let Term::Global(domain) = dom.as_ref() else { return None };
2241 let inst = match domain.as_str() {
2242 "Bool" => "decEqBool",
2243 "Nat" => "decEqNat",
2244 _ => return None,
2245 };
2246 Some(Term::App(
2247 Box::new(Term::App(
2248 Box::new(Term::Global(inst.to_string())),
2249 a.clone(),
2250 )),
2251 b.clone(),
2252 ))
2253}
2254
2255fn identity_domain(l: &ProofTerm, r: &ProofTerm) -> &'static str {
2260 match (term_domain(l), term_domain(r)) {
2261 ("Bool", _) | (_, "Bool") => "Bool",
2262 ("Int", _) | (_, "Int") => "Int",
2263 _ => "Entity",
2264 }
2265}
2266
2267fn term_domain(t: &ProofTerm) -> &'static str {
2270 match t {
2271 ProofTerm::Function(n, _) if matches!(n.as_str(), "le" | "lt" | "ge" | "gt") => "Bool",
2272 ProofTerm::Function(n, _)
2273 if matches!(
2274 n.as_str(),
2275 "add" | "sub" | "mul" | "div" | "mod" | "Add" | "Sub" | "Mul" | "Div"
2276 ) =>
2277 {
2278 "Int"
2279 }
2280 ProofTerm::Constant(s) if s == "true" || s == "false" => "Bool",
2281 ProofTerm::Constant(s) if s.parse::<i64>().is_ok() => "Int",
2282 _ => "Entity",
2283 }
2284}
2285
2286fn le_terms(expr: &ProofExpr) -> KernelResult<(ProofTerm, ProofTerm)> {
2289 if let ProofExpr::Identity(lhs, _) = expr {
2290 if let ProofTerm::Function(name, args) = lhs {
2291 if name == "le" && args.len() == 2 {
2292 return Ok((args[0].clone(), args[1].clone()));
2293 }
2294 }
2295 }
2296 Err(KernelError::CertificationError(format!(
2297 "expected an `le(a, b) = true` inequality, got {:?}",
2298 expr
2299 )))
2300}
2301
2302fn add_terms(t: &ProofTerm) -> KernelResult<(ProofTerm, ProofTerm)> {
2304 binop_terms("add", t)
2305}
2306
2307fn binop_terms(op: &str, t: &ProofTerm) -> KernelResult<(ProofTerm, ProofTerm)> {
2309 if let ProofTerm::Function(name, args) = t {
2310 if name == op && args.len() == 2 {
2311 return Ok((args[0].clone(), args[1].clone()));
2312 }
2313 }
2314 Err(KernelError::CertificationError(format!(
2315 "expected `{}(x, y)`, got {:?}",
2316 op, t
2317 )))
2318}
2319
2320fn build_bool_discriminator(h: Term) -> Term {
2325 let g = |s: &str| Term::Global(s.to_string());
2326 let motive = Term::Lambda {
2328 param: "b".to_string(),
2329 param_type: Box::new(g("Bool")),
2330 body: Box::new(Term::Match {
2331 discriminant: Box::new(Term::Var("b".to_string())),
2332 motive: Box::new(Term::Lambda {
2333 param: "_".to_string(),
2334 param_type: Box::new(g("Bool")),
2335 body: Box::new(Term::Sort(logicaffeine_kernel::Universe::Prop)),
2336 }),
2337 cases: vec![g("False"), g("True")],
2338 }),
2339 };
2340 [g("Bool"), g("false"), motive, g("I"), g("true"), h]
2341 .into_iter()
2342 .fold(g("Eq_rec"), |acc, arg| Term::App(Box::new(acc), Box::new(arg)))
2343}
2344
2345fn extract_motive_body(conclusion: &ProofExpr, var_name: &str) -> KernelResult<Term> {
2352 match conclusion {
2353 ProofExpr::ForAll { variable, body } if variable == var_name => {
2354 proof_expr_to_type(body)
2356 }
2357 _ => Err(KernelError::CertificationError(format!(
2358 "StructuralInduction conclusion must be ForAll over {}, got {:?}",
2359 var_name, conclusion
2360 ))),
2361 }
2362}
2363
2364fn compute_ih_conclusion(
2367 conclusion: &ProofExpr,
2368 orig_var: &str,
2369 step_var: &str,
2370) -> KernelResult<ProofExpr> {
2371 match conclusion {
2372 ProofExpr::ForAll { body, .. } => Ok(substitute_var_in_expr(body, orig_var, step_var)),
2373 _ => Err(KernelError::CertificationError(
2374 "Expected ForAll for IH computation".to_string(),
2375 )),
2376 }
2377}
2378
2379fn constructor_arg_types(ctx: &Context, constructor: &str) -> KernelResult<Vec<Term>> {
2385 let mut ty = ctx
2386 .get_global(constructor)
2387 .ok_or_else(|| {
2388 KernelError::CertificationError(format!("unknown constructor {}", constructor))
2389 })?
2390 .clone();
2391 let mut args = Vec::new();
2392 while let Term::Pi {
2393 param_type,
2394 body_type,
2395 ..
2396 } = ty
2397 {
2398 args.push(*param_type);
2399 ty = *body_type;
2400 }
2401 Ok(args)
2402}
2403
2404fn build_motive(ind_type: &str, result_type: &Term, motive_param: &str) -> Term {
2408 Term::Lambda {
2409 param: motive_param.to_string(),
2410 param_type: Box::new(Term::Global(ind_type.to_string())),
2411 body: Box::new(result_type.clone()),
2412 }
2413}
2414
2415fn substitute_var_in_expr(expr: &ProofExpr, from: &str, to: &str) -> ProofExpr {
2417 match expr {
2418 ProofExpr::Atom(s) if s == from => ProofExpr::Atom(to.to_string()),
2419 ProofExpr::Atom(s) => ProofExpr::Atom(s.clone()),
2420
2421 ProofExpr::TypedVar { name, typename } => ProofExpr::TypedVar {
2422 name: if name == from {
2423 to.to_string()
2424 } else {
2425 name.clone()
2426 },
2427 typename: typename.clone(),
2428 },
2429
2430 ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
2431 name: name.clone(),
2432 args: args
2433 .iter()
2434 .map(|a| substitute_var_in_term(a, from, to))
2435 .collect(),
2436 world: world.clone(),
2437 },
2438
2439 ProofExpr::Identity(l, r) => ProofExpr::Identity(
2440 substitute_var_in_term(l, from, to),
2441 substitute_var_in_term(r, from, to),
2442 ),
2443
2444 ProofExpr::And(l, r) => ProofExpr::And(
2445 Box::new(substitute_var_in_expr(l, from, to)),
2446 Box::new(substitute_var_in_expr(r, from, to)),
2447 ),
2448
2449 ProofExpr::Or(l, r) => ProofExpr::Or(
2450 Box::new(substitute_var_in_expr(l, from, to)),
2451 Box::new(substitute_var_in_expr(r, from, to)),
2452 ),
2453
2454 ProofExpr::Implies(l, r) => ProofExpr::Implies(
2455 Box::new(substitute_var_in_expr(l, from, to)),
2456 Box::new(substitute_var_in_expr(r, from, to)),
2457 ),
2458
2459 ProofExpr::Iff(l, r) => ProofExpr::Iff(
2460 Box::new(substitute_var_in_expr(l, from, to)),
2461 Box::new(substitute_var_in_expr(r, from, to)),
2462 ),
2463
2464 ProofExpr::Not(inner) => {
2465 ProofExpr::Not(Box::new(substitute_var_in_expr(inner, from, to)))
2466 }
2467
2468 ProofExpr::ForAll { variable, body } if variable != from => ProofExpr::ForAll {
2470 variable: variable.clone(),
2471 body: Box::new(substitute_var_in_expr(body, from, to)),
2472 },
2473
2474 ProofExpr::Exists { variable, body } if variable != from => ProofExpr::Exists {
2475 variable: variable.clone(),
2476 body: Box::new(substitute_var_in_expr(body, from, to)),
2477 },
2478
2479 ProofExpr::Lambda { variable, body } if variable != from => ProofExpr::Lambda {
2480 variable: variable.clone(),
2481 body: Box::new(substitute_var_in_expr(body, from, to)),
2482 },
2483
2484 ProofExpr::App(f, a) => ProofExpr::App(
2485 Box::new(substitute_var_in_expr(f, from, to)),
2486 Box::new(substitute_var_in_expr(a, from, to)),
2487 ),
2488
2489 ProofExpr::Term(t) => ProofExpr::Term(substitute_var_in_term(t, from, to)),
2490
2491 ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
2492 name: name.clone(),
2493 args: args
2494 .iter()
2495 .map(|a| substitute_var_in_expr(a, from, to))
2496 .collect(),
2497 },
2498
2499 other => other.clone(),
2501 }
2502}
2503
2504fn substitute_var_in_term(term: &ProofTerm, from: &str, to: &str) -> ProofTerm {
2506 match term {
2507 ProofTerm::Variable(s) if s == from => ProofTerm::Variable(to.to_string()),
2508 ProofTerm::Variable(s) => ProofTerm::Variable(s.clone()),
2509 ProofTerm::Constant(s) => ProofTerm::Constant(s.clone()),
2510 ProofTerm::BoundVarRef(s) if s == from => ProofTerm::BoundVarRef(to.to_string()),
2511 ProofTerm::BoundVarRef(s) => ProofTerm::BoundVarRef(s.clone()),
2512 ProofTerm::Function(name, args) => ProofTerm::Function(
2513 name.clone(),
2514 args.iter()
2515 .map(|a| substitute_var_in_term(a, from, to))
2516 .collect(),
2517 ),
2518 ProofTerm::Group(terms) => ProofTerm::Group(
2519 terms
2520 .iter()
2521 .map(|t| substitute_var_in_term(t, from, to))
2522 .collect(),
2523 ),
2524 }
2525}
2526
2527#[cfg(test)]
2528mod canonical_arith_boundary_tests {
2529 use super::*;
2530
2531 #[test]
2532 fn kernel_arith_name_maps_canonical_to_ring() {
2533 assert_eq!(kernel_arith_name("Add"), "add");
2536 assert_eq!(kernel_arith_name("Sub"), "sub");
2537 assert_eq!(kernel_arith_name("Mul"), "mul");
2538 assert_eq!(kernel_arith_name("Div"), "div");
2539 assert_eq!(kernel_arith_name("Score"), "Score");
2541 assert_eq!(kernel_arith_name("add"), "add");
2542 }
2543
2544 #[test]
2545 fn canonical_add_lowers_to_kernel_ring_head() {
2546 let t = proof_term_to_kernel_term(&ProofTerm::Function(
2549 "Add".to_string(),
2550 vec![
2551 ProofTerm::Variable("x".to_string()),
2552 ProofTerm::Variable("y".to_string()),
2553 ],
2554 ))
2555 .expect("Add term converts to a kernel term");
2556 match t {
2557 Term::App(f, _) => match *f {
2558 Term::App(g, _) => match *g {
2559 Term::Global(name) => assert_eq!(
2560 name, "add",
2561 "canonical Add must lower to the kernel ring name 'add'; got {name}"
2562 ),
2563 other => panic!("expected a Global head, got {:?}", other),
2564 },
2565 other => panic!("expected a nested App, got {:?}", other),
2566 },
2567 other => panic!("expected an App, got {:?}", other),
2568 }
2569 }
2570
2571 #[test]
2583 fn numeric_predicate_argument_lowers_to_entity_label() {
2584 let in_beta_2002 = ProofExpr::Predicate {
2585 name: "in".to_string(),
2586 args: vec![
2587 ProofTerm::Constant("Beta".to_string()),
2588 ProofTerm::Constant("2002".to_string()),
2589 ],
2590 world: None,
2591 };
2592 let t = proof_expr_to_type(&in_beta_2002).expect("predicate lowers to a kernel type");
2593 match t {
2595 Term::App(_, year) => assert!(
2596 matches!(*year, Term::Global(ref n) if n == "2002"),
2597 "year `2002` in a relation must be an Entity Global, got {:?}",
2598 year
2599 ),
2600 other => panic!("expected `(in Beta) 2002` application, got {:?}", other),
2601 }
2602 }
2603
2604 #[test]
2607 fn numeric_arithmetic_operand_stays_int_literal() {
2608 use logicaffeine_kernel::Literal;
2609 let le_2_5 = ProofTerm::Function(
2610 "le".to_string(),
2611 vec![
2612 ProofTerm::Constant("2".to_string()),
2613 ProofTerm::Constant("5".to_string()),
2614 ],
2615 );
2616 let t = proof_term_to_kernel_term(&le_2_5).expect("le term converts");
2617 match t {
2618 Term::App(_, five) => assert!(
2619 matches!(*five, Term::Lit(Literal::Int(5))),
2620 "an arithmetic operand must stay an Int literal, got {:?}",
2621 five
2622 ),
2623 other => panic!("expected `(le 2) 5` application, got {:?}", other),
2624 }
2625 }
2626
2627 #[test]
2630 fn entity_lowering_is_numeric_aware_but_arith_preserving() {
2631 use logicaffeine_kernel::Literal;
2632 assert!(
2633 matches!(
2634 proof_term_to_kernel_term_entity(&ProofTerm::Constant("2004".to_string())).unwrap(),
2635 Term::Global(ref n) if n == "2004"
2636 ),
2637 "a bare numeric in an entity position is an Entity label"
2638 );
2639 let add = ProofTerm::Function(
2641 "add".to_string(),
2642 vec![
2643 ProofTerm::Constant("2".to_string()),
2644 ProofTerm::Constant("3".to_string()),
2645 ],
2646 );
2647 match proof_term_to_kernel_term_entity(&add).unwrap() {
2648 Term::App(_, three) => assert!(
2649 matches!(*three, Term::Lit(Literal::Int(3))),
2650 "an arithmetic operand stays Int even inside an entity position, got {:?}",
2651 three
2652 ),
2653 other => panic!("expected `(add 2) 3`, got {:?}", other),
2654 }
2655 }
2656}