1use serde::{Deserialize, Serialize};
35
36use std::collections::{BTreeMap, BTreeSet, HashMap};
37
38use crate::prelude::StandardLibrary;
39use crate::{
40 double_check, infer_type, is_subtype, Context, DoubleCheck, KernelError, Term, Universe,
41};
42
43pub const PRELUDE_VERSION: &str = "logos-coc-6";
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68#[serde(deny_unknown_fields)]
69pub struct Certificate {
70 pub proof_term: Term,
72 pub claimed_type: Term,
74 pub prelude_version: String,
76}
77
78impl Certificate {
79 pub fn new(proof_term: Term, claimed_type: Term) -> Self {
81 Self {
82 proof_term: proof_term.into_stripped(),
83 claimed_type: claimed_type.into_stripped(),
84 prelude_version: PRELUDE_VERSION.to_string(),
85 }
86 }
87
88 pub fn axioms_used(&self) -> AxiomUsage {
100 let ctx = StandardLibrary::context();
101 let mut usage = axiom_dependencies(ctx, &self.proof_term);
102 usage.absorb(axiom_dependencies(ctx, &self.claimed_type));
103 usage
104 }
105}
106
107pub fn recheck(cert: &Certificate) -> Result<(), KernelError> {
117 if cert.prelude_version != PRELUDE_VERSION {
118 return Err(KernelError::CertificationError(format!(
119 "certificate was minted against prelude '{}', but this checker is '{}'",
120 cert.prelude_version, PRELUDE_VERSION
121 )));
122 }
123
124 let ctx = StandardLibrary::context();
127
128 let mut usage = axiom_dependencies(ctx, &cert.proof_term);
133 usage.absorb(axiom_dependencies(ctx, &cert.claimed_type));
134 if usage.is_admitted() {
135 return Err(KernelError::CertificationError(
136 usage.render("this certificate"),
137 ));
138 }
139
140 let inferred = infer_type(ctx, &cert.proof_term)?;
142
143 if is_subtype(ctx, &inferred, &cert.claimed_type) {
145 Ok(())
146 } else {
147 Err(KernelError::CertificationError(format!(
148 "certificate term proves a different proposition: has type {:?}, claims {:?}",
149 inferred, cert.claimed_type
150 )))
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186#[serde(deny_unknown_fields)]
187pub struct CertDeclaration {
188 pub name: String,
189 pub ty: Term,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195#[serde(deny_unknown_fields)]
196pub struct CertDefinition {
197 pub name: String,
198 pub ty: Term,
199 pub body: Term,
200}
201
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210#[serde(deny_unknown_fields)]
211pub struct TheoremCertificate {
212 pub theorem: String,
214 pub proof_term: Term,
216 pub claimed_type: Term,
218 pub declarations: Vec<CertDeclaration>,
221 pub definitions: Vec<CertDefinition>,
223 pub prelude_version: String,
225}
226
227pub use crate::prelude::{
240 axiom_dependencies, axiom_dependencies_of, is_admitted_axiom, AxiomUsage, ADMITTED_AXIOMS,
241};
242
243impl TheoremCertificate {
244 pub fn axioms_used(&self) -> AxiomUsage {
254 let ctx = self.rebuild_context().0;
255 theorem_axioms(&ctx, &self.proof_term, &self.claimed_type)
256 }
257}
258
259fn theorem_axioms(ctx: &Context, proof_term: &Term, claimed_type: &Term) -> AxiomUsage {
264 let mut usage = axiom_dependencies(ctx, proof_term);
265 usage.absorb(axiom_dependencies(ctx, claimed_type));
266 usage
267}
268
269#[derive(Debug, Clone, PartialEq)]
271pub struct TheoremRecheck {
272 pub rechecks: bool,
282 pub two_kernel_agreement: bool,
288 pub error: Option<String>,
290 pub axioms: AxiomUsage,
302}
303
304impl TheoremCertificate {
305 pub fn new(
307 theorem: impl Into<String>,
308 proof_term: Term,
309 claimed_type: Term,
310 declarations: Vec<CertDeclaration>,
311 definitions: Vec<CertDefinition>,
312 ) -> Self {
313 Self {
314 theorem: theorem.into(),
315 proof_term: proof_term.into_stripped(),
316 claimed_type: claimed_type.into_stripped(),
317 declarations: declarations
318 .into_iter()
319 .map(|d| CertDeclaration {
320 name: d.name,
321 ty: d.ty.into_stripped(),
322 })
323 .collect(),
324 definitions: definitions
325 .into_iter()
326 .map(|d| CertDefinition {
327 name: d.name,
328 ty: d.ty.into_stripped(),
329 body: d.body.into_stripped(),
330 })
331 .collect(),
332 prelude_version: PRELUDE_VERSION.to_string(),
333 }
334 }
335
336 fn rebuild_context(&self) -> (Context, Vec<String>) {
363 let mut ctx = StandardLibrary::context().clone();
364 let mut shadowed: Vec<String> = Vec::new();
365 for decl in &self.declarations {
366 if ctx.get_global(&decl.name).is_some() {
367 shadowed.push(decl.name.clone());
368 continue;
369 }
370 ctx.add_declaration(&decl.name, decl.ty.clone());
371 }
372 for def in &self.definitions {
373 if ctx.get_global(&def.name).is_some() {
374 shadowed.push(def.name.clone());
375 continue;
376 }
377 ctx.add_definition(def.name.clone(), def.ty.clone(), def.body.clone());
378 }
379 (ctx, shadowed)
380 }
381}
382
383pub fn recheck_theorem(cert: &TheoremCertificate) -> TheoremRecheck {
408 if cert.prelude_version != PRELUDE_VERSION {
409 return TheoremRecheck {
410 rechecks: false,
411 two_kernel_agreement: false,
412 error: Some(format!(
413 "certificate was minted against prelude '{}', but this checker is '{}'",
414 cert.prelude_version, PRELUDE_VERSION
415 )),
416 axioms: AxiomUsage::default(),
417 };
418 }
419
420 let (ctx, shadowed) = cert.rebuild_context();
421 if !shadowed.is_empty() {
422 return TheoremRecheck {
423 rechecks: false,
424 two_kernel_agreement: false,
425 error: Some(format!(
426 "the disclosed delta REDEFINES constants the trusted base already owns: {}. A \
427 certificate may extend the prelude with its own vocabulary and premises; it may \
428 never replace an axiom or definition the prelude already has, because every name \
429 it discloses has to mean what the reader's own prelude says it means",
430 shadowed.join(", ")
431 )),
432 axioms: AxiomUsage::default(),
433 };
434 }
435
436 let axioms = theorem_axioms(&ctx, &cert.proof_term, &cert.claimed_type);
442 if axioms.is_admitted() {
443 return TheoremRecheck {
444 rechecks: false,
445 two_kernel_agreement: false,
446 error: Some(axioms.render(&cert.theorem)),
447 axioms,
448 };
449 }
450
451 let inferred = match infer_type(&ctx, &cert.proof_term) {
453 Ok(t) => t,
454 Err(e) => {
455 return TheoremRecheck {
456 rechecks: false,
457 two_kernel_agreement: false,
458 error: Some(format!("proof term did not type-check: {:?}", e)),
459 axioms,
460 };
461 }
462 };
463 if !is_subtype(&ctx, &inferred, &cert.claimed_type) {
464 return TheoremRecheck {
465 rechecks: false,
466 two_kernel_agreement: false,
467 error: Some(format!(
468 "certificate term proves a different proposition: has type {:?}, claims {:?}",
469 inferred, cert.claimed_type
470 )),
471 axioms,
472 };
473 }
474
475 match double_check(&ctx, &cert.proof_term) {
480 DoubleCheck::Agreed => TheoremRecheck {
481 rechecks: true,
482 two_kernel_agreement: true,
483 error: None,
484 axioms,
485 },
486 DoubleCheck::MainOnlyReCheckerIncomplete(_) => TheoremRecheck {
487 rechecks: true,
488 two_kernel_agreement: false,
489 error: None,
490 axioms,
491 },
492 DoubleCheck::Disagree(why) => TheoremRecheck {
493 rechecks: false,
494 two_kernel_agreement: false,
495 error: Some(format!(
496 "the two kernels DISAGREED on the proof term: {why}"
497 )),
498 axioms,
499 },
500 }
501}
502
503#[derive(Debug, Clone, PartialEq)]
509pub enum AnyCertificate {
510 Proof(Certificate),
512 Theorem(TheoremCertificate),
514 Environment(EnvironmentCertificate),
516}
517
518pub const RETIRED_PRELUDE_VERSIONS: &[&str] = &[
522 "logos-coc-1",
523 "logos-coc-2",
524 "logos-coc-3",
525 "logos-coc-4",
526 "logos-coc-5",
527];
528
529pub fn read_certificate(json: &str) -> Result<AnyCertificate, EnvironmentError> {
537 let value: serde_json::Value = serde_json::from_str(json).map_err(|e| {
538 EnvironmentError::new(
539 EnvironmentErrorCode::Malformed,
540 format!("not a JSON certificate: {e}"),
541 )
542 })?;
543 let object = value.as_object().ok_or_else(|| {
544 EnvironmentError::new(
545 EnvironmentErrorCode::Malformed,
546 "a certificate is a JSON object",
547 )
548 })?;
549 let prelude = object
550 .get("prelude_version")
551 .and_then(|v| v.as_str())
552 .map(str::to_string);
553 let certificate = if let Some(schema) = object.get("schema") {
554 match schema.as_str() {
555 Some(OPAQUE_ENVIRONMENT_SCHEMA) | Some(ENVIRONMENT_SCHEMA) => {
556 let environment: EnvironmentCertificate = serde_json::from_value(value.clone()).map_err(|e| {
557 EnvironmentError::new(EnvironmentErrorCode::Malformed, format!("{OPAQUE_ENVIRONMENT_SCHEMA}: {e}"))
558 })?;
559 environment.check_schema()?;
560 AnyCertificate::Environment(environment)
561 }
562 other => {
563 return Err(EnvironmentError::new(
564 EnvironmentErrorCode::UnsupportedSchema,
565 format!("unsupported certificate schema {other:?}; this checker reads {OPAQUE_ENVIRONMENT_SCHEMA}"),
566 ))
567 }
568 }
569 } else if object.contains_key("theorem") {
570 AnyCertificate::Theorem(serde_json::from_value(value.clone()).map_err(|e| {
571 EnvironmentError::new(
572 EnvironmentErrorCode::Malformed,
573 format!("theorem certificate: {e}"),
574 )
575 })?)
576 } else {
577 AnyCertificate::Proof(serde_json::from_value(value.clone()).map_err(|e| {
578 EnvironmentError::new(
579 EnvironmentErrorCode::Malformed,
580 format!("proof certificate: {e}"),
581 )
582 })?)
583 };
584 check_prelude_version(prelude.as_deref().unwrap_or(""))?;
585 Ok(certificate)
586}
587
588pub fn check_prelude_version(found: &str) -> Result<(), EnvironmentError> {
590 if found == PRELUDE_VERSION {
591 return Ok(());
592 }
593 let message = if RETIRED_PRELUDE_VERSIONS.contains(&found) {
594 format!(
595 "certificate was minted against the retired prelude '{found}'; this checker is \
596 '{PRELUDE_VERSION}'. The two axiom sets differ and no validated migration exists, so the \
597 certificate is incompatible: re-mint it against '{PRELUDE_VERSION}'"
598 )
599 } else {
600 format!("certificate was minted against prelude '{found}', but this checker is '{PRELUDE_VERSION}'")
601 };
602 Err(EnvironmentError::new(
603 EnvironmentErrorCode::IncompatiblePrelude,
604 message,
605 ))
606}
607
608pub const OPAQUE_ENVIRONMENT_SCHEMA: &str = "logos-environment-certificate/2";
640pub const ENVIRONMENT_SCHEMA: &str = "logos-environment-certificate/1";
642
643pub type NodeRef = u32;
645
646#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
648#[serde(rename_all = "snake_case", deny_unknown_fields)]
649pub enum TermNode {
650 Sort(Universe),
651 Var(String),
652 Global(String),
653 Const(String, Vec<Universe>),
654 Pi(String, NodeRef, NodeRef),
655 Lambda(String, NodeRef, NodeRef),
656 App(NodeRef, NodeRef),
657 Match(NodeRef, NodeRef, Vec<NodeRef>),
658 Fix(String, NodeRef),
659 MutualFix(Vec<(String, NodeRef)>, usize),
660 Let(String, NodeRef, NodeRef, NodeRef),
661 Lit(crate::Literal),
662 Hole,
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
668#[serde(rename_all = "snake_case")]
669pub enum EnvironmentBase {
670 Kernel,
673 StandardLibrary,
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
679#[serde(rename_all = "snake_case")]
680pub enum ConversionDiscipline {
681 Cumulative,
683 Exact,
686}
687
688#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
690#[serde(rename_all = "snake_case")]
691pub enum QuotientPrimitive {
692 Type,
693 Constructor,
694 Lift,
695 Induction,
696}
697
698impl QuotientPrimitive {
699 fn kind(self) -> crate::quotient::Kind {
700 use crate::quotient::Kind;
701 match self {
702 QuotientPrimitive::Type => Kind::Type,
703 QuotientPrimitive::Constructor => Kind::Constructor,
704 QuotientPrimitive::Lift => Kind::Lift,
705 QuotientPrimitive::Induction => Kind::Induction,
706 }
707 }
708 fn of(kind: crate::quotient::Kind) -> Self {
709 use crate::quotient::Kind;
710 match kind {
711 Kind::Type => QuotientPrimitive::Type,
712 Kind::Constructor => QuotientPrimitive::Constructor,
713 Kind::Lift => QuotientPrimitive::Lift,
714 Kind::Induction => QuotientPrimitive::Induction,
715 }
716 }
717}
718
719#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
721#[serde(deny_unknown_fields)]
722pub struct ConstructorClaim {
723 pub name: String,
724 pub ty: NodeRef,
725}
726
727#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
729#[serde(deny_unknown_fields)]
730pub struct InductiveType {
731 pub name: String,
732 pub arity: NodeRef,
734 pub num_params: usize,
736 pub constructors: Vec<ConstructorClaim>,
737}
738
739#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
741#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
742pub enum EnvironmentEntry {
743 Axiom {
745 name: String,
746 level_params: Vec<String>,
747 ty: NodeRef,
748 },
749 Definition {
752 name: String,
753 level_params: Vec<String>,
754 ty: NodeRef,
755 body: NodeRef,
756 },
757 Theorem {
760 name: String,
761 level_params: Vec<String>,
762 ty: NodeRef,
763 proof: NodeRef,
764 },
765 Opaque {
767 name: String,
768 level_params: Vec<String>,
769 ty: NodeRef,
770 body: NodeRef,
771 },
772 Inductive {
774 level_params: Vec<String>,
775 types: Vec<InductiveType>,
776 },
777 Structure {
780 inductive: String,
781 projections: Vec<String>,
782 },
783 Quotient {
785 primitive: QuotientPrimitive,
786 level_params: Vec<String>,
787 ty: NodeRef,
788 equality: Option<String>,
789 },
790}
791
792impl EnvironmentEntry {
793 pub fn names(&self) -> Vec<&str> {
795 match self {
796 EnvironmentEntry::Axiom { name, .. }
797 | EnvironmentEntry::Definition { name, .. }
798 | EnvironmentEntry::Theorem { name, .. }
799 | EnvironmentEntry::Opaque { name, .. } => vec![name.as_str()],
800 EnvironmentEntry::Inductive { types, .. } => types
801 .iter()
802 .flat_map(|t| {
803 std::iter::once(t.name.as_str())
804 .chain(t.constructors.iter().map(|c| c.name.as_str()))
805 })
806 .collect(),
807 EnvironmentEntry::Structure { .. } => Vec::new(),
808 EnvironmentEntry::Quotient { primitive, .. } => vec![primitive.kind().name()],
809 }
810 }
811
812 pub fn roots(&self) -> Vec<NodeRef> {
814 match self {
815 EnvironmentEntry::Axiom { ty, .. } | EnvironmentEntry::Quotient { ty, .. } => vec![*ty],
816 EnvironmentEntry::Definition { ty, body, .. }
817 | EnvironmentEntry::Opaque { ty, body, .. } => vec![*ty, *body],
818 EnvironmentEntry::Theorem { ty, proof, .. } => vec![*ty, *proof],
819 EnvironmentEntry::Inductive { types, .. } => types
820 .iter()
821 .flat_map(|t| std::iter::once(t.arity).chain(t.constructors.iter().map(|c| c.ty)))
822 .collect(),
823 EnvironmentEntry::Structure { .. } => Vec::new(),
824 }
825 }
826
827 fn kind_name(&self) -> &'static str {
828 match self {
829 EnvironmentEntry::Axiom { .. } => "axiom",
830 EnvironmentEntry::Definition { .. } => "definition",
831 EnvironmentEntry::Theorem { .. } => "theorem",
832 EnvironmentEntry::Opaque { .. } => "opaque",
833 EnvironmentEntry::Inductive { .. } => "inductive",
834 EnvironmentEntry::Structure { .. } => "structure",
835 EnvironmentEntry::Quotient { .. } => "quotient",
836 }
837 }
838}
839
840#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
843#[serde(deny_unknown_fields)]
844pub struct EnvironmentCertificate {
845 pub schema: String,
847 pub prelude_version: String,
849 pub base: EnvironmentBase,
850 pub conversion: ConversionDiscipline,
851 pub nodes: Vec<TermNode>,
853 pub entries: Vec<EnvironmentEntry>,
856 pub target: String,
858 pub binder_kinds: BTreeMap<String, Vec<BinderKind>>,
862}
863
864#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
866#[serde(rename_all = "snake_case")]
867pub enum BinderKind {
868 Explicit,
869 Implicit,
870 StrictImplicit,
871 Instance,
872}
873
874impl BinderKind {
875 fn of(kind: crate::elaborate::ParamKind) -> Self {
876 use crate::elaborate::ParamKind;
877 match kind {
878 ParamKind::Explicit => BinderKind::Explicit,
879 ParamKind::Implicit => BinderKind::Implicit,
880 ParamKind::StrictImplicit => BinderKind::StrictImplicit,
881 ParamKind::Instance => BinderKind::Instance,
882 }
883 }
884 fn param_kind(self) -> crate::elaborate::ParamKind {
885 use crate::elaborate::ParamKind;
886 match self {
887 BinderKind::Explicit => ParamKind::Explicit,
888 BinderKind::Implicit => ParamKind::Implicit,
889 BinderKind::StrictImplicit => ParamKind::StrictImplicit,
890 BinderKind::Instance => ParamKind::Instance,
891 }
892 }
893}
894
895#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
898pub enum EnvironmentErrorCode {
899 UnsupportedSchema,
901 IncompatiblePrelude,
903 Malformed,
905 ResourceLimit,
907 DependencyCycle,
909 UnresolvedObligation,
911 TrustedNameReplacement,
913 IllTyped,
915 InexactConversion,
917 IndependentDisagreement,
919 TargetNotProved,
921 MissingEvidence,
923 Unsupported,
925}
926
927impl EnvironmentErrorCode {
928 pub fn as_str(self) -> &'static str {
929 match self {
930 EnvironmentErrorCode::UnsupportedSchema => "unsupported_schema",
931 EnvironmentErrorCode::IncompatiblePrelude => "incompatible_prelude",
932 EnvironmentErrorCode::Malformed => "malformed",
933 EnvironmentErrorCode::ResourceLimit => "resource_limit",
934 EnvironmentErrorCode::DependencyCycle => "dependency_cycle",
935 EnvironmentErrorCode::UnresolvedObligation => "unresolved_obligation",
936 EnvironmentErrorCode::TrustedNameReplacement => "trusted_name_replacement",
937 EnvironmentErrorCode::IllTyped => "ill_typed",
938 EnvironmentErrorCode::InexactConversion => "inexact_conversion",
939 EnvironmentErrorCode::IndependentDisagreement => "independent_disagreement",
940 EnvironmentErrorCode::TargetNotProved => "target_not_proved",
941 EnvironmentErrorCode::MissingEvidence => "missing_evidence",
942 EnvironmentErrorCode::Unsupported => "unsupported",
943 }
944 }
945}
946
947#[derive(Debug, Clone, PartialEq, Eq)]
949pub struct EnvironmentError {
950 pub code: EnvironmentErrorCode,
951 pub message: String,
952}
953
954impl EnvironmentError {
955 pub fn new(code: EnvironmentErrorCode, message: impl Into<String>) -> Self {
956 EnvironmentError {
957 code,
958 message: message.into(),
959 }
960 }
961}
962
963impl std::fmt::Display for EnvironmentError {
964 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
965 write!(f, "{}: {}", self.code.as_str(), self.message)
966 }
967}
968
969#[derive(Debug, Clone, Copy, PartialEq, Eq)]
971pub struct EnvironmentLimits {
972 pub max_nodes: usize,
974 pub max_entries: usize,
976 pub max_depth: usize,
979 pub max_tree_nodes: u64,
981 pub max_name_bytes: usize,
983}
984
985impl Default for EnvironmentLimits {
986 fn default() -> Self {
987 EnvironmentLimits {
988 max_nodes: 2_000_000,
989 max_entries: 100_000,
990 max_depth: crate::type_checker::MAX_TERM_DEPTH,
991 max_tree_nodes: 20_000_000,
992 max_name_bytes: 4096,
993 }
994 }
995}
996
997#[derive(Debug, Clone, PartialEq, Eq)]
999pub enum IndependentVerdict {
1000 Agreed,
1002 Unsupported(String),
1004}
1005
1006pub struct RebuiltEnvironment {
1008 pub context: Context,
1010 pub target: String,
1011 pub target_level_params: Vec<String>,
1013 pub target_type: Term,
1014 pub target_proof: Term,
1015 pub target_is_theorem: bool,
1017 pub target_is_opaque: bool,
1019 pub independent: Vec<(String, IndependentVerdict)>,
1021 pub axioms: AxiomUsage,
1023 pub assumptions: Vec<(String, Vec<String>, Term)>,
1025 pub proofs: BTreeMap<String, Term>,
1028 pub depth: usize,
1030}
1031
1032impl RebuiltEnvironment {
1033 pub fn independently_agreed(&self) -> bool {
1035 self.independent
1036 .iter()
1037 .all(|(_, v)| *v == IndependentVerdict::Agreed)
1038 }
1039}
1040
1041struct DecodedTable {
1043 store: crate::term_store::TermStore,
1044 ids: Vec<crate::term_store::TermId>,
1045 depth: usize,
1046}
1047
1048impl DecodedTable {
1049 fn term(&self, row: NodeRef) -> Result<Term, EnvironmentError> {
1050 let id = *self.ids.get(row as usize).ok_or_else(|| {
1051 EnvironmentError::new(
1052 EnvironmentErrorCode::Malformed,
1053 format!("term row {row} does not exist"),
1054 )
1055 })?;
1056 Ok(self.store.extract(id))
1057 }
1058}
1059
1060fn malformed(message: impl Into<String>) -> EnvironmentError {
1061 EnvironmentError::new(EnvironmentErrorCode::Malformed, message)
1062}
1063
1064fn limit(message: impl Into<String>) -> EnvironmentError {
1065 EnvironmentError::new(EnvironmentErrorCode::ResourceLimit, message)
1066}
1067
1068fn ill_typed(message: impl Into<String>) -> EnvironmentError {
1069 EnvironmentError::new(EnvironmentErrorCode::IllTyped, message)
1070}
1071
1072fn base_context(base: EnvironmentBase) -> Context {
1074 if base == EnvironmentBase::StandardLibrary {
1075 StandardLibrary::context().clone()
1076 } else {
1077 Context::new()
1078 }
1079}
1080
1081fn declared(ctx: &Context, name: &str) -> bool {
1082 ctx.get_global(name).is_some() || ctx.poly_declaration(name).is_some()
1083}
1084
1085fn cited_names(term: &Term) -> BTreeSet<String> {
1087 let mut out = BTreeSet::new();
1088 let mut work = vec![term];
1089 while let Some(t) = work.pop() {
1090 match t {
1091 Term::Global(n) => {
1092 out.insert(n.clone());
1093 }
1094 Term::Const { name, .. } => {
1095 out.insert(name.clone());
1096 }
1097 Term::Sort(_) | Term::Var(_) | Term::Lit(_) | Term::Hole => {}
1098 Term::Pi {
1099 param_type,
1100 body_type,
1101 ..
1102 } => {
1103 work.push(param_type);
1104 work.push(body_type);
1105 }
1106 Term::Lambda {
1107 param_type, body, ..
1108 } => {
1109 work.push(param_type);
1110 work.push(body);
1111 }
1112 Term::App(f, a) => {
1113 work.push(f);
1114 work.push(a);
1115 }
1116 Term::Match {
1117 discriminant,
1118 motive,
1119 cases,
1120 } => {
1121 work.push(discriminant);
1122 work.push(motive);
1123 work.extend(cases.iter());
1124 }
1125 Term::Fix { body, .. } => work.push(body),
1126 Term::MutualFix { defs, .. } => work.extend(defs.iter().map(|(_, b)| b)),
1127 Term::Let {
1128 ty, value, body, ..
1129 } => {
1130 work.push(ty);
1131 work.push(value);
1132 work.push(body);
1133 }
1134 Term::Annotated { term, .. } => work.push(term),
1135 }
1136 }
1137 out
1138}
1139
1140impl EnvironmentCertificate {
1141 fn decode(&self, limits: &EnvironmentLimits) -> Result<DecodedTable, EnvironmentError> {
1145 if self.nodes.len() > limits.max_nodes {
1146 return Err(limit(format!(
1147 "{} term rows exceed the limit of {}",
1148 self.nodes.len(),
1149 limits.max_nodes
1150 )));
1151 }
1152 if self.entries.len() > limits.max_entries {
1153 return Err(limit(format!(
1154 "{} declarations exceed the limit of {}",
1155 self.entries.len(),
1156 limits.max_entries
1157 )));
1158 }
1159 let long_name = |n: &str| n.len() > limits.max_name_bytes;
1160 let mut depth: Vec<usize> = Vec::with_capacity(self.nodes.len());
1161 let mut size: Vec<u64> = Vec::with_capacity(self.nodes.len());
1162 for (row, node) in self.nodes.iter().enumerate() {
1163 let mut children: Vec<NodeRef> = Vec::new();
1164 let mut names: Vec<&str> = Vec::new();
1165 match node {
1166 TermNode::Sort(_) | TermNode::Lit(_) | TermNode::Hole => {}
1167 TermNode::Var(n) | TermNode::Global(n) | TermNode::Const(n, _) => names.push(n),
1168 TermNode::Pi(n, a, b) | TermNode::Lambda(n, a, b) => {
1169 names.push(n);
1170 children.extend([*a, *b]);
1171 }
1172 TermNode::App(a, b) => children.extend([*a, *b]),
1173 TermNode::Match(d, m, cases) => {
1174 children.extend([*d, *m]);
1175 children.extend(cases.iter().copied());
1176 }
1177 TermNode::Fix(n, b) => {
1178 names.push(n);
1179 children.push(*b);
1180 }
1181 TermNode::MutualFix(defs, index) => {
1182 if *index >= defs.len() {
1183 return Err(malformed(format!(
1184 "term row {row}: mutual fixpoint index out of range"
1185 )));
1186 }
1187 for (n, b) in defs {
1188 names.push(n);
1189 children.push(*b);
1190 }
1191 }
1192 TermNode::Let(n, a, b, c) => {
1193 names.push(n);
1194 children.extend([*a, *b, *c]);
1195 }
1196 }
1197 if let Some(n) = names.iter().find(|n| long_name(n)) {
1198 return Err(limit(format!(
1199 "term row {row}: a {}-byte name exceeds the limit",
1200 n.len()
1201 )));
1202 }
1203 let mut d = 0usize;
1204 let mut s = 1u64;
1205 for child in children {
1206 if child as usize >= row {
1207 return Err(malformed(format!(
1208 "term row {row} cites row {child}: rows cite only EARLIER rows, so a forward or \
1209 self reference is a cycle, never a term"
1210 )));
1211 }
1212 d = d.max(depth[child as usize]);
1213 s = s.saturating_add(size[child as usize]);
1214 }
1215 let d = d + 1;
1216 if d > limits.max_depth {
1217 return Err(limit(format!(
1218 "term row {row} has depth {d}, beyond the limit of {}",
1219 limits.max_depth
1220 )));
1221 }
1222 depth.push(d);
1223 size.push(s);
1224 }
1225 let mut total = 0u64;
1226 for entry in &self.entries {
1227 for row in entry.roots() {
1228 let row_size = *size.get(row as usize).ok_or_else(|| {
1229 malformed(format!(
1230 "a {} cites term row {row}, which does not exist",
1231 entry.kind_name()
1232 ))
1233 })?;
1234 total = total.saturating_add(row_size);
1235 }
1236 }
1237 if total > limits.max_tree_nodes {
1238 return Err(limit(format!(
1239 "the declarations unfold to {total} term nodes, beyond the limit of {}",
1240 limits.max_tree_nodes
1241 )));
1242 }
1243 for entry in &self.entries {
1244 for name in entry.names() {
1245 if long_name(name) {
1246 return Err(limit(format!(
1247 "a {}-byte declaration name exceeds the limit",
1248 name.len()
1249 )));
1250 }
1251 }
1252 }
1253 let mut store = crate::term_store::TermStore::new();
1254 let mut ids = Vec::with_capacity(self.nodes.len());
1255 for node in &self.nodes {
1256 let at = |r: &NodeRef| ids[*r as usize];
1257 let id = match node {
1258 TermNode::Sort(u) => store.mk_sort(u),
1259 TermNode::Var(n) => store.mk_var(n),
1260 TermNode::Global(n) => store.mk_global(n),
1261 TermNode::Const(n, levels) => store.mk_const(n, levels),
1262 TermNode::Pi(n, a, b) => {
1263 let (a, b) = (at(a), at(b));
1264 store.mk_pi(n, a, b)
1265 }
1266 TermNode::Lambda(n, a, b) => {
1267 let (a, b) = (at(a), at(b));
1268 store.mk_lambda(n, a, b)
1269 }
1270 TermNode::App(a, b) => {
1271 let (a, b) = (at(a), at(b));
1272 store.mk_app(a, b)
1273 }
1274 TermNode::Match(d, m, cases) => {
1275 let cases: Vec<_> = cases.iter().map(at).collect();
1276 let (d, m) = (at(d), at(m));
1277 store.mk_match(d, m, &cases)
1278 }
1279 TermNode::Fix(n, b) => {
1280 let b = at(b);
1281 store.mk_fix(n, b)
1282 }
1283 TermNode::MutualFix(defs, index) => {
1284 let defs: Vec<(&str, _)> =
1285 defs.iter().map(|(n, b)| (n.as_str(), at(b))).collect();
1286 store.mk_mutual_fix(&defs, *index)
1287 }
1288 TermNode::Let(n, a, b, c) => {
1289 let (a, b, c) = (at(a), at(b), at(c));
1290 store.mk_let(n, a, b, c)
1291 }
1292 TermNode::Lit(l) => store.mk_lit(l),
1293 TermNode::Hole => store.mk_hole(),
1294 };
1295 ids.push(id);
1296 }
1297 Ok(DecodedTable {
1298 store,
1299 ids,
1300 depth: depth.iter().copied().max().unwrap_or(0),
1301 })
1302 }
1303
1304 pub fn admitted_depth(&self, limits: &EnvironmentLimits) -> Result<usize, EnvironmentError> {
1307 self.decode(limits).map(|t| t.depth)
1308 }
1309
1310 fn check_acyclic(&self, table: &DecodedTable) -> Result<(), EnvironmentError> {
1313 let mut owner: HashMap<String, usize> = HashMap::new();
1314 for (i, entry) in self.entries.iter().enumerate() {
1315 for name in entry.names() {
1316 owner.entry(name.to_string()).or_insert(i);
1317 }
1318 }
1319 let mut edges: Vec<BTreeSet<usize>> = Vec::with_capacity(self.entries.len());
1320 for (i, entry) in self.entries.iter().enumerate() {
1321 let mut out = BTreeSet::new();
1322 let mut cited = BTreeSet::new();
1323 for row in entry.roots() {
1324 cited.extend(cited_names(&table.term(row)?));
1325 }
1326 if let EnvironmentEntry::Structure {
1327 inductive,
1328 projections,
1329 } = entry
1330 {
1331 cited.insert(inductive.clone());
1332 cited.extend(projections.iter().cloned());
1333 }
1334 if let EnvironmentEntry::Quotient {
1335 equality: Some(eq), ..
1336 } = entry
1337 {
1338 cited.insert(eq.clone());
1339 }
1340 for name in cited {
1341 if let Some(&j) = owner.get(&name) {
1342 if j != i {
1343 out.insert(j);
1344 }
1345 }
1346 }
1347 edges.push(out);
1348 }
1349 let mut colour = vec![0u8; self.entries.len()];
1351 for start in 0..self.entries.len() {
1352 if colour[start] != 0 {
1353 continue;
1354 }
1355 let mut stack: Vec<(usize, Vec<usize>)> =
1356 vec![(start, edges[start].iter().copied().collect())];
1357 colour[start] = 1;
1358 while let Some((node, pending)) = stack.last_mut() {
1359 if let Some(next) = pending.pop() {
1360 match colour[next] {
1361 0 => {
1362 colour[next] = 1;
1363 let children = edges[next].iter().copied().collect();
1364 stack.push((next, children));
1365 }
1366 1 => {
1367 let cycle: Vec<String> = stack
1368 .iter()
1369 .skip_while(|(n, _)| *n != next)
1370 .flat_map(|(n, _)| {
1371 self.entries[*n]
1372 .names()
1373 .into_iter()
1374 .take(1)
1375 .map(str::to_string)
1376 })
1377 .collect();
1378 return Err(EnvironmentError::new(
1379 EnvironmentErrorCode::DependencyCycle,
1380 format!(
1381 "declarations cite each other in a cycle: {}",
1382 cycle.join(" → ")
1383 ),
1384 ));
1385 }
1386 _ => {}
1387 }
1388 } else {
1389 colour[*node] = 2;
1390 stack.pop();
1391 }
1392 }
1393 }
1394 Ok(())
1395 }
1396
1397 pub fn rebuild(
1404 &self,
1405 limits: &EnvironmentLimits,
1406 ) -> Result<RebuiltEnvironment, EnvironmentError> {
1407 self.rebuild_observing_definitions(limits, &mut |_, _, _, _, _| {})
1408 }
1409
1410 pub(crate) fn rebuild_observing_definitions(
1414 &self,
1415 limits: &EnvironmentLimits,
1416 observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1417 ) -> Result<RebuiltEnvironment, EnvironmentError> {
1418 self.rebuild_observing_values_impl(limits, observe, false)
1419 }
1420
1421 pub(crate) fn rebuild_observing_values(
1424 &self,
1425 limits: &EnvironmentLimits,
1426 observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1427 ) -> Result<RebuiltEnvironment, EnvironmentError> {
1428 self.rebuild_observing_values_impl(limits, observe, true)
1429 }
1430
1431 fn rebuild_observing_values_impl(
1432 &self,
1433 limits: &EnvironmentLimits,
1434 observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1435 include_opaque: bool,
1436 ) -> Result<RebuiltEnvironment, EnvironmentError> {
1437 self.check_schema()?;
1438 check_prelude_version(&self.prelude_version)?;
1439 #[cfg(not(feature = "lean"))]
1440 if self.conversion == ConversionDiscipline::Exact {
1441 return Err(EnvironmentError::new(
1442 EnvironmentErrorCode::Unsupported,
1443 "exact conversion is checked by the `lean` feature's exact typing, which this build lacks",
1444 ));
1445 }
1446 let table = self.decode(limits)?;
1447 for row in 0..self.nodes.len() {
1448 if matches!(self.nodes[row], TermNode::Hole) {
1449 return Err(ill_typed(format!(
1450 "term row {row} is a hole: a certificate carries no unelaborated term"
1451 )));
1452 }
1453 }
1454 self.check_acyclic(&table)?;
1455 let mut rebuild = Rebuild {
1456 certificate: self,
1457 table: &table,
1458 ctx: base_context(self.base),
1459 introduced: BTreeSet::new(),
1460 independent: Vec::new(),
1461 assumptions: Vec::new(),
1462 proofs: BTreeMap::new(),
1463 };
1464 for entry in &self.entries {
1465 rebuild.entry(entry, observe, include_opaque)?;
1466 }
1467 let mut ctx = rebuild.ctx;
1468 for (name, kinds) in &self.binder_kinds {
1469 if !rebuild.introduced.contains(name) {
1470 return Err(malformed(format!(
1471 "binder annotations name {name}, which the certificate does not declare"
1472 )));
1473 }
1474 let ty = ctx
1475 .poly_declaration(name)
1476 .map(|(_, t)| t.clone())
1477 .or_else(|| ctx.get_global(name).cloned());
1478 let mut telescope = 0usize;
1479 let mut at = ty.as_ref();
1480 while let Some(Term::Pi { body_type, .. }) = at {
1481 telescope += 1;
1482 at = Some(body_type);
1483 }
1484 if kinds.len() > telescope {
1485 return Err(malformed(format!(
1486 "{name} has {} binder annotations for a telescope of {telescope}",
1487 kinds.len()
1488 )));
1489 }
1490 ctx.set_binder_kinds(name, kinds.iter().map(|k| k.param_kind()).collect());
1491 }
1492 let obligations = ctx.pending_obligations();
1493 if !obligations.is_empty() {
1494 return Err(EnvironmentError::new(
1495 EnvironmentErrorCode::UnresolvedObligation,
1496 format!(
1497 "the rebuilt environment still owes {} obligation(s): {:?}",
1498 obligations.len(),
1499 obligations
1500 ),
1501 ));
1502 }
1503 if !ctx.refused_declarations().is_empty() {
1504 return Err(ill_typed(format!(
1505 "the kernel refused {:?}",
1506 ctx.refused_declarations()
1507 )));
1508 }
1509 let (target_level_params, target_type, target_proof, target_is_theorem, target_is_opaque) = self
1510 .entries
1511 .iter()
1512 .find_map(|entry| match entry {
1513 EnvironmentEntry::Definition { name, level_params, ty, body } if *name == self.target => {
1514 Some((level_params.clone(), *ty, *body, false, false))
1515 }
1516 EnvironmentEntry::Theorem { name, level_params, ty, proof } if *name == self.target => {
1517 Some((level_params.clone(), *ty, *proof, true, false))
1518 }
1519 EnvironmentEntry::Opaque { name, level_params, ty, body } if *name == self.target => {
1520 Some((level_params.clone(), *ty, *body, false, true))
1521 }
1522 _ => None,
1523 })
1524 .ok_or_else(|| {
1525 let assumed = self.entries.iter().any(|e| matches!(e, EnvironmentEntry::Axiom { name, .. } if *name == self.target));
1526 EnvironmentError::new(
1527 EnvironmentErrorCode::TargetNotProved,
1528 if assumed {
1529 format!("the target {} is an assumption: a certificate proves its target, it does not assume it", self.target)
1530 } else {
1531 format!("the certificate declares no proved target named {}", self.target)
1532 },
1533 )
1534 })?;
1535 let target_type = table.term(target_type)?;
1536 let target_proof = table.term(target_proof)?;
1537 let mut axioms = axiom_dependencies(&ctx, &target_type);
1538 axioms.absorb(axiom_dependencies(&ctx, &target_proof));
1539 if let Some(evidence) = axiom_dependencies_of(&ctx, &self.target) {
1541 axioms.absorb(evidence);
1542 }
1543 Ok(RebuiltEnvironment {
1544 context: ctx,
1545 target: self.target.clone(),
1546 target_level_params,
1547 target_type,
1548 target_proof,
1549 target_is_theorem,
1550 target_is_opaque,
1551 independent: rebuild.independent,
1552 axioms,
1553 assumptions: rebuild.assumptions,
1554 proofs: rebuild.proofs,
1555 depth: table.depth,
1556 })
1557 }
1558}
1559
1560struct Rebuild<'a> {
1562 certificate: &'a EnvironmentCertificate,
1563 table: &'a DecodedTable,
1564 ctx: Context,
1565 introduced: BTreeSet<String>,
1566 independent: Vec<(String, IndependentVerdict)>,
1567 assumptions: Vec<(String, Vec<String>, Term)>,
1568 proofs: BTreeMap<String, Term>,
1569}
1570
1571impl Rebuild<'_> {
1572 fn exact(&self) -> bool {
1573 self.certificate.conversion == ConversionDiscipline::Exact
1574 }
1575
1576 fn claim_name(
1578 &mut self,
1579 name: &str,
1580 canonical_nat: bool,
1581 quotient: bool,
1582 ) -> Result<(), EnvironmentError> {
1583 let replacement =
1584 |why: String| EnvironmentError::new(EnvironmentErrorCode::TrustedNameReplacement, why);
1585 if name.is_empty() {
1586 return Err(malformed("a declaration has an empty name"));
1587 }
1588 if self.introduced.contains(name) {
1589 return Err(replacement(format!("{name} is declared twice")));
1590 }
1591 if declared(&self.ctx, name) {
1592 return Err(replacement(format!(
1593 "{name} is already declared by the trusted base and may not be replaced"
1594 )));
1595 }
1596 let is_nat = matches!(name, "Nat" | "Zero" | "Succ");
1597 let is_quotient = name.starts_with("kernel.quot.");
1598 if is_quotient && !quotient {
1599 return Err(replacement(format!(
1600 "{name} is a certified quotient primitive and only a quotient entry may install it"
1601 )));
1602 }
1603 if is_nat && !canonical_nat {
1604 return Err(replacement(format!("{name} is the kernel's literal Nat and only its canonical inductive may declare it")));
1605 }
1606 if self.certificate.base == EnvironmentBase::Kernel
1612 && !is_nat
1613 && !is_quotient
1614 && declared(StandardLibrary::context(), name)
1615 {
1616 return Err(replacement(format!(
1617 "{name} carries native meaning in the standard library; an imported environment may not declare it"
1618 )));
1619 }
1620 self.introduced.insert(name.to_string());
1621 Ok(())
1622 }
1623
1624 fn require_declared(
1626 &self,
1627 subject: &str,
1628 terms: &[&Term],
1629 own: &BTreeSet<String>,
1630 ) -> Result<(), EnvironmentError> {
1631 for term in terms {
1632 for name in cited_names(term) {
1633 if !own.contains(&name) && !declared(&self.ctx, &name) {
1634 return Err(EnvironmentError::new(
1635 EnvironmentErrorCode::UnresolvedObligation,
1636 format!("{subject} cites {name}, which is not declared before it"),
1637 ));
1638 }
1639 }
1640 }
1641 Ok(())
1642 }
1643
1644 fn level_telescope(
1645 &self,
1646 subject: &str,
1647 params: &[String],
1648 terms: &[&Term],
1649 ) -> Result<(), EnvironmentError> {
1650 let bound: BTreeSet<&str> = params.iter().map(String::as_str).collect();
1651 if bound.len() != params.len() {
1652 return Err(ill_typed(format!("{subject} repeats a universe parameter")));
1653 }
1654 for term in terms {
1655 for level in crate::term::free_level_vars(term) {
1656 if !bound.contains(level.as_str()) {
1657 return Err(ill_typed(format!(
1658 "{subject} uses the unbound universe parameter {level}"
1659 )));
1660 }
1661 }
1662 }
1663 Ok(())
1664 }
1665
1666 fn infer(&self, term: &Term) -> Result<Term, EnvironmentError> {
1668 if self.exact() {
1669 #[cfg(feature = "lean")]
1670 {
1671 return crate::interface::lean::lean_type(&self.ctx, term).map_err(|verdict| {
1672 let cumulative = crate::infer_type(&self.ctx, term).is_ok();
1673 EnvironmentError::new(
1674 if cumulative {
1675 EnvironmentErrorCode::InexactConversion
1676 } else {
1677 EnvironmentErrorCode::IllTyped
1678 },
1679 verdict.reason().message().to_string(),
1680 )
1681 });
1682 }
1683 }
1684 crate::infer_type(&self.ctx, term).map_err(|e| ill_typed(e.to_string()))
1685 }
1686
1687 fn conforms(
1690 &self,
1691 subject: &str,
1692 inferred: &Term,
1693 declared_ty: &Term,
1694 ) -> Result<(), EnvironmentError> {
1695 let ok = if self.exact() {
1696 crate::type_checker::def_eq(&self.ctx, inferred, declared_ty)
1697 } else {
1698 is_subtype(&self.ctx, inferred, declared_ty)
1699 };
1700 if ok {
1701 return Ok(());
1702 }
1703 if self.exact() && is_subtype(&self.ctx, inferred, declared_ty) {
1704 return Err(EnvironmentError::new(
1705 EnvironmentErrorCode::InexactConversion,
1706 format!("{subject} has its declared type only by cumulative subtyping"),
1707 ));
1708 }
1709 Err(ill_typed(format!(
1710 "{subject} does not have its declared type"
1711 )))
1712 }
1713
1714 fn check_type(&self, subject: &str, ty: &Term) -> Result<(), EnvironmentError> {
1716 let sort = self.infer(ty)?;
1717 let sort = crate::normalize_checked(&self.ctx, &sort).map_err(|e| {
1718 ill_typed(format!(
1719 "{subject}: the sort of its type did not normalize: {e:?}"
1720 ))
1721 })?;
1722 if matches!(sort, Term::Sort(_)) {
1723 Ok(())
1724 } else {
1725 Err(ill_typed(format!(
1726 "{subject}: its declared type is not a type"
1727 )))
1728 }
1729 }
1730
1731 fn independently(
1733 &mut self,
1734 subject: &str,
1735 term: &Term,
1736 expected: &Term,
1737 ) -> Result<(), EnvironmentError> {
1738 match crate::recheck(&self.ctx, term) {
1739 Ok(inferred) => {
1740 if crate::type_checker::def_eq(&self.ctx, &inferred, expected) || is_subtype(&self.ctx, &inferred, expected) {
1741 self.independent.push((subject.to_string(), IndependentVerdict::Agreed));
1742 Ok(())
1743 } else {
1744 Err(EnvironmentError::new(
1745 EnvironmentErrorCode::IndependentDisagreement,
1746 format!("{subject}: the independent kernel infers a different type"),
1747 ))
1748 }
1749 }
1750 Err(crate::ReCheckError::Unsupported(why)) => {
1751 self.independent.push((subject.to_string(), IndependentVerdict::Unsupported(why.into())));
1752 Ok(())
1753 }
1754 Err(crate::ReCheckError::Ill(why)) => Err(EnvironmentError::new(
1755 EnvironmentErrorCode::IndependentDisagreement,
1756 format!("{subject}: the independent kernel refuses what the primary kernel accepted: {why}"),
1757 )),
1758 }
1759 }
1760
1761 fn entry(
1762 &mut self,
1763 entry: &EnvironmentEntry,
1764 observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1765 include_opaque: bool,
1766 ) -> Result<(), EnvironmentError> {
1767 match entry {
1768 EnvironmentEntry::Axiom {
1769 name,
1770 level_params,
1771 ty,
1772 } => {
1773 let ty = self.table.term(*ty)?;
1774 self.claim_name(name, false, false)?;
1775 self.require_declared(name, &[&ty], &BTreeSet::new())?;
1776 self.level_telescope(name, level_params, &[&ty])?;
1777 self.check_type(name, &ty)?;
1778 let sort =
1779 crate::infer_type(&self.ctx, &ty).map_err(|e| ill_typed(e.to_string()))?;
1780 self.independently(name, &ty, &sort)?;
1781 self.ctx
1782 .add_universe_poly_declaration(name, level_params.clone(), ty.clone())
1783 .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1784 self.assumptions
1785 .push((name.clone(), level_params.clone(), ty));
1786 Ok(())
1787 }
1788 EnvironmentEntry::Definition {
1789 name,
1790 level_params,
1791 ty,
1792 body,
1793 } => {
1794 let (ty, body) = (self.table.term(*ty)?, self.table.term(*body)?);
1795 self.claim_name(name, false, false)?;
1796 self.require_declared(name, &[&ty, &body], &BTreeSet::new())?;
1797 self.level_telescope(name, level_params, &[&ty, &body])?;
1798 self.check_type(name, &ty)?;
1799 let inferred = self.infer(&body)?;
1800 self.conforms(name, &inferred, &ty)?;
1801 self.independently(name, &body, &ty)?;
1802 observe(&self.ctx, name, level_params, &ty, &body);
1803 let registered = if level_params.is_empty() {
1804 self.ctx.declare(name, ty, body)
1805 } else {
1806 self.ctx
1807 .declare_universe_poly(name, level_params.clone(), ty, body)
1808 };
1809 registered.map_err(|e| ill_typed(format!("{name}: {e}")))
1810 }
1811 EnvironmentEntry::Theorem {
1812 name,
1813 level_params,
1814 ty,
1815 proof,
1816 } => {
1817 let (ty, proof) = (self.table.term(*ty)?, self.table.term(*proof)?);
1818 self.claim_name(name, false, false)?;
1819 self.require_declared(name, &[&ty, &proof], &BTreeSet::new())?;
1820 self.level_telescope(name, level_params, &[&ty, &proof])?;
1821 self.check_type(name, &ty)?;
1822 let inferred = self.infer(&proof)?;
1823 self.conforms(name, &inferred, &ty)?;
1824 self.independently(name, &proof, &ty)?;
1825 self.ctx
1828 .declare_theorem_checked(name, level_params.clone(), ty, proof.clone())
1829 .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1830 self.proofs.insert(name.clone(), proof);
1831 Ok(())
1832 }
1833 EnvironmentEntry::Opaque {
1834 name,
1835 level_params,
1836 ty,
1837 body,
1838 } => {
1839 let (ty, body) = (self.table.term(*ty)?, self.table.term(*body)?);
1840 self.claim_name(name, false, false)?;
1841 self.require_declared(name, &[&ty, &body], &BTreeSet::new())?;
1842 self.level_telescope(name, level_params, &[&ty, &body])?;
1843 self.check_type(name, &ty)?;
1844 let inferred = self.infer(&body)?;
1845 self.conforms(name, &inferred, &ty)?;
1846 self.independently(name, &body, &ty)?;
1847 if include_opaque {
1848 observe(&self.ctx, name, level_params, &ty, &body);
1849 }
1850 self.ctx
1851 .declare_opaque_checked(name, level_params.clone(), ty, body.clone())
1852 .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1853 self.proofs.insert(name.clone(), body);
1854 Ok(())
1855 }
1856 EnvironmentEntry::Inductive {
1857 level_params,
1858 types,
1859 } => self.inductive(level_params, types),
1860 EnvironmentEntry::Structure {
1861 inductive,
1862 projections,
1863 } => self.structure(inductive, projections),
1864 EnvironmentEntry::Quotient {
1865 primitive,
1866 level_params,
1867 ty,
1868 equality,
1869 } => {
1870 let kind = primitive.kind();
1871 let name = kind.name();
1872 let ty = self.table.term(*ty)?;
1873 self.claim_name(name, false, true)?;
1874 let equality_name = equality.clone().unwrap_or_default();
1875 if kind == crate::quotient::Kind::Lift && equality.is_none() {
1876 return Err(malformed("the quotient lift names no equality"));
1877 }
1878 let levels: Vec<Universe> =
1879 level_params.iter().cloned().map(Universe::Var).collect();
1880 let expected = crate::quotient::signature(kind, &levels, &equality_name)
1881 .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1882 if !crate::type_checker::def_eq(&self.ctx, &ty, &expected) {
1883 return Err(ill_typed(format!(
1884 "{name} does not carry the canonical quotient signature"
1885 )));
1886 }
1887 crate::quotient::install(&mut self.ctx, kind, level_params.clone(), &equality_name)
1888 .map_err(|e| ill_typed(format!("{name}: {e}")))
1889 }
1890 }
1891 }
1892
1893 fn inductive(
1894 &mut self,
1895 level_params: &[String],
1896 types: &[InductiveType],
1897 ) -> Result<(), EnvironmentError> {
1898 if types.is_empty() {
1899 return Err(malformed("an inductive entry declares no type"));
1900 }
1901 let mut own = BTreeSet::new();
1902 let mut decoded: Vec<(String, Term, usize, Vec<(String, Term)>)> = Vec::new();
1903 for member in types {
1904 let arity = self.table.term(member.arity)?;
1905 let constructors = member
1906 .constructors
1907 .iter()
1908 .map(|c| Ok((c.name.clone(), self.table.term(c.ty)?)))
1909 .collect::<Result<Vec<_>, EnvironmentError>>()?;
1910 decoded.push((member.name.clone(), arity, member.num_params, constructors));
1911 }
1912 let canonical_nat = self.canonical_nat(level_params, &decoded)?;
1913 for (name, _, _, constructors) in &decoded {
1914 self.claim_name(name, canonical_nat, false)?;
1915 own.insert(name.clone());
1916 for (c, _) in constructors {
1917 self.claim_name(c, canonical_nat, false)?;
1918 own.insert(c.clone());
1919 }
1920 }
1921 for (name, arity, _, constructors) in &decoded {
1922 let mut terms: Vec<&Term> = vec![arity];
1923 terms.extend(constructors.iter().map(|(_, t)| t));
1924 self.require_declared(name, &terms, &own)?;
1925 self.level_telescope(name, level_params, &terms)?;
1926 }
1927 let mut headers = self.ctx.clone();
1930 for (name, arity, num_params, _) in &decoded {
1931 if level_params.is_empty() {
1932 headers.add_indexed_inductive(name, arity.clone(), *num_params);
1933 } else {
1934 headers.add_universe_poly_inductive(
1935 name,
1936 level_params.to_vec(),
1937 arity.clone(),
1938 *num_params,
1939 );
1940 }
1941 }
1942 let outer = std::mem::replace(&mut self.ctx, headers);
1943 let checked = decoded
1944 .iter()
1945 .try_for_each(|(name, arity, _, constructors)| {
1946 self.check_type(name, arity)?;
1947 constructors
1948 .iter()
1949 .try_for_each(|(c, ty)| self.check_type(c, ty))
1950 });
1951 self.ctx = outer;
1952 checked?;
1953 let block: Vec<crate::MutualInductive> = decoded
1954 .iter()
1955 .map(
1956 |(name, arity, num_params, constructors)| crate::MutualInductive {
1957 name: name.clone(),
1958 sort: arity.clone(),
1959 num_params: *num_params,
1960 constructors: constructors.clone(),
1961 },
1962 )
1963 .collect();
1964 let registered = if level_params.is_empty() {
1965 self.ctx.add_mutual_inductives(&block)
1966 } else {
1967 self.ctx.add_universe_poly_inductives(level_params, &block)
1968 };
1969 registered.map_err(|e| ill_typed(format!("inductive {}: {e}", decoded[0].0)))?;
1970 for (name, arity, _, constructors) in &decoded {
1971 let sort = crate::infer_type(&self.ctx, arity).map_err(|e| ill_typed(e.to_string()))?;
1972 self.independently(name, arity, &sort)?;
1973 for (c, ty) in constructors {
1974 let sort =
1975 crate::infer_type(&self.ctx, ty).map_err(|e| ill_typed(e.to_string()))?;
1976 self.independently(c, ty, &sort)?;
1977 }
1978 }
1979 Ok(())
1980 }
1981
1982 fn canonical_nat(
1986 &self,
1987 level_params: &[String],
1988 decoded: &[(String, Term, usize, Vec<(String, Term)>)],
1989 ) -> Result<bool, EnvironmentError> {
1990 let reserved = |n: &str| matches!(n, "Nat" | "Zero" | "Succ");
1991 let uses = decoded
1992 .iter()
1993 .any(|(n, _, _, cs)| reserved(n) || cs.iter().any(|(c, _)| reserved(c)));
1994 if !uses {
1995 return Ok(false);
1996 }
1997 let nat = Term::Global("Nat".into());
1998 let canonical = level_params.is_empty()
1999 && decoded.len() == 1
2000 && decoded[0].0 == "Nat"
2001 && decoded[0].2 == 0
2002 && decoded[0].1 == Term::Sort(Universe::Type(0))
2003 && decoded[0].3.len() == 2
2004 && decoded[0].3[0].0 == "Zero"
2005 && decoded[0].3[0].1 == nat
2006 && decoded[0].3[1].0 == "Succ"
2007 && matches!(&decoded[0].3[1].1, Term::Pi { param_type, body_type, .. }
2008 if **param_type == nat && **body_type == nat);
2009 if canonical {
2010 Ok(true)
2011 } else {
2012 Err(EnvironmentError::new(
2013 EnvironmentErrorCode::TrustedNameReplacement,
2014 "a block using the names Nat, Zero or Succ must be the canonical Nat: Nat : Type 0, Zero : Nat, Succ : Nat → Nat",
2015 ))
2016 }
2017 }
2018
2019 fn structure(
2024 &mut self,
2025 inductive: &str,
2026 projections: &[String],
2027 ) -> Result<(), EnvironmentError> {
2028 if self.ctx.is_inductive(inductive) && !self.introduced.contains(inductive) {
2029 return Err(EnvironmentError::new(
2030 EnvironmentErrorCode::UnresolvedObligation,
2031 format!(
2032 "structure η names {inductive}, which is not an inductive declared before it"
2033 ),
2034 ));
2035 }
2036 if let Some(borrowed) = projections
2037 .iter()
2038 .find(|p| declared(&self.ctx, p) && !self.introduced.contains(p.as_str()))
2039 {
2040 return Err(ill_typed(format!(
2041 "{borrowed} is not a definition declared before the structure"
2042 )));
2043 }
2044 self.ctx
2045 .register_structure_checked(inductive, projections)
2046 .map_err(structure_refusal)
2047 }
2048}
2049
2050fn structure_refusal(refusal: crate::StructureRefusal) -> EnvironmentError {
2053 use crate::StructureRefusal as R;
2054 let code = match refusal {
2055 R::NotAnInductive { .. } => EnvironmentErrorCode::UnresolvedObligation,
2056 R::AlreadyAStructure { .. } => EnvironmentErrorCode::TrustedNameReplacement,
2057 _ => EnvironmentErrorCode::IllTyped,
2058 };
2059 EnvironmentError::new(code, refusal.to_string())
2060}
2061
2062fn checked_theorem<'c>(ctx: &'c Context, name: &str) -> Option<(&'c [String], &'c Term, &'c Term)> {
2071 ctx.checked_opaque_dependencies(name)?;
2072 ctx.get_universe_poly(name)
2073 .map(|(params, ty, proof)| (params.as_slice(), ty, proof))
2074}
2075
2076pub struct CaptureRequest<'a> {
2077 pub context: &'a Context,
2078 pub base: EnvironmentBase,
2079 pub conversion: ConversionDiscipline,
2080 pub target: &'a str,
2081 pub opaque_proofs: &'a BTreeMap<String, Term>,
2084}
2085
2086#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
2088enum Item {
2089 Declaration(String),
2090 Block(Vec<String>),
2091}
2092
2093struct TableWriter {
2095 nodes: Vec<TermNode>,
2096 index: HashMap<String, NodeRef>,
2097 refusal: Option<String>,
2099}
2100
2101impl TableWriter {
2102 fn row(&mut self, node: TermNode) -> NodeRef {
2103 let key = match serde_json::to_string(&node) {
2104 Ok(key) => key,
2105 Err(e) => {
2106 self.refusal.get_or_insert_with(|| {
2107 format!("a term the certificate format cannot carry: {e}")
2108 });
2109 format!("{node:?}")
2110 }
2111 };
2112 if let Some(&r) = self.index.get(&key) {
2113 return r;
2114 }
2115 let r = self.nodes.len() as NodeRef;
2116 self.nodes.push(node);
2117 self.index.insert(key, r);
2118 r
2119 }
2120
2121 fn write(&mut self, term: &Term) -> NodeRef {
2123 enum Task<'a> {
2124 Enter(&'a Term),
2125 Exit(&'a Term),
2126 }
2127 let mut work = vec![Task::Enter(term)];
2128 let mut out: Vec<NodeRef> = Vec::new();
2129 while let Some(task) = work.pop() {
2130 match task {
2131 Task::Enter(t) => {
2132 work.push(Task::Exit(t));
2133 let mut children: Vec<&Term> = Vec::new();
2134 match t {
2135 Term::Sort(_)
2136 | Term::Var(_)
2137 | Term::Global(_)
2138 | Term::Const { .. }
2139 | Term::Lit(_)
2140 | Term::Hole => {}
2141 Term::Pi {
2142 param_type,
2143 body_type,
2144 ..
2145 } => children.extend([&**param_type, &**body_type]),
2146 Term::Lambda {
2147 param_type, body, ..
2148 } => children.extend([&**param_type, &**body]),
2149 Term::App(f, a) => children.extend([&**f, &**a]),
2150 Term::Match {
2151 discriminant,
2152 motive,
2153 cases,
2154 } => {
2155 children.extend([&**discriminant, &**motive]);
2156 children.extend(cases.iter());
2157 }
2158 Term::Fix { body, .. } => children.push(body),
2159 Term::MutualFix { defs, .. } => {
2160 children.extend(defs.iter().map(|(_, b)| b))
2161 }
2162 Term::Let {
2163 ty, value, body, ..
2164 } => children.extend([&**ty, &**value, &**body]),
2165 Term::Annotated { term, .. } => children.push(term),
2166 }
2167 for child in children.into_iter().rev() {
2168 work.push(Task::Enter(child));
2169 }
2170 }
2171 Task::Exit(t) => {
2172 let mut take = |n: usize| -> Vec<NodeRef> { out.split_off(out.len() - n) };
2173 let node = match t {
2174 Term::Sort(u) => TermNode::Sort(u.clone()),
2175 Term::Var(n) => TermNode::Var(n.clone()),
2176 Term::Global(n) => TermNode::Global(n.clone()),
2177 Term::Const { name, levels } => {
2178 TermNode::Const(name.clone(), levels.clone())
2179 }
2180 Term::Lit(l) => TermNode::Lit(l.clone()),
2181 Term::Hole => TermNode::Hole,
2182 Term::Pi { param, .. } => {
2183 let k = take(2);
2184 TermNode::Pi(param.clone(), k[0], k[1])
2185 }
2186 Term::Lambda { param, .. } => {
2187 let k = take(2);
2188 TermNode::Lambda(param.clone(), k[0], k[1])
2189 }
2190 Term::App(..) => {
2191 let k = take(2);
2192 TermNode::App(k[0], k[1])
2193 }
2194 Term::Match { cases, .. } => {
2195 let k = take(2 + cases.len());
2196 TermNode::Match(k[0], k[1], k[2..].to_vec())
2197 }
2198 Term::Fix { name, .. } => {
2199 let k = take(1);
2200 TermNode::Fix(name.clone(), k[0])
2201 }
2202 Term::MutualFix { defs, index } => {
2203 let k = take(defs.len());
2204 TermNode::MutualFix(
2205 defs.iter().map(|(n, _)| n.clone()).zip(k).collect(),
2206 *index,
2207 )
2208 }
2209 Term::Let { name, .. } => {
2210 let k = take(3);
2211 TermNode::Let(name.clone(), k[0], k[1], k[2])
2212 }
2213 Term::Annotated { .. } => continue,
2216 };
2217 let r = self.row(node);
2218 out.push(r);
2219 }
2220 }
2221 }
2222 out.pop().expect("one root")
2223 }
2224}
2225
2226impl EnvironmentCertificate {
2227 fn check_schema(&self) -> Result<(), EnvironmentError> {
2228 if self.schema != OPAQUE_ENVIRONMENT_SCHEMA && self.schema != ENVIRONMENT_SCHEMA {
2229 return Err(EnvironmentError::new(
2230 EnvironmentErrorCode::UnsupportedSchema,
2231 format!("unsupported certificate schema {:?}; this checker reads {OPAQUE_ENVIRONMENT_SCHEMA} and {ENVIRONMENT_SCHEMA}", self.schema),
2232 ));
2233 }
2234 if self.schema == ENVIRONMENT_SCHEMA
2235 && self
2236 .entries
2237 .iter()
2238 .any(|entry| matches!(entry, EnvironmentEntry::Opaque { .. }))
2239 {
2240 return Err(EnvironmentError::new(
2241 EnvironmentErrorCode::UnsupportedSchema,
2242 format!("an opaque entry requires {OPAQUE_ENVIRONMENT_SCHEMA}; {ENVIRONMENT_SCHEMA} has no opaque entry"),
2243 ));
2244 }
2245 Ok(())
2246 }
2247
2248 pub fn capture(
2252 request: &CaptureRequest<'_>,
2253 ) -> Result<EnvironmentCertificate, EnvironmentError> {
2254 let ctx = request.context;
2255 let base = base_context(request.base);
2256 let in_base = |n: &str| declared(&base, n);
2257 let unsupported =
2258 |why: String| EnvironmentError::new(EnvironmentErrorCode::Unsupported, why);
2259 if request.base == EnvironmentBase::StandardLibrary
2262 && !crate::quotient::same_prelude_base(ctx, &base)
2263 {
2264 return Err(unsupported(
2265 "source context does not retain the standard quotient primitive identities".into(),
2266 ));
2267 }
2268 let item_of = |name: &str| -> Result<Option<Item>, EnvironmentError> {
2269 if request.base == EnvironmentBase::Kernel
2270 && crate::quotient::prelude_kind(ctx, name).is_some()
2271 {
2272 return Err(unsupported(
2273 "legacy prelude quotient identities require the StandardLibrary base".into(),
2274 ));
2275 }
2276 if in_base(name) {
2277 return Ok(None);
2278 }
2279 let inductive = if ctx.is_inductive(name) {
2280 Some(name.to_string())
2281 } else {
2282 ctx.constructor_inductive(name).map(str::to_string)
2283 };
2284 if let Some(ind) = inductive {
2285 let members = ctx
2286 .mutual_block_of(&ind)
2287 .map(|b| b.to_vec())
2288 .unwrap_or_else(|| vec![ind.clone()]);
2289 return Ok(Some(Item::Block(members)));
2290 }
2291 if declared(ctx, name) {
2292 return Ok(Some(Item::Declaration(name.to_string())));
2293 }
2294 Err(EnvironmentError::new(
2295 EnvironmentErrorCode::UnresolvedObligation,
2296 format!("the context does not declare {name}"),
2297 ))
2298 };
2299 let item_terms = |item: &Item| -> Result<Vec<Term>, EnvironmentError> {
2301 match item {
2302 Item::Declaration(name) => {
2303 let mut terms = Vec::new();
2304 if let Some((_, ty, proof)) = checked_theorem(ctx, name) {
2305 terms.extend([ty.clone(), proof.clone()]);
2306 } else if let Some((_, ty, body)) = ctx.get_universe_poly(name) {
2307 terms.extend([ty.clone(), body.clone()]);
2308 } else if let Some(body) = ctx.get_definition_body(name) {
2309 terms.push(body.clone());
2310 terms.extend(ctx.get_definition_type(name).cloned());
2311 } else {
2312 let ty = ctx
2313 .poly_declaration(name)
2314 .map(|(_, t)| t.clone())
2315 .or_else(|| ctx.get_global(name).cloned())
2316 .expect("declared");
2317 terms.push(ty);
2318 if ctx.checked_opaque_dependencies(name).is_some() {
2319 let proof = request.opaque_proofs.get(name).ok_or_else(|| EnvironmentError::new(
2320 EnvironmentErrorCode::MissingEvidence,
2321 format!("the checked proof of the opaque theorem {name} was not retained"),
2322 ))?;
2323 terms.push(proof.clone());
2324 }
2325 if let Some(certificate) = ctx.quotient_primitives.get(name) {
2326 for dependency in &certificate.dependencies {
2327 terms.push(Term::Global(dependency.clone()));
2328 }
2329 }
2330 }
2331 Ok(terms)
2332 }
2333 Item::Block(members) => {
2334 let mut terms = Vec::new();
2335 for member in members {
2336 terms.extend(ctx.get_global(member).cloned());
2337 terms.extend(
2338 ctx.get_constructors(member)
2339 .into_iter()
2340 .map(|(_, t)| t.clone()),
2341 );
2342 }
2343 Ok(terms)
2344 }
2345 }
2346 };
2347 let own_names = |item: &Item| -> BTreeSet<String> {
2348 match item {
2349 Item::Declaration(n) => BTreeSet::from([n.clone()]),
2350 Item::Block(members) => members
2351 .iter()
2352 .flat_map(|m| {
2353 std::iter::once(m.clone()).chain(
2354 ctx.get_constructors(m)
2355 .into_iter()
2356 .map(|(c, _)| c.to_string()),
2357 )
2358 })
2359 .collect(),
2360 }
2361 };
2362
2363 let mut order: Vec<Item> = Vec::new();
2364 let mut done: BTreeSet<Item> = BTreeSet::new();
2365 let mut on_stack: BTreeSet<Item> = BTreeSet::new();
2366 let root = item_of(request.target)?.ok_or_else(|| {
2367 EnvironmentError::new(
2368 EnvironmentErrorCode::TargetNotProved,
2369 format!("{} belongs to the base", request.target),
2370 )
2371 })?;
2372 {
2373 let mut stack: Vec<(Item, Vec<Item>)> = Vec::new();
2374 let children = |item: &Item| -> Result<Vec<Item>, EnvironmentError> {
2375 let own = own_names(item);
2376 let mut names = BTreeSet::new();
2377 for term in item_terms(item)? {
2378 names.extend(cited_names(&term));
2379 }
2380 let mut out = Vec::new();
2381 for name in names.into_iter().filter(|n| !own.contains(n)) {
2382 if let Some(child) = item_of(&name)? {
2383 if !out.contains(&child) {
2384 out.push(child);
2385 }
2386 }
2387 }
2388 out.reverse();
2389 Ok(out)
2390 };
2391 on_stack.insert(root.clone());
2392 let first = children(&root)?;
2393 stack.push((root, first));
2394 while let Some((item, pending)) = stack.last_mut() {
2395 if let Some(next) = pending.pop() {
2396 if done.contains(&next) {
2397 continue;
2398 }
2399 if on_stack.contains(&next) {
2400 return Err(EnvironmentError::new(
2401 EnvironmentErrorCode::DependencyCycle,
2402 format!("{next:?} depends on itself through {item:?}"),
2403 ));
2404 }
2405 on_stack.insert(next.clone());
2406 let grandchildren = children(&next)?;
2407 stack.push((next, grandchildren));
2408 } else {
2409 let (item, _) = stack.pop().expect("nonempty");
2410 on_stack.remove(&item);
2411 done.insert(item.clone());
2412 order.push(item.clone());
2413 if let Item::Block(members) = &item {
2417 for member in members {
2418 let Some(info) = ctx.struct_info(member) else {
2419 continue;
2420 };
2421 for projection in &info.projections {
2422 let projection = Item::Declaration(projection.clone());
2423 if done.contains(&projection) {
2424 continue;
2425 }
2426 if let Some(missing) = children(&projection)?
2427 .into_iter()
2428 .find(|c| !done.contains(c))
2429 {
2430 return Err(unsupported(format!(
2431 "the projection {projection:?} of {member} cites {missing:?}, outside its structure"
2432 )));
2433 }
2434 done.insert(projection.clone());
2435 order.push(projection);
2436 }
2437 }
2438 }
2439 }
2440 }
2441 }
2442
2443 let mut writer = TableWriter {
2444 nodes: Vec::new(),
2445 index: HashMap::new(),
2446 refusal: None,
2447 };
2448 let mut entries = Vec::new();
2449 let mut pending_structures: Vec<(String, Vec<String>)> = Vec::new();
2450 for item in &order {
2451 match item {
2452 Item::Block(members) => {
2453 for member in members {
2454 if let Some(info) = ctx.struct_info(member) {
2455 pending_structures.push((member.clone(), info.projections.clone()));
2456 }
2457 }
2458 let level_params = ctx
2459 .poly_declaration(&members[0])
2460 .map(|(p, _)| p.to_vec())
2461 .unwrap_or_default();
2462 let types = members
2463 .iter()
2464 .map(|member| InductiveType {
2465 name: member.clone(),
2466 arity: writer.write(ctx.get_global(member).expect("inductive")),
2467 num_params: ctx.inductive_num_params(member),
2468 constructors: ctx
2469 .get_constructors(member)
2470 .into_iter()
2471 .map(|(name, ty)| ConstructorClaim {
2472 name: name.to_string(),
2473 ty: writer.write(ty),
2474 })
2475 .collect(),
2476 })
2477 .collect();
2478 entries.push(EnvironmentEntry::Inductive {
2479 level_params,
2480 types,
2481 });
2482 }
2483 Item::Declaration(name) => {
2484 if let Some(certificate) = ctx.quotient_primitives.get(name) {
2485 let (params, ty) = ctx.poly_declaration(name).expect("quotient primitive");
2486 let equality = if certificate.kind == crate::quotient::Kind::Lift {
2487 certificate
2488 .dependencies
2489 .iter()
2490 .find(|d| ctx.is_inductive(d))
2491 .cloned()
2492 } else {
2493 None
2494 };
2495 entries.push(EnvironmentEntry::Quotient {
2496 primitive: QuotientPrimitive::of(certificate.kind),
2497 level_params: params.to_vec(),
2498 ty: writer.write(ty),
2499 equality,
2500 });
2501 } else if let Some((level_params, ty, proof)) = checked_theorem(ctx, name) {
2502 entries.push(EnvironmentEntry::Theorem {
2503 name: name.clone(),
2504 level_params: level_params.to_vec(),
2505 ty: writer.write(ty),
2506 proof: writer.write(proof),
2507 });
2508 } else if let Some((params, ty, body)) = ctx.get_universe_poly(name) {
2509 entries.push(EnvironmentEntry::Definition {
2510 name: name.clone(),
2511 level_params: params.clone(),
2512 ty: writer.write(ty),
2513 body: writer.write(body),
2514 });
2515 } else if let Some(body) = ctx.get_definition_body(name) {
2516 let ty = ctx.get_definition_type(name).expect("definition type");
2517 entries.push(EnvironmentEntry::Definition {
2518 name: name.clone(),
2519 level_params: Vec::new(),
2520 ty: writer.write(ty),
2521 body: writer.write(body),
2522 });
2523 } else {
2524 let level_params = ctx
2525 .poly_declaration(name)
2526 .map(|(p, _)| p.to_vec())
2527 .unwrap_or_default();
2528 let ty = ctx
2529 .poly_declaration(name)
2530 .map(|(_, t)| t)
2531 .or_else(|| ctx.get_global(name))
2532 .expect("declared");
2533 if ctx.checked_opaque_dependencies(name).is_some() {
2534 let proof = &request.opaque_proofs[name];
2535 entries.push(EnvironmentEntry::Opaque {
2536 name: name.clone(),
2537 level_params,
2538 ty: writer.write(ty),
2539 body: writer.write(proof),
2540 });
2541 } else {
2542 entries.push(EnvironmentEntry::Axiom {
2543 name: name.clone(),
2544 level_params,
2545 ty: writer.write(ty),
2546 });
2547 }
2548 }
2549 pending_structures.retain(|(inductive, projections)| {
2551 let placed = projections.iter().all(|p| {
2552 entries.iter().any(|e| matches!(e, EnvironmentEntry::Definition { name, .. } if name == p))
2553 });
2554 if placed {
2555 entries.push(EnvironmentEntry::Structure {
2556 inductive: inductive.clone(),
2557 projections: projections.clone(),
2558 });
2559 }
2560 !placed
2561 });
2562 }
2563 }
2564 }
2565 if let Some((inductive, _)) = pending_structures.first() {
2566 return Err(unsupported(format!(
2567 "the structure η of {inductive} could not be ordered after its projections"
2568 )));
2569 }
2570 if let Some(why) = writer.refusal {
2571 return Err(unsupported(why));
2572 }
2573 let binder_kinds = entries
2574 .iter()
2575 .flat_map(|e| e.names())
2576 .filter_map(|name| {
2577 ctx.binder_kinds(name).map(|kinds| {
2578 (
2579 name.to_string(),
2580 kinds.iter().copied().map(BinderKind::of).collect(),
2581 )
2582 })
2583 })
2584 .collect();
2585 Ok(EnvironmentCertificate {
2586 schema: if entries
2587 .iter()
2588 .any(|entry| matches!(entry, EnvironmentEntry::Opaque { .. }))
2589 {
2590 OPAQUE_ENVIRONMENT_SCHEMA
2591 } else {
2592 ENVIRONMENT_SCHEMA
2593 }
2594 .to_string(),
2595 prelude_version: PRELUDE_VERSION.to_string(),
2596 base: request.base,
2597 conversion: request.conversion,
2598 nodes: writer.nodes,
2599 entries,
2600 target: request.target.to_string(),
2601 binder_kinds,
2602 })
2603 }
2604}
2605
2606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2612pub enum RecheckOutcome {
2613 Verified,
2615 SingleChecked,
2617 Rejected,
2619}
2620
2621#[derive(Debug, Clone, PartialEq, Eq)]
2623pub struct RecheckReport {
2624 pub outcome: RecheckOutcome,
2625 pub lines: Vec<String>,
2626}
2627
2628pub fn recheck_document(json: &str, limits: &EnvironmentLimits) -> RecheckReport {
2634 let rejected = |line: String| RecheckReport {
2635 outcome: RecheckOutcome::Rejected,
2636 lines: vec![line],
2637 };
2638 let certificate = match read_certificate(json) {
2639 Ok(certificate) => certificate,
2640 Err(error) => return rejected(format!("REJECTED: {error}")),
2641 };
2642 match certificate {
2643 AnyCertificate::Proof(certificate) => match recheck(&certificate) {
2644 Ok(()) => RecheckReport {
2645 outcome: RecheckOutcome::Verified,
2646 lines: vec!["VERIFIED".into()],
2647 },
2648 Err(e) => rejected(format!("REJECTED: {e}")),
2649 },
2650 AnyCertificate::Theorem(certificate) => {
2651 let out = recheck_theorem(&certificate);
2652 let axioms = out
2653 .axioms
2654 .axioms
2655 .iter()
2656 .cloned()
2657 .collect::<Vec<_>>()
2658 .join(", ");
2659 match (out.rechecks, out.two_kernel_agreement) {
2660 (true, true) => RecheckReport {
2661 outcome: RecheckOutcome::Verified,
2662 lines: vec![format!("VERIFIED theorem {} (two kernels agree; axioms: [{axioms}])", certificate.theorem)],
2663 },
2664 (true, false) => RecheckReport {
2665 outcome: RecheckOutcome::SingleChecked,
2666 lines: vec![format!(
2667 "SINGLE-CHECKED theorem {}: the independent kernel does not cover this proof (axioms: [{axioms}])",
2668 certificate.theorem
2669 )],
2670 },
2671 _ => rejected(format!("REJECTED: {}", out.error.unwrap_or_default())),
2672 }
2673 }
2674 AnyCertificate::Environment(certificate) => {
2675 let depth = match certificate.admitted_depth(limits) {
2676 Ok(depth) => depth,
2677 Err(error) => return rejected(format!("REJECTED: {error}")),
2678 };
2679 let rungs = depth
2680 .saturating_mul(3)
2681 .saturating_add(crate::term_store::INLINE_DEPTH);
2682 let report = std::thread::scope(|scope| {
2683 std::thread::Builder::new()
2684 .name("recheck-environment".into())
2685 .stack_size(crate::term_store::stack_bytes_for_depth(rungs))
2686 .spawn_scoped(scope, || environment_report(&certificate, limits))
2687 .map(|handle| {
2688 handle
2689 .join()
2690 .unwrap_or_else(|p| std::panic::resume_unwind(p))
2691 })
2692 });
2693 report.unwrap_or_else(|e| {
2694 rejected(format!("REJECTED: cannot reserve the re-check stack: {e}"))
2695 })
2696 }
2697 }
2698}
2699
2700fn environment_report(
2701 certificate: &EnvironmentCertificate,
2702 limits: &EnvironmentLimits,
2703) -> RecheckReport {
2704 let rebuilt = match certificate.rebuild(limits) {
2705 Ok(rebuilt) => rebuilt,
2706 Err(error) => {
2707 return RecheckReport {
2708 outcome: RecheckOutcome::Rejected,
2709 lines: vec![format!("REJECTED: {error}")],
2710 }
2711 }
2712 };
2713 let unsupported: Vec<String> = rebuilt
2714 .independent
2715 .iter()
2716 .filter_map(|(name, verdict)| match verdict {
2717 IndependentVerdict::Unsupported(why) => Some(format!("{name} ({why})")),
2718 IndependentVerdict::Agreed => None,
2719 })
2720 .collect();
2721 let outcome = if unsupported.is_empty() {
2722 RecheckOutcome::Verified
2723 } else {
2724 RecheckOutcome::SingleChecked
2725 };
2726 let mut lines = vec![
2727 format!(
2728 "{} environment certificate for {}: {} declarations rebuilt from the {} base under {} conversion",
2729 if outcome == RecheckOutcome::Verified { "VERIFIED" } else { "SINGLE-CHECKED" },
2730 certificate.target,
2731 certificate.entries.len(),
2732 match certificate.base { EnvironmentBase::Kernel => "bare kernel", EnvironmentBase::StandardLibrary => "standard library" },
2733 match certificate.conversion { ConversionDiscipline::Exact => "exact", ConversionDiscipline::Cumulative => "cumulative" },
2734 ),
2735 format!("primary check: passed ({} {})", if rebuilt.target_is_theorem { "theorem" } else { "definition" }, rebuilt.target),
2736 if unsupported.is_empty() {
2737 format!("independent re-check: passed ({} terms)", rebuilt.independent.len())
2738 } else {
2739 format!("independent re-check: unsupported for {}", unsupported.join("; "))
2740 },
2741 format!(
2742 "assumptions (transitive, statement and proof): [{}]",
2743 rebuilt.axioms.axioms.iter().chain(rebuilt.axioms.unresolved.iter()).cloned().collect::<Vec<_>>().join(", ")
2744 ),
2745 "statement binding: not established by a certificate alone; replay its package to bind the statement to the Lean source".into(),
2746 ];
2747 if !rebuilt.axioms.unresolved.is_empty() {
2748 lines.push(format!("unresolved: {:?}", rebuilt.axioms.unresolved));
2749 }
2750 RecheckReport { outcome, lines }
2751}