1use crate::{
10 defeq_for_test as definitionally_equal, infer_type, normalize_checked, Context, KernelError,
11 Term, Universe,
12};
13use serde_json::Value;
14mod diagnostic;
15pub use diagnostic::{DiagnosticCode, Outcome, Reason};
16pub mod emit;
17pub mod intake;
18pub mod native_decide;
19pub mod package;
20pub mod pipeline;
21pub mod receipt;
22pub mod reference;
23pub mod taint;
24pub use pipeline::run_cli;
25mod beta;
26mod dag;
27mod metadata;
28#[cfg(test)]
30#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/lazy_conversion.rs"]
31mod lazy_conversion;
32mod strings;
33use std::collections::{HashMap, HashSet};
34
35fn parse_natural_literal(digits: &str, budget: &mut usize) -> Result<logicaffeine_base::BigInt> {
40 *budget = budget.checked_sub(digits.len()).ok_or_else(|| {
41 declined(
42 DiagnosticCode::ResourceLimit,
43 "natural literal parsing budget",
44 )
45 })?;
46 if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
47 return Err(declined(
48 DiagnosticCode::MalformedExport,
49 "malformed natural literal",
50 ));
51 }
52 let significant = digits.trim_start_matches('0');
53 if significant.is_empty() {
54 return Ok(logicaffeine_base::BigInt::zero());
55 }
56 let n = significant.len();
57 let work = n.saturating_mul(n / 16 + 1).saturating_mul(2);
58 *budget = budget.checked_sub(work).ok_or_else(|| {
59 declined(
60 DiagnosticCode::ResourceLimit,
61 "natural literal parsing budget",
62 )
63 })?;
64 logicaffeine_base::BigInt::parse_decimal(significant)
65 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "malformed natural literal"))
66}
67
68#[derive(Debug)]
71pub enum Verdict {
72 Reject(Reason),
73 Decline(Reason),
74}
75impl Verdict {
76 pub fn reason(&self) -> &Reason {
77 match self {
78 Self::Reject(reason) | Self::Decline(reason) => reason,
79 }
80 }
81 pub fn code(&self) -> i32 {
83 match self {
84 Self::Reject(_) => 1,
85 Self::Decline(_) => 2,
86 }
87 }
88 pub fn outcome(&self) -> Outcome {
90 Outcome::of_refusal(self.reason().code(), matches!(self, Self::Reject(_)))
91 }
92 pub fn at_line(self, line: usize) -> Verdict {
94 self.map_reason(|reason| reason.at_line(line))
95 }
96 pub fn in_declaration(self, name: impl Into<String>) -> Verdict {
98 let name = name.into();
99 self.map_reason(|reason| reason.in_declaration(name))
100 }
101 fn map_reason(self, f: impl FnOnce(Reason) -> Reason) -> Verdict {
102 match self {
103 Verdict::Reject(reason) => Verdict::Reject(f(reason)),
104 Verdict::Decline(reason) => Verdict::Decline(f(reason)),
105 }
106 }
107}
108fn emit_stage(
109 sink: Option<StageSink<'_>>,
110 declaration: Option<&(usize, String, String, Option<u64>)>,
111 stage: &str,
112 event: &str,
113) {
114 if let (Some(sink), Some((line, name, kind, name_index))) = (sink, declaration) {
115 sink(&serde_json::json!({
116 "schema":"logos-lean-stages/1","line":line,"name":name,
117 "kind":kind,"name_index":name_index,"stage":stage,"event":event,
118 }));
119 }
120}
121type Result<T> = std::result::Result<T, Verdict>;
122impl From<KernelError> for Verdict {
123 fn from(error: KernelError) -> Self {
124 kernel(error)
125 }
126}
127
128fn declined(code: DiagnosticCode, s: impl Into<String>) -> Verdict {
129 Verdict::Decline(Reason::new(code, s))
130}
131fn rejected(code: DiagnosticCode, s: impl Into<String>) -> Verdict {
132 Verdict::Reject(Reason::new(code, s))
133}
134impl Verdict {
135 fn located(self, prefix: impl std::fmt::Display) -> Verdict {
137 self.map_reason(|reason| reason.located(prefix))
138 }
139}
140fn index(v: &Value) -> Result<usize> {
141 v.as_u64()
142 .and_then(|n| usize::try_from(n).ok())
143 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "malformed index"))
144}
145fn array(v: &Value) -> Result<&Vec<Value>> {
146 v.as_array()
147 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "expected array"))
148}
149fn independent_verdict(error: &crate::ReCheckError, what: impl std::fmt::Display) -> Verdict {
153 let code = match error.incomplete() {
154 Some(incomplete) if incomplete.is_resource_limit() => DiagnosticCode::ResourceLimit,
155 _ => DiagnosticCode::RecheckRefused,
156 };
157 Verdict::Decline(
158 Reason::new(code, format!("{what}: {error:?}")).at_stage("independent_recheck"),
159 )
160}
161fn independent_comparison(compared: Result<()>, what: impl std::fmt::Display) -> Result<()> {
165 compared.map_err(|verdict| match verdict {
166 Verdict::Decline(reason) if reason.code() == DiagnosticCode::ResourceLimit => {
167 Verdict::Decline(reason.located(what).at_stage("independent_recheck"))
168 }
169 _ => declined(DiagnosticCode::RecheckRefused, what.to_string()),
170 })
171}
172fn kernel(e: KernelError) -> Verdict {
173 match e {
174 KernelError::IndependentRecheck { subject, error } => {
175 independent_verdict(&error, format!("Certification error: {subject}"))
176 }
177 KernelError::UnboundVariable(_)
178 | KernelError::NotAFunction(_)
179 | KernelError::NotAType(_)
180 | KernelError::TypeMismatch { .. }
181 | KernelError::PositivityViolation { .. }
182 | KernelError::InvalidDeclaration(_) => {
183 rejected(DiagnosticCode::InvalidProof, e.to_string())
184 }
185 _ => declined(DiagnosticCode::UnsupportedFeature, e.to_string()),
186 }
187}
188fn inductive_verdict(error: KernelError) -> Verdict {
192 match error {
193 KernelError::CertificationError(message) => {
194 declined(DiagnosticCode::UnsupportedFeature, message)
195 }
196 other => kernel(other),
197 }
198}
199fn inductive_safety(flag: &Value, message: &str) -> Verdict {
202 match flag {
203 Value::Bool(true) => declined(DiagnosticCode::UnsupportedFeature, message),
204 _ => declined(DiagnosticCode::MalformedExport, message),
205 }
206}
207fn normal(ctx: &Context, t: &Term) -> Result<Term> {
208 normalize_checked(ctx, t).map_err(|e| {
209 let code = match e.1 {
210 crate::Unreduced::Cyclic => DiagnosticCode::UnsupportedFeature,
211 crate::Unreduced::BudgetSpent | crate::Unreduced::TooDeep => {
212 DiagnosticCode::ResourceLimit
213 }
214 };
215 declined(code, format!("normalization unfinished: {e:?}"))
216 })
217}
218fn sort(ctx: &Context, t: &Term) -> Result<Universe> {
219 match normal(ctx, &infer_type(ctx, t).map_err(kernel)?)? {
220 Term::Sort(u) => Ok(u),
221 _ => Err(rejected(
222 DiagnosticCode::InvalidProof,
223 "declaration type is not a type",
224 )),
225 }
226}
227
228fn exact(ctx: &Context, a: &Term, b: &Term) -> Result<()> {
229 let (equal, stopped) =
237 crate::reduction::observe_resource_stops(|| definitionally_equal(ctx, a, b));
238 if equal {
239 return Ok(());
240 }
241 Err(match stopped {
242 Some(reason) => declined(
243 DiagnosticCode::ResourceLimit,
244 format!("Lean conversion undecided: reduction stopped ({reason:?})"),
245 ),
246 None => rejected(
247 DiagnosticCode::InvalidProof,
248 "Lean conversion mismatch (cumulative subtyping is insufficient)",
249 ),
250 })
251}
252#[cfg(test)]
254static FUNCTION_TYPE_NORMALIZATIONS: std::sync::atomic::AtomicUsize =
255 std::sync::atomic::AtomicUsize::new(0);
256
257pub fn lean_type(ctx: &Context, t: &Term) -> Result<Term> {
260 use crate::term_store::{deepen_stack, within_kernel_depth, KernelCall, TermStore, TypeCache};
261 if let Some(result) = crate::annotation::erased(t, |t| lean_type(ctx, t)) {
262 return result;
263 }
264 if !within_kernel_depth(t) {
265 return Err(declined(
266 DiagnosticCode::ResourceLimit,
267 "Lean typing input exceeds the kernel depth limit",
268 ));
269 }
270 if let Some(result) = deepen_stack(t, || lean_type(ctx, t)) {
273 return result;
274 }
275 let call = KernelCall::begin();
276 let mut store = TermStore::new();
277 let mut cache = TypeCache::new(&call);
278 let mut equal = HashMap::new();
279 lean_type_cached(ctx, t, &mut store, &mut cache, &mut equal, &call, &[])
280}
281
282fn lean_type_cached<'call>(
286 ctx: &Context,
287 t: &Term,
288 store: &mut crate::term_store::TermStore,
289 cache: &mut crate::term_store::TypeCache<'call, Term>,
290 equal: &mut HashMap<crate::term_store::EnvId, crate::term_store::ScopedDefEqCache<'call>>,
291 call: &'call crate::term_store::KernelCall,
292 locals: &[(logicaffeine_base::Symbol, crate::term_store::TermId)],
293) -> Result<Term> {
294 let id = store.intern(t);
295 let env = store.env(locals);
296 if let Some(ty) = cache.get(env, id) {
297 return Ok(ty.clone());
298 }
299 let result = (|| {
300 match t {
301 Term::App(f, a) => {
302 let ft = lean_type_cached(ctx, f, store, cache, equal, call, locals)?;
303 let ft = match crate::type_checker::exposed_function_type(ctx, ft) {
308 Ok(exposed) => exposed,
309 Err(ft) => {
310 #[cfg(test)]
311 FUNCTION_TYPE_NORMALIZATIONS
312 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
313 normal(ctx, &ft)?
314 }
315 };
316 let at = lean_type_cached(ctx, a, store, cache, equal, call, locals)?;
317 if let Term::Pi {
318 param,
319 param_type,
320 body_type,
321 } = ft
322 {
323 lean_exact_cached(ctx, &at, ¶m_type, store, equal, call, env)?;
324 return Ok(crate::instantiate_codomain(
325 ¶m,
326 ¶m_type,
327 &body_type,
328 a,
329 ));
330 }
331 return Err(rejected(
332 DiagnosticCode::InvalidProof,
333 "application head is not a function",
334 ));
335 }
336 Term::Pi {
337 param,
338 param_type,
339 body_type,
340 }
341 | Term::Lambda {
342 param,
343 param_type,
344 body: body_type,
345 } => {
346 let domain_sort =
347 lean_type_cached(ctx, param_type, store, cache, equal, call, locals)?;
348 if !matches!(normal(ctx, &domain_sort)?, Term::Sort(_)) {
349 return Err(rejected(
350 DiagnosticCode::InvalidProof,
351 "binder domain is not a type",
352 ));
353 }
354 let name = store.symbol(param);
355 let domain = store.intern(param_type);
356 let mut inner = locals.to_vec();
357 inner.retain(|(n, _)| *n != name);
358 inner.push((name, domain));
359 let body = lean_type_cached(
360 &ctx.extend(param, (**param_type).clone()),
361 body_type,
362 store,
363 cache,
364 equal,
365 call,
366 &inner,
367 )?;
368 if matches!(t, Term::Lambda { .. }) {
369 return Ok(Term::Pi {
370 param: param.clone(),
371 param_type: param_type.clone(),
372 body_type: Box::new(body),
373 });
374 }
375 }
376 Term::Let {
377 name,
378 ty,
379 value,
380 body,
381 } => {
382 lean_type_cached(ctx, ty, store, cache, equal, call, locals)?;
383 let actual = lean_type_cached(ctx, value, store, cache, equal, call, locals)?;
384 lean_exact_cached(ctx, &actual, ty, store, equal, call, env)?;
385 if ctx.get(name).is_none() {
386 std::thread_local! { static LET_PROBE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) }; }
387 struct Probe(bool);
388 impl Drop for Probe {
389 fn drop(&mut self) {
390 LET_PROBE.with(|flag| flag.set(self.0));
391 }
392 }
393 let probe = Probe(LET_PROBE.with(|flag| flag.replace(true)));
394 let nested = probe.0;
395 let symbol = store.symbol(name);
396 let domain = store.intern(ty);
397 let mut inner = locals.to_vec();
398 inner.push((symbol, domain));
399 let result = lean_type_cached(
400 &ctx.extend(name, (**ty).clone()),
401 body,
402 store,
403 cache,
404 equal,
405 call,
406 &inner,
407 );
408 drop(probe);
409 match result {
410 Ok(body_type) => {
411 return Ok(crate::instantiate_codomain(name, ty, &body_type, value))
412 }
413 Err(error) if nested => return Err(error),
414 Err(_) => {}
415 }
416 }
417 return lean_type_cached(
418 ctx,
419 &crate::substitute(body, name, value),
420 store,
421 cache,
422 equal,
423 call,
424 locals,
425 );
426 }
427 _ => {}
428 }
429 infer_type(ctx, t).map_err(kernel)
430 })()?;
431 cache.insert(env, id, result.clone());
432 Ok(result)
433}
434
435fn lean_exact_cached<'call>(
438 ctx: &Context,
439 a: &Term,
440 b: &Term,
441 store: &mut crate::term_store::TermStore,
442 equal: &mut HashMap<crate::term_store::EnvId, crate::term_store::ScopedDefEqCache<'call>>,
443 call: &'call crate::term_store::KernelCall,
444 env: crate::term_store::EnvId,
445) -> Result<()> {
446 use crate::term_store::DefEqCache;
447 let left = store.intern(a);
448 let right = store.intern(b);
449 if let Some(answer) = equal
450 .entry(env)
451 .or_insert_with(|| DefEqCache::scoped(call))
452 .get(left, right)
453 {
454 return if answer {
455 Ok(())
456 } else {
457 Err(rejected(
458 DiagnosticCode::InvalidProof,
459 "Lean conversion mismatch (cumulative subtyping is insufficient)",
460 ))
461 };
462 }
463 let answer = exact(ctx, a, b);
464 let decided = match &answer {
467 Ok(()) => Some(true),
468 Err(Verdict::Reject(_)) => Some(false),
469 Err(Verdict::Decline(_)) => None,
470 };
471 if let Some(decided) = decided {
472 equal.get_mut(&env).unwrap().insert(left, right, decided);
473 }
474 answer
475}
476
477fn apply(f: Term, a: Term) -> Term {
478 Term::App(Box::new(f), Box::new(a))
479}
480fn telescope_len(t: &Term) -> usize {
481 let mut n = 0;
482 let mut cur = t;
483 while let Term::Pi { body_type, .. } = cur {
484 n += 1;
485 cur = body_type;
486 }
487 n
488}
489fn resolved_telescope_len(ctx: &Context, term: &Term) -> Result<usize> {
492 let mut ctx = ctx.clone();
493 let mut term = term.clone();
494 let mut count = 0;
495 loop {
496 if let Term::Pi {
497 param,
498 param_type,
499 body_type,
500 } = term
501 {
502 ctx = ctx.extend(¶m, *param_type);
503 term = *body_type;
504 count += 1;
505 continue;
506 }
507 let mut head = &term;
508 while let Term::App(f, _) = head {
509 head = f;
510 }
511 if matches!(head, Term::Global(n) | Term::Const {name:n, ..} if ctx.is_inductive(n))
512 || matches!(head, Term::Sort(_))
513 {
514 return Ok(count);
515 }
516 term = normal(&ctx, &term)?;
517 if !matches!(term, Term::Pi { .. }) {
518 return Ok(count);
519 }
520 }
521}
522fn declared_arity(ctx: &Context, arity: &Term) -> Result<Term> {
525 crate::context::exposed_arity(ctx, arity, &mut |ctx, t| normal(ctx, t))
526}
527fn open_binder(t: Term, name: &str) -> Result<(Term, Term)> {
528 if let Term::Pi {
529 param,
530 param_type,
531 body_type,
532 } = t
533 {
534 Ok((
535 *param_type,
536 crate::substitute(&body_type, ¶m, &Term::Var(name.into())),
537 ))
538 } else {
539 Err(rejected(
540 DiagnosticCode::InvalidProof,
541 "short declaration telescope",
542 ))
543 }
544}
545fn ctor_rule(
546 ctx: &Context,
547 rec: &str,
548 levels: &[String],
549 rec_ty: &Term,
550 ctor: &str,
551 ctor_ty: &Term,
552 nparams: usize,
553 prefix: usize,
554) -> Result<Term> {
555 let ctor_value = match ctx.poly_declaration(ctor) {
556 Some((parameters, _)) => Term::Const {
557 name: ctor.into(),
558 levels: parameters.iter().cloned().map(Universe::Var).collect(),
559 },
560 None => Term::Global(ctor.into()),
561 };
562 ctor_rule_applied(
563 ctx,
564 rec,
565 levels,
566 rec_ty,
567 ctor_value,
568 ctor_ty.clone(),
569 nparams,
570 prefix,
571 nparams,
572 )
573}
574
575fn ctor_rule_applied(
576 ctx: &Context,
577 rec: &str,
578 levels: &[String],
579 rec_ty: &Term,
580 mut ctor_value: Term,
581 mut cty: Term,
582 nparams: usize,
583 prefix: usize,
584 result_parameters: usize,
585) -> Result<Term> {
586 let mut binders = Vec::new();
587 let mut ty = rec_ty.clone();
588 for i in 0..prefix {
589 let name = format!("arena.rule.{i}");
590 let (domain, body) = open_binder(ty, &name)?;
591 binders.push((name, domain));
592 ty = body;
593 }
594 for (name, _) in binders.iter().take(nparams) {
595 let variable = Term::Var(name.clone());
596 if let Term::Pi {
597 param, body_type, ..
598 } = cty
599 {
600 cty = crate::substitute(&body_type, ¶m, &variable);
601 } else {
602 return Err(rejected(
603 DiagnosticCode::InvalidProof,
604 "constructor parameter telescope is too short",
605 ));
606 }
607 ctor_value = apply(ctor_value, variable);
608 }
609 while matches!(cty, Term::Pi { .. }) {
610 let name = format!("arena.rule.{}", binders.len());
611 let (domain, body) = open_binder(cty, &name)?;
612 ctor_value = apply(ctor_value, Term::Var(name.clone()));
613 binders.push((name, domain));
614 cty = body;
615 }
616 let mut result_args = Vec::new();
617 let mut head = &cty;
618 while let Term::App(f, a) = head {
619 result_args.push((**a).clone());
620 head = f;
621 }
622 result_args.reverse();
623 let mut rhs = if levels.is_empty() {
624 Term::Global(rec.into())
625 } else {
626 Term::Const {
627 name: rec.into(),
628 levels: levels.iter().cloned().map(Universe::Var).collect(),
629 }
630 };
631 for (name, _) in binders.iter().take(prefix) {
632 rhs = apply(rhs, Term::Var(name.clone()));
633 }
634 for index in result_args.into_iter().skip(result_parameters) {
635 rhs = apply(rhs, index);
636 }
637 rhs = apply(rhs, ctor_value);
638 for (name, domain) in binders.into_iter().rev() {
639 rhs = Term::Lambda {
640 param: name,
641 param_type: Box::new(domain),
642 body: Box::new(rhs),
643 };
644 }
645 infer_type(ctx, &rhs).map_err(|e| {
647 declined(
648 DiagnosticCode::UnsupportedFeature,
649 format!("derived rule failed to typecheck: {e}"),
650 )
651 })?;
652 Ok(rhs)
653}
654
655const PROJECTION_NORMAL_FORM_STEPS: usize = 1 << 12;
668fn projection(
669 ctx: &Context,
670 name: &str,
671 index: usize,
672 disc: Term,
673 budget: &mut usize,
674) -> Result<Term> {
675 if *budget == 0 {
676 return Err(declined(
677 DiagnosticCode::ResourceLimit,
678 "projection expansion budget",
679 ));
680 }
681 *budget -= 1;
682 let prefix = format!("arena.projection.{}", *budget);
683 let value_type = lean_type(ctx, &disc)?;
691 let ty = match crate::reduction::normalize_checked_within(
692 ctx,
693 &value_type,
694 PROJECTION_NORMAL_FORM_STEPS,
695 ) {
696 Ok(normal_form) => normal_form,
697 Err((_, crate::Unreduced::BudgetSpent)) => {
698 match crate::type_checker::exposed_inductive_type(ctx, value_type) {
699 Ok(exposed) => match crate::reduction::beta_normal_form(&exposed, &mut (1 << 12)) {
700 Some(contracted) => contracted,
701 None => normal(ctx, &exposed)?,
702 },
703 Err(value_type) => normal(ctx, &value_type)?,
704 }
705 }
706 Err(_) => normal(ctx, &value_type)?,
707 };
708 let mut head = &ty;
709 let mut args = Vec::new();
710 while let Term::App(f, a) = head {
711 args.push((**a).clone());
712 head = f;
713 }
714 args.reverse();
715 let (parent, levels) = match head {
716 Term::Global(n) => (n.as_str(), Vec::new()),
717 Term::Const { name, levels } => (name.as_str(), levels.clone()),
718 _ => {
719 return Err(rejected(
720 DiagnosticCode::InvalidProof,
721 "projection of a non-inductive value",
722 ))
723 }
724 };
725 if parent != name || !ctx.is_inductive(name) {
726 return Err(rejected(
727 DiagnosticCode::InvalidProof,
728 "projection parent mismatch",
729 ));
730 }
731 let ctors = ctx.get_constructors(name);
732 if ctors.len() != 1 {
733 return Err(rejected(
734 DiagnosticCode::InvalidProof,
735 "projection requires exactly one constructor",
736 ));
737 }
738 let parameters = ctx.inductive_num_params(name);
739 let arity = ctx
740 .get_global(name)
741 .ok_or_else(|| rejected(DiagnosticCode::InvalidProof, "missing projection parent"))?
742 .clone();
743 if args.len() != telescope_len(&arity) || args.len() < parameters {
744 return Err(rejected(
745 DiagnosticCode::InvalidProof,
746 "projection parent is not fully applied",
747 ));
748 }
749 let family = |arguments: Vec<Term>| {
750 arguments.into_iter().fold(
751 if levels.is_empty() {
752 Term::Global(name.into())
753 } else {
754 Term::Const {
755 name: name.into(),
756 levels: levels.clone(),
757 }
758 },
759 apply,
760 )
761 };
762 let ctor = if levels.is_empty() {
763 Term::Global(ctors[0].0.into())
764 } else {
765 Term::Const {
766 name: ctors[0].0.into(),
767 levels: levels.clone(),
768 }
769 };
770 let mut cty = infer_type(ctx, &ctor).map_err(kernel)?;
771 let mut arity_rest = if levels.is_empty() {
772 arity
773 } else {
774 infer_type(ctx, &family(Vec::new())).map_err(kernel)?
775 };
776 for arg in &args[..parameters] {
777 let (_, body) = open_binder(cty, &format!("{prefix}.parameter"))?;
778 cty = crate::substitute(&body, &format!("{prefix}.parameter"), arg);
779 let (_, rest) = open_binder(arity_rest, &format!("{prefix}.parameter"))?;
780 arity_rest = crate::substitute(&rest, &format!("{prefix}.parameter"), arg);
781 }
782 let mut motive_context = ctx.clone();
784 let mut index_binders = Vec::new();
785 for i in parameters..args.len() {
786 let binder = format!("{prefix}.index.{}", i - parameters);
787 let (domain, rest) = open_binder(arity_rest, &binder)?;
788 motive_context = motive_context.extend(&binder, domain.clone());
789 index_binders.push((binder, domain));
790 arity_rest = rest;
791 }
792 let generic = if index_binders.is_empty() {
793 ty.clone()
794 } else {
795 family(
796 args[..parameters]
797 .iter()
798 .cloned()
799 .chain(
800 index_binders
801 .iter()
802 .map(|(binder, _)| Term::Var(binder.clone())),
803 )
804 .collect(),
805 )
806 };
807 let maybe_prop = !Universe::Type(0).is_subtype_of(&sort(ctx, &ty)?);
808 let mut field_context = ctx.clone();
809 let mut fields = Vec::new();
810 while matches!(cty, Term::Pi { .. }) {
811 let field = format!("{prefix}.field.{}", fields.len());
812 let (domain, body) = open_binder(cty, &field)?;
813 if maybe_prop
816 && fields.len() < index
817 && crate::substitute(
818 &body,
819 &field,
820 &Term::Global("arena.projection.marker".into()),
821 ) != body
822 && !sort(&field_context, &domain)?.equiv(&Universe::Prop)
823 {
824 return Err(rejected(
825 DiagnosticCode::InvalidProof,
826 "dependent projection traverses a propositional data field",
827 ));
828 }
829 field_context = field_context.extend(&field, domain.clone());
830 fields.push((field, domain));
831 cty = body;
832 }
833 let (selected, field_type) = fields.get(index).ok_or_else(|| {
834 rejected(
835 DiagnosticCode::InvalidProof,
836 "projection index out of range",
837 )
838 })?;
839 let motive_name = format!("{prefix}.value");
840 let motive_var = Term::Var(motive_name.clone());
841 let local = motive_context.extend(&motive_name, generic.clone());
842 let mut output = field_type.clone();
843 for (i, (field, _)) in fields.iter().enumerate().take(index).rev() {
844 let marked = crate::substitute(&output, field, &motive_var);
845 if marked != output {
846 let previous = projection(&local, name, i, motive_var.clone(), budget)?;
847 output = crate::substitute(&output, field, &previous);
848 }
849 }
850 if maybe_prop && !sort(&local, &output)?.equiv(&Universe::Prop) {
851 return Err(rejected(
852 DiagnosticCode::InvalidProof,
853 "a possibly propositional structure cannot project data",
854 ));
855 }
856 let mut case = Term::Var(selected.clone());
857 for (field, domain) in fields.into_iter().rev() {
858 case = Term::Lambda {
859 param: field,
860 param_type: Box::new(domain),
861 body: Box::new(case),
862 };
863 }
864 let mut motive = Term::Lambda {
865 param: motive_name,
866 param_type: Box::new(generic),
867 body: Box::new(output),
868 };
869 for (binder, domain) in index_binders.into_iter().rev() {
870 motive = Term::Lambda {
871 param: binder,
872 param_type: Box::new(domain),
873 body: Box::new(motive),
874 };
875 }
876 let result = Term::Match {
877 discriminant: Box::new(disc),
878 motive: Box::new(motive),
879 cases: vec![case],
880 };
881 infer_type(ctx, &result).map_err(kernel)?;
882 Ok(result)
883}
884
885fn structure_recursor_body(
897 ctx: &Context,
898 rec_ty: &Term,
899 nparams: usize,
900 minor: usize,
901 telescope: usize,
902 projections: &[String],
903) -> Result<Term> {
904 if nparams >= minor || minor + 1 >= telescope {
905 return Err(rejected(
906 DiagnosticCode::InvalidProof,
907 "structure recursor minor premise outside its telescope",
908 ));
909 }
910 let mut binders = Vec::new();
911 let mut ty = rec_ty.clone();
912 for i in 0..telescope {
913 let name = format!("arena.structure.rec.{i}");
914 let (domain, body) = open_binder(ty, &name)?;
915 binders.push((name, domain));
916 ty = body;
917 }
918 let (_, major_type) = &binders[telescope - 1];
919 let mut head = major_type;
920 while let Term::App(f, _) = head {
921 head = f;
922 }
923 let levels = match head {
924 Term::Global(_) => Vec::new(),
925 Term::Const { levels, .. } => levels.clone(),
926 _ => {
927 return Err(rejected(
928 DiagnosticCode::InvalidProof,
929 "structure recursor major premise is not the structure",
930 ))
931 }
932 };
933 let major = Term::Var(binders[telescope - 1].0.clone());
934 let mut result = Term::Var(binders[minor].0.clone());
935 for projection in projections {
936 let body = if levels.is_empty() {
937 ctx.get_definition_body(projection).cloned()
938 } else {
939 ctx.get_universe_poly(projection)
940 .map(|(declared, _, body)| {
941 let subst = declared
942 .iter()
943 .cloned()
944 .zip(levels.iter().cloned())
945 .collect();
946 crate::instantiate_universes(body, &subst)
947 })
948 };
949 let Some(mut projected) = body else {
950 return Err(rejected(
951 DiagnosticCode::InvalidProof,
952 "structure projection has no checked body",
953 ));
954 };
955 let arguments = binders[..nparams]
956 .iter()
957 .map(|(param, _)| Term::Var(param.clone()))
958 .chain(std::iter::once(major.clone()));
959 for argument in arguments {
960 let Term::Lambda { param, body, .. } = projected else {
961 return Err(rejected(
962 DiagnosticCode::InvalidProof,
963 "structure projection arity mismatch",
964 ));
965 };
966 projected = crate::substitute(&body, ¶m, &argument);
967 }
968 result = apply(result, projected);
969 }
970 for (name, domain) in binders.into_iter().rev() {
971 result = Term::Lambda {
972 param: name,
973 param_type: Box::new(domain),
974 body: Box::new(result),
975 };
976 }
977 Ok(result)
978}
979
980fn source_binder_kind(binder: &Value) -> Result<Option<crate::elaborate::ParamKind>> {
981 use crate::elaborate::ParamKind;
982 match binder.get("binderInfo") {
983 None => Ok(None),
984 Some(value) => Ok(Some(match value.as_str() {
985 Some("default") => ParamKind::Explicit,
986 Some("implicit") => ParamKind::Implicit,
987 Some("strictImplicit") => ParamKind::StrictImplicit,
988 Some("instImplicit") => ParamKind::Instance,
989 _ => {
990 return Err(declined(
991 DiagnosticCode::MalformedExport,
992 "unsupported binder information",
993 ))
994 }
995 })),
996 }
997}
998
999type StageSink<'a> = &'a (dyn Fn(&Value) + Sync);
1000
1001struct Importer<'a> {
1002 trace_sink: Option<StageSink<'a>>,
1003 trace_declaration: Option<(usize, String, String, Option<u64>)>,
1004 names: HashMap<usize, String>,
1005 levels: HashMap<usize, Universe>,
1006 level_vars: HashMap<usize, HashSet<String>>,
1007 level_depth: HashMap<usize, usize>,
1008 exprs: HashMap<usize, Value>,
1009 expr_depth: HashMap<usize, usize>,
1010 declarations: HashMap<String, usize>,
1011 ctx: Context,
1012 report: ImportReport,
1013 audit_declarations: bool,
1014 audited_names: HashMap<String, usize>,
1015 runtime_names: HashMap<String, &'static str>,
1016 runtime_target_pending: bool,
1017 retain: RetainSource,
1018 retained: Vec<(String, Term, Term)>,
1019 binder_names: bool,
1020 lean_version: String,
1023}
1024impl<'a> Importer<'a> {
1025 fn new(audit_declarations: bool) -> Self {
1026 Self {
1027 trace_sink: None,
1028 trace_declaration: None,
1029 names: HashMap::from([(0, String::new())]),
1030 levels: HashMap::from([(0, Universe::Prop)]),
1031 level_vars: HashMap::from([(0, HashSet::new())]),
1032 level_depth: HashMap::from([(0, 0)]),
1033 exprs: HashMap::new(),
1034 expr_depth: HashMap::new(),
1035 declarations: HashMap::new(),
1036 ctx: Context::new(),
1037 report: ImportReport {
1038 declaration_audit_requested: audit_declarations,
1039 ..ImportReport::default()
1040 },
1041 audit_declarations,
1042 audited_names: HashMap::new(),
1043 runtime_names: HashMap::new(),
1044 runtime_target_pending: false,
1045 retain: RetainSource::Nothing,
1046 retained: Vec::new(),
1047 binder_names: false,
1048 lean_version: String::new(),
1049 }
1050 }
1051 fn name(&self, v: &Value) -> Result<String> {
1052 let name = self
1053 .names
1054 .get(&index(v)?)
1055 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "unknown name index"))?;
1056 Ok(canonical_name(name))
1057 }
1058 fn level(&self, v: &Value, params: Option<&HashSet<String>>) -> Result<Universe> {
1059 let i = index(v)?;
1060 let u = self
1061 .levels
1062 .get(&i)
1063 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "unknown level index"))?;
1064 if let Some(bound) = params {
1065 if !self.level_vars[&i].is_subset(bound) {
1066 return Err(rejected(
1067 DiagnosticCode::InvalidProof,
1068 "unbound universe parameter",
1069 ));
1070 }
1071 }
1072 Ok(u.clone())
1073 }
1074 fn check_runtime_references(&self, roots: &[&Value]) -> Result<()> {
1077 if self.runtime_names.is_empty() {
1078 return Ok(());
1079 }
1080 let mut work = roots
1081 .iter()
1082 .map(|id| index(id))
1083 .collect::<Result<Vec<_>>>()?;
1084 let mut seen = HashSet::new();
1085 while let Some(id) = work.pop() {
1086 if !seen.insert(id) {
1087 continue;
1088 }
1089 let row = self.exprs.get(&id).ok_or_else(|| {
1090 declined(
1091 DiagnosticCode::MalformedExport,
1092 "unknown safety-dependency expression",
1093 )
1094 })?;
1095 if let Some(constant) = row.get("const") {
1096 let name = self.name(&constant["name"])?;
1097 if let Some(safety) = self.runtime_names.get(&name) {
1098 return Err(rejected(
1099 DiagnosticCode::InvalidProof,
1100 format!("safe declaration references {safety} runtime declaration {name}"),
1101 ));
1102 }
1103 }
1104 for child in record_children(row) {
1105 work.push(index(child)?);
1106 }
1107 }
1108 Ok(())
1109 }
1110 fn binder_param(&self, depth: usize, name: &Value) -> String {
1115 let base = format!("arena.local.{depth}");
1116 if !self.binder_names {
1117 return base;
1118 }
1119 match index(name)
1120 .ok()
1121 .and_then(|i| self.names.get(&i))
1122 .filter(|n| !n.is_empty())
1123 {
1124 Some(encoded) => format!("{base}.{}", intake::lean_name(encoded)),
1125 None => base,
1126 }
1127 }
1128 fn term(
1129 &self,
1130 id: &Value,
1131 bound: &mut Vec<(String, Term, Option<Term>)>,
1132 params: &HashSet<String>,
1133 budget: &mut usize,
1134 ) -> Result<Term> {
1135 self.term_core(
1136 id,
1137 bound,
1138 params,
1139 budget,
1140 None,
1141 &mut ImportReport::default(),
1142 &mut None,
1143 )
1144 }
1145
1146 fn shared_term(
1147 &mut self,
1148 id: &Value,
1149 bound: &mut Vec<(String, Term, Option<Term>)>,
1150 params: &HashSet<String>,
1151 budget: &mut usize,
1152 ) -> Result<Term> {
1153 let plan = dag::Plan::new(self, index(id)?, bound.len())?;
1154 if plan.size(index(id)?) <= *budget && plan.depth(index(id)?) < dag::CLOSED_CHUNK_DEPTH {
1155 return self.term(id, bound, params, budget);
1156 }
1157 let mut context = self.ctx.clone();
1158 let mut report = ImportReport::default();
1159 let mut sharing = Some(dag::Sharing::new(plan, params));
1160 let result = self.term_core(
1161 id,
1162 bound,
1163 params,
1164 budget,
1165 Some(&mut context),
1166 &mut report,
1167 &mut sharing,
1168 );
1169 let term = result?;
1170 self.ctx = context;
1171 self.report.internal_helpers.extend(report.internal_helpers);
1172 Ok(term)
1173 }
1174
1175 fn term_core(
1176 &self,
1177 id: &Value,
1178 bound: &mut Vec<(String, Term, Option<Term>)>,
1179 params: &HashSet<String>,
1180 budget: &mut usize,
1181 mut ctx: Option<&mut Context>,
1182 report: &mut ImportReport,
1183 sharing: &mut Option<dag::Sharing>,
1184 ) -> Result<Term> {
1185 enum Frame<'a> {
1186 Visit(usize, bool),
1187 FinishSharing(dag::Capture),
1188 App(usize),
1189 BinderType(usize, &'a Value, &'static str),
1190 BinderValue(usize, &'a Value, Term, usize),
1191 BetaDomain(usize, &'a Value, &'a Value),
1192 BetaValue(usize, &'a Value, usize, Term, usize),
1193 BinderBody {
1194 source: usize,
1195 param: String,
1196 ty: Term,
1197 ty_depth: usize,
1198 value: Option<Term>,
1199 value_depth: usize,
1200 lowered: bool,
1201 kind: &'static str,
1202 },
1203 Projection(usize, String, usize),
1204 }
1205 let initial_scope = bound.len();
1206 let result = (|| {
1207 let mut work = vec![Frame::Visit(index(id)?, false)];
1208 let mut values = Vec::<Term>::new();
1209 let mut depths = Vec::<usize>::new();
1210 let mut checked_lets = HashSet::<usize>::new();
1211 let mut closed_let_checks = HashSet::<(usize, usize)>::new();
1216 while let Some(frame) = work.pop() {
1217 let mut completed: Option<(usize, Term, usize)> = None;
1218 match frame {
1219 Frame::Visit(id, unshared) => {
1220 if !unshared {
1221 if let Some(shared) = sharing.as_mut() {
1222 if shared.plan.shared(id) {
1223 let capture = shared.capture_budgeted(id, bound, budget)?;
1224 if let Some(term) = shared.cached(&capture, budget)? {
1225 depths.push(crate::term_store::term_depth(&term));
1226 values.push(term);
1227 } else {
1228 work.push(Frame::FinishSharing(capture));
1229 work.push(Frame::Visit(id, true));
1230 }
1231 continue;
1232 }
1233 }
1234 }
1235 if *budget == 0 {
1236 return Err(declined(
1237 DiagnosticCode::ResourceLimit,
1238 "term expansion budget",
1239 ));
1240 }
1241 *budget -= 1;
1242 let row = self.exprs.get(&id).ok_or_else(|| {
1243 declined(DiagnosticCode::MalformedExport, "unknown expression index")
1244 })?;
1245 if let Some(x) = row.get("app") {
1246 if let Some(shared) = sharing.as_mut() {
1247 let function = index(&x["fn"])?;
1248 if let Some(lambda) =
1249 self.exprs.get(&function).and_then(|row| row.get("lam"))
1250 {
1251 if shared.beta.is_none() {
1252 shared.beta = Some(beta::Certificates::new(
1253 self.ctx.clone(),
1254 params,
1255 bound,
1256 budget,
1257 )?);
1258 }
1259 let certificates =
1260 shared.beta.as_mut().expect("source certificate pass");
1261 if certificates.certify(self, &shared.plan, id, budget)? {
1262 work.push(Frame::BetaDomain(id, lambda, &x["arg"]));
1263 work.push(Frame::Visit(index(&lambda["type"])?, false));
1264 continue;
1265 }
1266 }
1267 }
1268 work.push(Frame::App(id));
1269 work.push(Frame::Visit(index(&x["arg"])?, false));
1270 work.push(Frame::Visit(index(&x["fn"])?, false));
1271 } else if let Some(x) = row.get("mdata") {
1272 work.push(Frame::Visit(index(&x["expr"])?, false));
1275 } else if let Some(x) = row.get("proj") {
1276 work.push(Frame::Projection(
1277 id,
1278 self.name(&x["typeName"])?,
1279 index(&x["idx"])?,
1280 ));
1281 work.push(Frame::Visit(index(&x["struct"])?, false));
1282 } else if let Some((kind, x)) = ["forallE", "lam", "letE"]
1283 .into_iter()
1284 .find_map(|kind| row.get(kind).map(|x| (kind, x)))
1285 {
1286 work.push(Frame::BinderType(id, x, kind));
1287 work.push(Frame::Visit(index(&x["type"])?, false));
1288 } else if row.get("strVal").is_some() {
1289 let term = strings::expand(
1290 self,
1291 row,
1292 id,
1293 budget,
1294 ctx.as_deref_mut(),
1295 report,
1296 sharing,
1297 )?;
1298 let depth = crate::term_store::term_depth(&term);
1299 completed = Some((id, term, depth));
1300 } else {
1301 let term = if let Some(index_value) = row.get("bvar") {
1302 let position = bound
1303 .len()
1304 .checked_sub(index(index_value)?.saturating_add(1))
1305 .ok_or_else(|| {
1306 rejected(
1307 DiagnosticCode::MalformedExport,
1308 "free bound variable",
1309 )
1310 })?;
1311 if checked_lets.contains(&position) {
1312 sharing
1313 .as_mut()
1314 .expect("checked let transaction")
1315 .copy_checked_value(
1316 bound[position].2.as_ref().expect("checked let value"),
1317 budget,
1318 )?
1319 } else {
1320 self.term_atom(row, bound, params, budget)?
1321 }
1322 } else {
1323 self.term_atom(row, bound, params, budget)?
1324 };
1325 let depth = crate::term_store::term_depth(&term);
1326 completed = Some((id, term, depth));
1327 }
1328 }
1329 Frame::FinishSharing(capture) => {
1330 let body = values.pop().expect("decoded shared source template");
1331 depths.pop().expect("shared source depth");
1332 let term = sharing.as_mut().expect("active sharing plan").finish(
1333 ctx.as_deref_mut().expect("sharing transaction"),
1334 report,
1335 capture,
1336 body,
1337 budget,
1338 )?;
1339 depths.push(crate::term_store::term_depth(&term));
1340 values.push(term);
1341 }
1342 Frame::App(source) => {
1343 let arg = values.pop().expect("decoded application argument");
1344 let fun = values.pop().expect("decoded application function");
1345 let arg_depth = depths.pop().expect("application argument depth");
1346 let fun_depth = depths.pop().expect("application function depth");
1347 completed = Some((
1348 source,
1349 Term::App(Box::new(fun), Box::new(arg)),
1350 1 + arg_depth.max(fun_depth),
1351 ));
1352 }
1353 Frame::BinderType(source, x, kind) => {
1354 let ty = values.pop().expect("decoded binder domain");
1355 let ty_depth = depths.pop().expect("binder domain depth");
1356 if kind == "letE" {
1357 work.push(Frame::BinderValue(source, x, ty, ty_depth));
1358 work.push(Frame::Visit(index(&x["value"])?, false));
1359 } else {
1360 let param = self.binder_param(bound.len(), &x["name"]);
1361 if let Some(certificates) =
1362 sharing.as_mut().and_then(|s| s.beta.as_mut())
1363 {
1364 certificates.enter(&ty, budget)?;
1365 }
1366 bound.push((param.clone(), ty.clone(), None));
1367 work.push(Frame::BinderBody {
1368 source,
1369 param,
1370 ty,
1371 ty_depth,
1372 value: None,
1373 value_depth: 0,
1374 lowered: false,
1375 kind,
1376 });
1377 work.push(Frame::Visit(index(&x["body"])?, false));
1378 }
1379 }
1380 Frame::BetaDomain(source, lambda, argument) => {
1381 let ty = values.pop().expect("certified beta domain");
1382 let ty_depth = depths.pop().expect("certified beta domain depth");
1383 work.push(Frame::BetaValue(
1384 source,
1385 lambda,
1386 index(argument)?,
1387 ty,
1388 ty_depth,
1389 ));
1390 work.push(Frame::Visit(index(argument)?, false));
1391 }
1392 Frame::BetaValue(source, lambda, value_source, ty, ty_depth) => {
1393 let mut value = values.pop().expect("certified beta value");
1394 let mut value_depth = depths.pop().expect("certified beta value depth");
1395 let lowered = sharing
1396 .as_mut()
1397 .expect("beta sharing transaction")
1398 .check_closed_let(
1399 ctx.as_deref().expect("beta transaction"),
1400 &ty,
1401 &value,
1402 budget,
1403 )?;
1404 if lowered {
1405 value = sharing
1406 .as_mut()
1407 .expect("beta sharing transaction")
1408 .share_closed_value(
1409 ctx.as_deref_mut().expect("beta transaction"),
1410 report,
1411 value_source,
1412 value,
1413 budget,
1414 )?;
1415 value_depth = crate::term_store::term_depth(&value);
1416 }
1417 let param = self.binder_param(bound.len(), &lambda["name"]);
1420 if lowered {
1421 checked_lets.insert(bound.len());
1422 }
1423 if let Some(certificates) = sharing.as_mut().and_then(|s| s.beta.as_mut()) {
1424 certificates.enter(&ty, budget)?;
1425 }
1426 bound.push((param.clone(), ty.clone(), Some(value.clone())));
1427 work.push(Frame::BinderBody {
1428 source,
1429 param,
1430 ty,
1431 ty_depth,
1432 value: Some(value),
1433 value_depth,
1434 lowered,
1435 kind: "letE",
1436 });
1437 work.push(Frame::Visit(index(&lambda["body"])?, false));
1438 }
1439 Frame::BinderValue(source, x, ty, ty_depth) => {
1440 let mut value = values.pop().expect("decoded let value");
1441 let mut value_depth = depths.pop().expect("let value depth");
1442 let source_pair = (index(&x["type"])?, index(&x["value"])?);
1443 let let_body = index(&x["body"])?;
1444 let retained = sharing
1445 .as_ref()
1446 .is_none_or(|shared| shared.retains_let(let_body));
1447 let reusable = sharing.as_ref().is_some_and(|shared| {
1448 shared.plan.closed(source_pair.0) && shared.plan.closed(source_pair.1)
1449 });
1450 let lowered = if retained {
1451 false
1452 } else if reusable && closed_let_checks.contains(&source_pair) {
1453 true
1454 } else if let Some(shared) = sharing.as_mut() {
1455 let checked = shared.check_closed_let(
1456 ctx.as_deref().expect("let transaction"),
1457 &ty,
1458 &value,
1459 budget,
1460 )?;
1461 if checked && reusable {
1462 closed_let_checks.insert(source_pair);
1463 }
1464 checked
1465 } else {
1466 false
1467 };
1468 if lowered {
1469 value = sharing
1470 .as_mut()
1471 .expect("let sharing transaction")
1472 .share_closed_value(
1473 ctx.as_deref_mut().expect("let transaction"),
1474 report,
1475 index(&x["value"])?,
1476 value,
1477 budget,
1478 )?;
1479 value_depth = crate::term_store::term_depth(&value);
1480 }
1481 let param = self.binder_param(bound.len(), &x["name"]);
1482 if lowered {
1483 checked_lets.insert(bound.len());
1484 }
1485 if let Some(certificates) = sharing.as_mut().and_then(|s| s.beta.as_mut()) {
1486 certificates.enter(&ty, budget)?;
1487 }
1488 bound.push((param.clone(), ty.clone(), Some(value.clone())));
1489 work.push(Frame::BinderBody {
1490 source,
1491 param,
1492 ty,
1493 ty_depth,
1494 value: Some(value),
1495 value_depth,
1496 lowered,
1497 kind: "letE",
1498 });
1499 work.push(Frame::Visit(index(&x["body"])?, false));
1500 }
1501 Frame::BinderBody {
1502 source,
1503 param,
1504 ty,
1505 ty_depth,
1506 value,
1507 value_depth,
1508 lowered,
1509 kind,
1510 } => {
1511 checked_lets.remove(&(bound.len() - 1));
1512 if let Some(certificates) = sharing.as_mut().and_then(|s| s.beta.as_mut()) {
1513 certificates.leave();
1514 }
1515 bound.pop();
1516 let body = values.pop().expect("decoded binder body");
1517 let body_depth = depths.pop().expect("binder body depth");
1518 if lowered {
1519 completed = Some((source, body, body_depth));
1520 } else {
1521 let body = Box::new(body);
1522 let param_type = Box::new(ty);
1523 let term = match kind {
1524 "forallE" => Term::Pi {
1525 param,
1526 param_type,
1527 body_type: body,
1528 },
1529 "lam" => Term::Lambda {
1530 param,
1531 param_type,
1532 body,
1533 },
1534 _ => Term::Let {
1535 name: param,
1536 ty: param_type,
1537 value: Box::new(value.expect("let value")),
1538 body,
1539 },
1540 };
1541 completed =
1542 Some((source, term, 1 + body_depth.max(ty_depth).max(value_depth)));
1543 }
1544 }
1545 Frame::Projection(source, name, field) => {
1546 let disc = values.pop().expect("decoded projection discriminant");
1547 depths.pop().expect("projection discriminant depth");
1548 let mut local = ctx.as_deref().unwrap_or(&self.ctx).clone();
1549 for (name, ty, _) in bound.iter() {
1550 local = local.extend(name, ty.clone());
1551 }
1552 let mut trial_budget = *budget;
1553 let projected =
1554 projection(&local, &name, field, disc.clone(), &mut trial_budget);
1555 *budget = trial_budget;
1558 match projected {
1559 Ok(projected) => {
1560 let depth = crate::term_store::term_depth(&projected);
1561 completed = Some((source, projected, depth));
1562 }
1563 Err(error) if !bound.iter().any(|(_, _, value)| value.is_some()) => {
1564 return Err(error)
1565 }
1566 Err(_) => {
1567 let expand = |term: &Term, prefix: usize| {
1571 let mut term = term.clone();
1572 for (local, _, value) in bound[..prefix].iter().rev() {
1573 if let Some(value) = value {
1574 term = crate::substitute(&term, local, value);
1575 }
1576 }
1577 term
1578 };
1579 let mut transparent = ctx.as_deref().unwrap_or(&self.ctx).clone();
1580 for (i, (local, ty, _)) in bound.iter().enumerate() {
1581 transparent = transparent.extend(local, expand(ty, i));
1582 }
1583 let projected = projection(
1584 &transparent,
1585 &name,
1586 field,
1587 expand(&disc, bound.len()),
1588 budget,
1589 )?;
1590 let depth = crate::term_store::term_depth(&projected);
1591 completed = Some((source, projected, depth));
1592 }
1593 }
1594 }
1595 }
1596 if let Some((source, mut term, mut depth)) = completed {
1597 if depth >= dag::CLOSED_CHUNK_DEPTH
1601 && sharing.is_some()
1602 && work
1603 .iter()
1604 .rev()
1605 .any(|frame| !matches!(frame, Frame::BinderBody { lowered: true, .. }))
1606 {
1607 if let Some(shared) = sharing.as_mut() {
1608 let (cut, did_cut) = shared.cut_closed(
1609 ctx.as_deref_mut().expect("closed chunk transaction"),
1610 report,
1611 source,
1612 term,
1613 budget,
1614 )?;
1615 term = cut;
1616 if did_cut {
1617 depth = 1;
1618 }
1619 }
1620 }
1621 depths.push(depth);
1622 values.push(term);
1623 }
1624 }
1625 let term = values.pop().expect("decoded expression");
1626 debug_assert!(values.is_empty());
1627 Ok(term)
1628 })();
1629 bound.truncate(initial_scope);
1630 result
1631 }
1632 fn term_atom(
1633 &self,
1634 r: &Value,
1635 bound: &[(String, Term, Option<Term>)],
1636 params: &HashSet<String>,
1637 budget: &mut usize,
1638 ) -> Result<Term> {
1639 if let Some(x) = r.get("sort") {
1640 return Ok(Term::Sort(self.level(x, Some(params))?));
1641 }
1642 if let Some(x) = r.get("bvar") {
1643 return bound
1644 .len()
1645 .checked_sub(index(x)?.saturating_add(1))
1646 .map(|i| Term::Var(bound[i].0.clone()))
1647 .ok_or_else(|| rejected(DiagnosticCode::MalformedExport, "free bound variable"));
1648 }
1649 if let Some(x) = r.get("const") {
1650 let name = self.name(&x["name"])?;
1651 if let Some(safety) = self.runtime_names.get(&name) {
1652 return Err(rejected(
1653 DiagnosticCode::InvalidProof,
1654 format!("safe term references {safety} runtime declaration {name}"),
1655 ));
1656 }
1657 let arity = self.declarations.get(&name).ok_or_else(|| {
1658 rejected(
1659 DiagnosticCode::MissingDeclaration,
1660 "undeclared or forward constant",
1661 )
1662 })?;
1663 let levels = array(&x["us"])?
1664 .iter()
1665 .map(|u| self.level(u, Some(params)))
1666 .collect::<Result<Vec<_>>>()?;
1667 if levels.len() != *arity {
1668 return Err(rejected(
1669 DiagnosticCode::InvalidProof,
1670 "constant universe arity mismatch",
1671 ));
1672 }
1673 return Ok(if levels.is_empty() {
1674 Term::Global(name)
1675 } else {
1676 Term::Const { name, levels }
1677 });
1678 }
1679 if let Some(x) = r.get("natVal") {
1680 let digits = x.as_str().ok_or_else(|| {
1681 declined(DiagnosticCode::MalformedExport, "malformed natural literal")
1682 })?;
1683 let value = parse_natural_literal(digits, budget)?;
1684 let nat = "Nat";
1685 let zero = "Zero";
1686 let succ = "Succ";
1687 let ctors = self.ctx.get_constructors(nat);
1688 if self.declarations.get(nat) != Some(&0)
1689 || ctors.len() != 2
1690 || ctors[0].0 != zero
1691 || ctors[1].0 != succ
1692 || self.ctx.inductive_num_params(nat) != 0
1693 {
1694 return Err(rejected(
1695 DiagnosticCode::InvalidProof,
1696 "natural literal requires canonical checked Nat",
1697 ));
1698 }
1699 let nt = Term::Global(nat.into());
1700 let declared = self
1701 .ctx
1702 .get_global(nat)
1703 .ok_or_else(|| rejected(DiagnosticCode::MissingDeclaration, "missing Nat"))?;
1704 exact(&self.ctx, declared, &Term::Sort(Universe::Type(0)))?;
1705 exact(&self.ctx, ctors[0].1, &nt)?;
1706 exact(
1707 &self.ctx,
1708 ctors[1].1,
1709 &Term::Pi {
1710 param: "arena.nat.value".into(),
1711 param_type: Box::new(nt.clone()),
1712 body_type: Box::new(nt),
1713 },
1714 )?;
1715 return Ok(Term::Lit(crate::Literal::Nat(value)));
1716 }
1717 Err(declined(
1718 DiagnosticCode::UnknownRecordKind,
1719 "unsupported expression (metadata or new format)",
1720 ))
1721 }
1722
1723 fn parameters(&self, d: &Value) -> Result<Vec<String>> {
1724 let levels = array(&d["levelParams"])?
1725 .iter()
1726 .map(|x| self.name(x))
1727 .collect::<Result<Vec<_>>>()?;
1728 if levels.iter().collect::<HashSet<_>>().len() != levels.len() {
1729 return Err(rejected(
1730 DiagnosticCode::InvalidProof,
1731 "duplicate universe parameter",
1732 ));
1733 }
1734 Ok(levels)
1735 }
1736 fn declaration_term(&self, id: &Value, params: &[String]) -> Result<Term> {
1737 self.term(
1738 id,
1739 &mut Vec::new(),
1740 ¶ms.iter().cloned().collect(),
1741 &mut 100_000,
1742 )
1743 }
1744 fn structure_view(
1747 &mut self,
1748 name: &str,
1749 levels: &[String],
1750 ctor_type: &Term,
1751 ) -> Result<Option<Vec<String>>> {
1752 if self.ctx.block_is_recursive(name) {
1755 return Ok(None);
1756 }
1757 let parameters = self.ctx.inductive_num_params(name);
1758 let mut ty = ctor_type.clone();
1759 let mut params = Vec::new();
1760 let mut local = self.ctx.clone();
1761 let mut carrier = if levels.is_empty() {
1762 Term::Global(name.into())
1763 } else {
1764 Term::Const {
1765 name: name.into(),
1766 levels: levels.iter().cloned().map(Universe::Var).collect(),
1767 }
1768 };
1769 for i in 0..parameters {
1770 let param = format!("arena.structure.parameter.{i}");
1771 let (domain, body) = open_binder(ty, ¶m)?;
1772 local = local.extend(¶m, domain.clone());
1773 carrier = apply(carrier, Term::Var(param.clone()));
1774 params.push((param, domain));
1775 ty = body;
1776 }
1777 if !Universe::Type(0).is_subtype_of(&sort(&local, &carrier)?) {
1780 return Ok(None);
1781 }
1782 let mut fields = Vec::new();
1783 while matches!(ty, Term::Pi { .. }) {
1784 let field = format!("arena.structure.field.{}", fields.len());
1785 let (domain, body) = open_binder(ty, &field)?;
1786 fields.push((field, domain));
1787 ty = body;
1788 }
1789 let disc_name = "arena.structure.value";
1790 local = local.extend(disc_name, carrier.clone());
1791 let mut scratch = self.ctx.clone();
1792 let mut projections = Vec::new();
1793 for i in 0..fields.len() {
1794 let mut body = projection(&local, name, i, Term::Var(disc_name.into()), &mut 100_000)?;
1795 body = Term::Lambda {
1796 param: disc_name.into(),
1797 param_type: Box::new(carrier.clone()),
1798 body: Box::new(body),
1799 };
1800 for (param, domain) in params.iter().rev() {
1801 body = Term::Lambda {
1802 param: param.clone(),
1803 param_type: Box::new(domain.clone()),
1804 body: Box::new(body),
1805 };
1806 }
1807 let ty = infer_type(&scratch, &body).map_err(kernel)?;
1808 let projection_name = format!("arena.structure.projection.{name}.{i}");
1809 if levels.is_empty() {
1810 scratch
1811 .declare(&projection_name, ty, body)
1812 .map_err(kernel)?;
1813 } else {
1814 scratch
1815 .declare_universe_poly(&projection_name, levels.to_vec(), ty, body)
1816 .map_err(kernel)?;
1817 }
1818 projections.push(projection_name);
1819 }
1820 match scratch.register_structure_checked(name, &projections) {
1825 Ok(()) => {}
1826 Err(crate::StructureRefusal::Recursive { .. }) => return Ok(None),
1827 Err(refusal) => {
1828 return Err(declined(
1829 DiagnosticCode::UnsupportedFeature,
1830 format!("structure η for {name}: {refusal}"),
1831 ))
1832 }
1833 }
1834 self.ctx = scratch;
1835 Ok(Some(projections))
1836 }
1837
1838 fn quotient(&mut self, d: &Value) -> Result<()> {
1839 use crate::quotient::{self, Kind};
1840 let kind = match d["kind"].as_str() {
1841 Some("type") => Kind::Type,
1842 Some("ctor") => Kind::Constructor,
1843 Some("lift") => Kind::Lift,
1844 Some("ind") => Kind::Induction,
1845 _ => {
1846 return Err(rejected(
1847 DiagnosticCode::UnknownRecordKind,
1848 "unknown quotient primitive kind",
1849 ))
1850 }
1851 };
1852 let name = self.name(&d["name"])?;
1853 if name != kind.name() {
1854 return Err(rejected(
1855 DiagnosticCode::InvalidProof,
1856 "quotient primitive name does not match its kind",
1857 ));
1858 }
1859 if self.declarations.contains_key(&name) {
1860 return Err(rejected(
1861 DiagnosticCode::DuplicateDeclaration,
1862 "duplicate quotient declaration",
1863 ));
1864 }
1865 let params = self.parameters(d)?;
1866 if params.len() != kind.arity() {
1867 return Err(rejected(
1868 DiagnosticCode::InvalidProof,
1869 "quotient primitive universe arity",
1870 ));
1871 }
1872 let ty = self.declaration_term(&d["type"], ¶ms)?;
1873 let levels = params
1874 .iter()
1875 .cloned()
1876 .map(Universe::Var)
1877 .collect::<Vec<_>>();
1878 let equality = "/s:\"Eq\"";
1879 let expected = quotient::signature(kind, &levels, equality).map_err(kernel)?;
1880 lean_type(&self.ctx, &ty)?;
1881 exact(&self.ctx, &ty, &expected)?;
1882 quotient::install(&mut self.ctx, kind, params.clone(), equality)
1883 .map_err(|e| rejected(DiagnosticCode::InvalidProof, e.to_string()))?;
1884 self.declarations.insert(name, params.len());
1885 Ok(())
1886 }
1887
1888 fn install_binder_kinds(&mut self, declaration: &Value) -> Result<()> {
1889 let parameters = match declaration.get("value") {
1895 Some(value) => Some(self.leading_binders(index(value)?, "lam")?),
1896 None => None,
1897 };
1898 let mut expression = index(&declaration["type"])?;
1899 let mut kinds = Vec::new();
1900 let mut binders = 0;
1901 let mut complete = true;
1902 while parameters.is_none_or(|count| binders < count) {
1903 let row = self.exprs.get(&expression).ok_or_else(|| {
1904 declined(
1905 DiagnosticCode::MalformedExport,
1906 "missing declaration binder expression",
1907 )
1908 })?;
1909 if let Some(metadata) = row.get("mdata") {
1910 expression = index(&metadata["expr"])?;
1911 } else if let Some(binder) = row.get("forallE") {
1912 match source_binder_kind(binder)? {
1913 Some(kind) => kinds.push(kind),
1914 None => complete = false,
1915 }
1916 binders += 1;
1917 expression = index(&binder["body"])?;
1918 } else {
1919 break;
1920 }
1921 }
1922 if complete {
1926 let name = self.name(&declaration["name"])?;
1927 self.ctx.set_binder_kinds(&name, kinds);
1928 }
1929 Ok(())
1930 }
1931
1932 fn leading_binders(&self, mut expression: usize, kind: &str) -> Result<usize> {
1934 let mut count = 0;
1935 loop {
1936 let row = self.exprs.get(&expression).ok_or_else(|| {
1937 declined(
1938 DiagnosticCode::MalformedExport,
1939 "missing declaration binder expression",
1940 )
1941 })?;
1942 if let Some(metadata) = row.get("mdata") {
1943 expression = index(&metadata["expr"])?;
1944 } else if let Some(binder) = row.get(kind) {
1945 count += 1;
1946 expression = index(&binder["body"])?;
1947 } else {
1948 return Ok(count);
1949 }
1950 }
1951 }
1952
1953 fn inductive(&mut self, d: &Value) -> Result<()> {
1954 let types = array(&d["types"])?;
1955 let ctors = array(&d["ctors"])?;
1956 let recs = array(&d["recs"])?;
1957 let mut source_roots = Vec::new();
1958 for item in types.iter().chain(ctors).chain(recs) {
1959 source_roots.push(&item["type"]);
1960 }
1961 for rec in recs {
1962 for rule in array(&rec["rules"])? {
1963 source_roots.push(&rule["rhs"]);
1964 }
1965 }
1966 self.check_runtime_references(&source_roots)?;
1967
1968 if types.is_empty() {
1969 return Err(declined(
1972 DiagnosticCode::MalformedExport,
1973 "empty inductive block",
1974 ));
1975 }
1976 if types.len() > 1 {
1977 return self.ordinary_mutual_inductive(types, ctors, recs);
1978 }
1979 let header = &types[0];
1980 let name = self.name(&header["name"])?;
1981 let params = self.parameters(header)?;
1982 let nested_count = index(&header["numNested"])?;
1983 for item in types.iter().chain(ctors).chain(recs) {
1984 if item["isUnsafe"] != false {
1985 return Err(inductive_safety(
1986 &item["isUnsafe"],
1987 "unsafe or unspecified inductive safety",
1988 ));
1989 }
1990 }
1991 let mut names = HashSet::new();
1992 for item in types.iter().chain(ctors).chain(recs) {
1993 let n = self.name(&item["name"])?;
1994 if self.declarations.contains_key(&n) || !names.insert(n) {
1995 return Err(rejected(
1996 DiagnosticCode::InvalidProof,
1997 "duplicate declaration in inductive block",
1998 ));
1999 }
2000 }
2001 let all = array(&header["all"])?
2002 .iter()
2003 .map(|x| self.name(x))
2004 .collect::<Result<Vec<_>>>()?;
2005 if all != vec![name.clone()] {
2006 return Err(rejected(
2007 DiagnosticCode::InvalidProof,
2008 "inconsistent inductive block membership",
2009 ));
2010 }
2011 let nparams = index(&header["numParams"])?;
2012 let nindices = index(&header["numIndices"])?;
2013 let arity = self.declaration_term(&header["type"], ¶ms)?;
2014 lean_type(&self.ctx, &arity)?;
2015 let arity = declared_arity(&self.ctx, &arity)?;
2016 let mut result = &arity;
2017 while let Term::Pi { body_type, .. } = result {
2018 result = body_type;
2019 }
2020 if !matches!(result, Term::Sort(_))
2021 || nparams.checked_add(nindices) != Some(telescope_len(&arity))
2022 {
2023 return Err(rejected(
2024 DiagnosticCode::InvalidProof,
2025 "inductive arity or parameter/index count is invalid",
2026 ));
2027 }
2028 let declared_ctors = array(&header["ctors"])?
2029 .iter()
2030 .map(|x| self.name(x))
2031 .collect::<Result<Vec<_>>>()?;
2032 if declared_ctors.len() != ctors.len() {
2033 return Err(rejected(
2034 DiagnosticCode::InvalidProof,
2035 "constructor inventory mismatch",
2036 ));
2037 }
2038 let old_ctx = self.ctx.clone();
2039 if params.is_empty() {
2042 self.ctx
2043 .add_indexed_inductive(&name, arity.clone(), nparams);
2044 } else {
2045 self.ctx
2046 .add_universe_poly_inductive(&name, params.clone(), arity.clone(), nparams);
2047 }
2048 self.declarations.insert(name.clone(), params.len());
2049 let mut checked_ctors = Vec::new();
2050 for (i, c) in ctors.iter().enumerate() {
2051 let cname = self.name(&c["name"])?;
2052 if self.name(&c["induct"])? != name
2053 || index(&c["cidx"])? != i
2054 || cname != declared_ctors[i]
2055 || index(&c["numParams"])? != nparams
2056 {
2057 return Err(rejected(
2058 DiagnosticCode::InvalidProof,
2059 "constructor parent, order or parameter count mismatch",
2060 ));
2061 }
2062 if self.parameters(c)? != params {
2063 return Err(rejected(
2064 DiagnosticCode::InvalidProof,
2065 "constructor universe telescope mismatch",
2066 ));
2067 }
2068 let cty = self.declaration_term(&c["type"], ¶ms)?;
2069 lean_type(&self.ctx, &cty)?;
2070 if nparams.checked_add(index(&c["numFields"])?)
2071 != Some(resolved_telescope_len(&self.ctx, &cty)?)
2072 {
2073 return Err(rejected(
2074 DiagnosticCode::InvalidProof,
2075 "constructor field count mismatch",
2076 ));
2077 }
2078 checked_ctors.push((cname, cty));
2081 }
2082 let reserved = |n: &str| matches!(n, "Nat" | "Zero" | "Succ");
2086 if reserved(&name) || checked_ctors.iter().any(|(n, _)| reserved(n)) {
2087 if name != "Nat"
2088 || !params.is_empty()
2089 || nparams != 0
2090 || nindices != 0
2091 || checked_ctors.len() != 2
2092 || checked_ctors[0].0 != "Zero"
2093 || checked_ctors[1].0 != "Succ"
2094 {
2095 return Err(declined(
2096 DiagnosticCode::UnsupportedFeature,
2097 "noncanonical datatype uses primitive Nat identities",
2098 ));
2099 }
2100 let nt = Term::Global("Nat".into());
2101 let canonical = exact(&self.ctx, &arity, &Term::Sort(Universe::Type(0)))
2102 .and_then(|_| exact(&self.ctx, &checked_ctors[0].1, &nt))
2103 .and_then(|_| {
2104 exact(
2105 &self.ctx,
2106 &checked_ctors[1].1,
2107 &Term::Pi {
2108 param: "arena.nat.field".into(),
2109 param_type: Box::new(nt.clone()),
2110 body_type: Box::new(nt),
2111 },
2112 )
2113 });
2114 if canonical.is_err() {
2115 return Err(declined(
2116 DiagnosticCode::UnsupportedFeature,
2117 "noncanonical datatype uses primitive Nat identities",
2118 ));
2119 }
2120 }
2121 let block = crate::MutualInductive {
2122 name: name.clone(),
2123 sort: arity.clone(),
2124 num_params: nparams,
2125 constructors: checked_ctors.clone(),
2126 };
2127 let mut verified = old_ctx.clone();
2128 if params.is_empty() {
2129 verified
2130 .add_mutual_inductives(&[block])
2131 .map_err(inductive_verdict)?;
2132 } else {
2133 verified
2134 .add_universe_poly_inductives(¶ms, &[block])
2135 .map_err(inductive_verdict)?;
2136 }
2137 let nested_decl = if nested_count > 0 {
2138 let decl = crate::NestedDecl {
2141 name: name.clone(),
2142 sort: arity.clone(),
2143 constructors: checked_ctors.clone(),
2144 };
2145 crate::inductive_compile::eliminate_nested(
2147 &old_ctx,
2148 &crate::inductive_compile::NestedBlock {
2149 members: std::slice::from_ref(&decl),
2150 num_params: nparams,
2151 levels: ¶ms,
2152 },
2153 )
2154 .map_err(inductive_verdict)?;
2155 Some(decl)
2156 } else {
2157 None
2158 };
2159 self.ctx = verified;
2160 for (n, _) in &checked_ctors {
2161 self.declarations.insert(n.clone(), params.len());
2162 }
2163 if let Some(decl) = nested_decl {
2164 return self.native_nested_recursors(
2165 std::slice::from_ref(&decl),
2166 nparams,
2167 ¶ms,
2168 recs,
2169 &all,
2170 nested_count,
2171 );
2172 }
2173 if recs.len() != 1 {
2174 return Err(rejected(
2178 DiagnosticCode::InvalidProof,
2179 "noncanonical recursor inventory for a single non-nested inductive",
2180 ));
2181 }
2182 let rec = &recs[0];
2183 let rec_name = self.name(&rec["name"])?;
2184 if rec_name != format!("{name}/s:\"rec\"") {
2185 return Err(rejected(
2186 DiagnosticCode::InvalidProof,
2187 "recursor does not have its canonical generated name",
2188 ));
2189 }
2190 if index(&rec["numParams"])? != nparams
2191 || index(&rec["numIndices"])? != nindices
2192 || index(&rec["numMotives"])? != 1
2193 || index(&rec["numMinors"])? != ctors.len()
2194 {
2195 return Err(rejected(
2196 DiagnosticCode::InvalidProof,
2197 "recursor telescope counts mismatch",
2198 ));
2199 }
2200 if array(&rec["all"])?
2201 .iter()
2202 .map(|x| self.name(x))
2203 .collect::<Result<Vec<_>>>()?
2204 != all
2205 {
2206 return Err(rejected(
2207 DiagnosticCode::InvalidProof,
2208 "recursor block membership mismatch",
2209 ));
2210 }
2211 let expected_k = self.ctx.k_constructor(&name).is_some();
2212 if rec["k"].as_bool() != Some(expected_k) {
2213 return Err(rejected(
2214 DiagnosticCode::InvalidProof,
2215 "recursor K flag disagrees with its checked inductive",
2216 ));
2217 }
2218 let levels = self.parameters(rec)?;
2219 let (derived_levels, derived_ty, derived_body) =
2220 crate::derive_recursor_universe_poly(&self.ctx, &name).map_err(|e| {
2221 declined(
2222 DiagnosticCode::UnsupportedFeature,
2223 format!("cannot derive recursor: {e}"),
2224 )
2225 })?;
2226 if levels.len() != derived_levels.len() {
2227 return Err(rejected(
2228 DiagnosticCode::InvalidProof,
2229 "recursor universe telescope mismatch",
2230 ));
2231 }
2232 let subst = derived_levels
2233 .into_iter()
2234 .zip(levels.iter().cloned().map(Universe::Var))
2235 .collect();
2236 let derived_ty = crate::instantiate_universes(&derived_ty, &subst);
2237 let derived_body = crate::instantiate_universes(&derived_body, &subst);
2238 let mut computation = &derived_body;
2239 while let Term::Lambda { body, .. } = computation {
2240 computation = body;
2241 }
2242 let nonrecursive = !matches!(computation, Term::Fix { .. } | Term::MutualFix { .. });
2243 let projections = if nindices == 0 && nonrecursive && checked_ctors.len() == 1 {
2249 self.structure_view(&name, ¶ms, &checked_ctors[0].1)?
2250 } else {
2251 None
2252 };
2253 let derived_body = match &projections {
2254 Some(projections) => structure_recursor_body(
2255 &self.ctx,
2256 &derived_ty,
2257 nparams,
2258 nparams + 1,
2259 nparams + 3,
2260 projections,
2261 )?,
2262 None => derived_body,
2263 };
2264
2265 let exported_ty = self.declaration_term(&rec["type"], &levels)?;
2266 lean_type(&self.ctx, &exported_ty)?;
2267 exact(&self.ctx, &exported_ty, &derived_ty)?;
2268 if levels.is_empty() {
2273 self.ctx
2274 .declare(&rec_name, exported_ty.clone(), derived_body)
2275 .map_err(kernel)?;
2276 } else {
2277 self.ctx
2278 .declare_universe_poly(&rec_name, levels.clone(), exported_ty.clone(), derived_body)
2279 .map_err(kernel)?;
2280 }
2281 self.declarations.insert(rec_name.clone(), levels.len());
2282 let rules = array(&rec["rules"])?;
2283 if rules.len() != ctors.len() {
2284 return Err(rejected(
2285 DiagnosticCode::InvalidProof,
2286 "missing or additional recursor computation rule",
2287 ));
2288 }
2289 let mut seen = HashSet::new();
2290 for rule in rules {
2291 let cname = self.name(&rule["ctor"])?;
2292 let Some(i) = declared_ctors.iter().position(|n| n == &cname) else {
2293 return Err(rejected(
2294 DiagnosticCode::InvalidProof,
2295 "recursor rule names a foreign constructor",
2296 ));
2297 };
2298 if !seen.insert(cname.clone()) || rule["nfields"] != ctors[i]["numFields"] {
2299 return Err(rejected(
2300 DiagnosticCode::InvalidProof,
2301 "duplicate rule or wrong field count",
2302 ));
2303 }
2304 let expected = ctor_rule(
2305 &self.ctx,
2306 &rec_name,
2307 &levels,
2308 &derived_ty,
2309 &cname,
2310 &checked_ctors[i].1,
2311 nparams,
2312 nparams + 1 + ctors.len(),
2313 )?;
2314 let actual = self.declaration_term(&rule["rhs"], &levels)?;
2315 let actual_ty = lean_type(&self.ctx, &actual)?;
2316 let expected_ty = infer_type(&self.ctx, &expected).map_err(kernel)?;
2317 exact(&self.ctx, &actual_ty, &expected_ty)?;
2318 exact(&self.ctx, &actual, &expected)?;
2319 }
2320 Ok(())
2321 }
2322
2323 fn ordinary_mutual_inductive(
2324 &mut self,
2325 types: &[Value],
2326 ctors: &[Value],
2327 recs: &[Value],
2328 ) -> Result<()> {
2329 use crate::MutualInductive;
2330 struct Member {
2331 declaration: MutualInductive,
2332 indices: usize,
2333 constructors: Vec<String>,
2334 }
2335 let mut names = HashSet::new();
2336 for item in types.iter().chain(ctors).chain(recs) {
2337 if item["isUnsafe"] != false {
2338 return Err(inductive_safety(
2339 &item["isUnsafe"],
2340 "unsafe or unspecified mutual inductive safety",
2341 ));
2342 }
2343 let name = self.name(&item["name"])?;
2344 if self.declarations.contains_key(&name) || !names.insert(name.clone()) {
2345 return Err(rejected(
2346 DiagnosticCode::InvalidProof,
2347 "duplicate declaration in mutual block",
2348 ));
2349 }
2350 if matches!(name.as_str(), "Nat" | "Zero" | "Succ") || name.starts_with("kernel.quot.")
2351 {
2352 return Err(rejected(
2353 DiagnosticCode::InvalidProof,
2354 "primitive identities require their canonical declaration",
2355 ));
2356 }
2357 }
2358 let all = types
2359 .iter()
2360 .map(|h| self.name(&h["name"]))
2361 .collect::<Result<Vec<_>>>()?;
2362 let params = self.parameters(&types[0])?;
2363 let nparams = index(&types[0]["numParams"])?;
2364 let mut members = Vec::new();
2365 let mut shared_domains: Vec<Term> = Vec::new();
2366 let mut common_sort: Option<Universe> = None;
2367 let nested_count = index(&types[0]["numNested"])?;
2368 for (i, header) in types.iter().enumerate() {
2369 if index(&header["numNested"])? != nested_count {
2371 return Err(rejected(
2372 DiagnosticCode::InvalidProof,
2373 "mutual members disagree on the block's nested count",
2374 ));
2375 }
2376 if self.parameters(header)? != params || index(&header["numParams"])? != nparams {
2377 return Err(rejected(
2378 DiagnosticCode::InvalidProof,
2379 "mutual headers disagree on their shared parameters",
2380 ));
2381 }
2382 if array(&header["all"])?
2383 .iter()
2384 .map(|n| self.name(n))
2385 .collect::<Result<Vec<_>>>()?
2386 != all
2387 {
2388 return Err(rejected(
2389 DiagnosticCode::InvalidProof,
2390 "mutual header block membership mismatch",
2391 ));
2392 }
2393 let arity = self.declaration_term(&header["type"], ¶ms)?;
2394 lean_type(&self.ctx, &arity)?;
2395 let arity = declared_arity(&self.ctx, &arity)?;
2396 let indices = index(&header["numIndices"])?;
2397 if nparams.checked_add(indices) != Some(telescope_len(&arity)) {
2398 return Err(rejected(
2399 DiagnosticCode::InvalidProof,
2400 "mutual header parameter/index count mismatch",
2401 ));
2402 }
2403 let mut telescope = arity.clone();
2404 let mut local = self.ctx.clone();
2405 for j in 0..nparams {
2406 let name = format!("arena.mutual.parameter.{j}");
2407 let (domain, body) = open_binder(telescope, &name)?;
2408 if i == 0 {
2409 shared_domains.push(domain.clone());
2410 } else {
2411 exact(&local, &domain, &shared_domains[j])?;
2412 }
2413 local = local.extend(&name, domain);
2414 telescope = body;
2415 }
2416 while let Term::Pi { body_type, .. } = telescope {
2417 telescope = *body_type;
2418 }
2419 let Term::Sort(level) = telescope else {
2420 return Err(rejected(
2421 DiagnosticCode::InvalidProof,
2422 "mutual header does not return a sort",
2423 ));
2424 };
2425 if let Some(common) = &common_sort {
2426 if !level.equiv(common) {
2427 return Err(rejected(
2428 DiagnosticCode::InvalidProof,
2429 "mutual members have different result universes",
2430 ));
2431 }
2432 } else {
2433 common_sort = Some(level);
2434 }
2435 let constructors = array(&header["ctors"])?
2436 .iter()
2437 .map(|n| self.name(n))
2438 .collect::<Result<Vec<_>>>()?;
2439 members.push(Member {
2440 declaration: MutualInductive {
2441 name: all[i].clone(),
2442 sort: arity,
2443 num_params: nparams,
2444 constructors: Vec::new(),
2445 },
2446 indices,
2447 constructors,
2448 });
2449 }
2450 let old_ctx = self.ctx.clone();
2451 for member in &members {
2452 let declaration = &member.declaration;
2453 if params.is_empty() {
2454 self.ctx.add_indexed_inductive(
2455 &declaration.name,
2456 declaration.sort.clone(),
2457 nparams,
2458 );
2459 } else {
2460 self.ctx.add_universe_poly_inductive(
2461 &declaration.name,
2462 params.clone(),
2463 declaration.sort.clone(),
2464 nparams,
2465 );
2466 }
2467 self.declarations
2468 .insert(declaration.name.clone(), params.len());
2469 }
2470 let mut used = HashSet::new();
2471 for member in &mut members {
2472 for (i, name) in member.constructors.iter().enumerate() {
2473 let ctor = ctors
2474 .iter()
2475 .find(|c| self.name(&c["name"]).ok().as_ref() == Some(name))
2476 .ok_or_else(|| {
2477 rejected(
2478 DiagnosticCode::InvalidProof,
2479 "missing declared mutual constructor",
2480 )
2481 })?;
2482 if !used.insert(name.clone())
2483 || self.name(&ctor["induct"])? != member.declaration.name
2484 || index(&ctor["cidx"])? != i
2485 || index(&ctor["numParams"])? != nparams
2486 {
2487 return Err(rejected(
2488 DiagnosticCode::InvalidProof,
2489 "mutual constructor parent/order/parameter mismatch",
2490 ));
2491 }
2492 if self.parameters(ctor)? != params {
2493 return Err(rejected(
2494 DiagnosticCode::InvalidProof,
2495 "mutual constructor universe telescope mismatch",
2496 ));
2497 }
2498 let ty = self.declaration_term(&ctor["type"], ¶ms)?;
2499 lean_type(&self.ctx, &ty)?;
2500 if nparams.checked_add(index(&ctor["numFields"])?)
2501 != Some(resolved_telescope_len(&self.ctx, &ty)?)
2502 {
2503 return Err(rejected(
2504 DiagnosticCode::InvalidProof,
2505 "mutual constructor field count mismatch",
2506 ));
2507 }
2508 member.declaration.constructors.push((name.clone(), ty));
2509 }
2510 }
2511 if used.len() != ctors.len() {
2512 return Err(rejected(
2513 DiagnosticCode::InvalidProof,
2514 "undeclared constructor in mutual block",
2515 ));
2516 }
2517 let block = members
2518 .iter()
2519 .map(|m| m.declaration.clone())
2520 .collect::<Vec<_>>();
2521 self.ctx = old_ctx;
2522 if params.is_empty() {
2523 self.ctx
2524 .add_mutual_inductives(&block)
2525 .map_err(inductive_verdict)?;
2526 } else {
2527 self.ctx
2528 .add_universe_poly_inductives(¶ms, &block)
2529 .map_err(inductive_verdict)?;
2530 }
2531 for member in &members {
2532 for name in &member.constructors {
2533 self.declarations.insert(name.clone(), params.len());
2534 }
2535 }
2536 if nested_count > 0 {
2537 let decls: Vec<crate::NestedDecl> = members
2538 .iter()
2539 .map(|m| crate::NestedDecl {
2540 name: m.declaration.name.clone(),
2541 sort: m.declaration.sort.clone(),
2542 constructors: m.declaration.constructors.clone(),
2543 })
2544 .collect();
2545 return self.native_nested_recursors(
2546 &decls,
2547 nparams,
2548 ¶ms,
2549 recs,
2550 &all,
2551 nested_count,
2552 );
2553 }
2554 if recs.len() != members.len() {
2555 return Err(rejected(
2556 DiagnosticCode::InvalidProof,
2557 "mutual recursor inventory mismatch",
2558 ));
2559 }
2560 let mut structures = Vec::with_capacity(members.len());
2566 for member in &members {
2567 structures.push(match &member.declaration.constructors[..] {
2568 [(_, constructor)] if member.indices == 0 => {
2569 self.structure_view(&member.declaration.name, ¶ms, constructor)?
2570 }
2571 _ => None,
2572 });
2573 }
2574 let large = Universe::Type(0).is_subtype_of(common_sort.as_ref().expect("nonempty block"));
2578 let motive_offset = usize::from(large);
2579 let prefix = nparams
2580 .checked_add(members.len())
2581 .and_then(|v| v.checked_add(ctors.len()))
2582 .ok_or_else(|| {
2583 declined(
2584 DiagnosticCode::ResourceLimit,
2585 "mutual recursor telescope overflow",
2586 )
2587 })?;
2588 let mut checked = Vec::new();
2589 let mut minors_before = 0;
2590 for (member, projections) in members.iter().zip(&structures) {
2591 let minor = prefix - ctors.len() + minors_before;
2592 minors_before += member.constructors.len();
2593 let name = format!("{}/s:\"rec\"", member.declaration.name);
2594 let position = recs
2595 .iter()
2596 .position(|r| self.name(&r["name"]).ok().as_ref() == Some(&name))
2597 .ok_or_else(|| {
2598 rejected(
2599 DiagnosticCode::InvalidProof,
2600 "missing canonical mutual recursor",
2601 )
2602 })?;
2603 let rec = &recs[position];
2604 if index(&rec["numParams"])? != nparams
2605 || index(&rec["numIndices"])? != member.indices
2606 || index(&rec["numMotives"])? != members.len()
2607 || index(&rec["numMinors"])? != ctors.len()
2608 {
2609 return Err(rejected(
2610 DiagnosticCode::InvalidProof,
2611 "mutual recursor telescope counts mismatch",
2612 ));
2613 }
2614 if array(&rec["all"])?
2615 .iter()
2616 .map(|n| self.name(n))
2617 .collect::<Result<Vec<_>>>()?
2618 != all
2619 {
2620 return Err(rejected(
2621 DiagnosticCode::InvalidProof,
2622 "mutual recursor block membership mismatch",
2623 ));
2624 }
2625 if rec["k"].as_bool() != Some(false) {
2626 return Err(rejected(
2627 DiagnosticCode::InvalidProof,
2628 "multi-member recursor cannot have K flag",
2629 ));
2630 }
2631 let levels = self.parameters(rec)?;
2632 if levels.len() != params.len() + motive_offset {
2633 return Err(rejected(
2634 DiagnosticCode::InvalidProof,
2635 "mutual recursor universe telescope mismatch",
2636 ));
2637 }
2638 let (derived_levels, ty, body) =
2639 crate::derive_recursor_universe_poly(&self.ctx, &member.declaration.name).map_err(
2640 |e| {
2641 declined(
2642 DiagnosticCode::UnsupportedFeature,
2643 format!("cannot derive mutual recursor: {e}"),
2644 )
2645 },
2646 )?;
2647 let motive_count = derived_levels
2648 .len()
2649 .checked_sub(params.len())
2650 .ok_or_else(|| {
2651 declined(
2652 DiagnosticCode::UnsupportedFeature,
2653 "derived mutual universe inventory mismatch",
2654 )
2655 })?;
2656 if derived_levels[motive_count..] != params {
2657 return Err(declined(
2658 DiagnosticCode::UnsupportedFeature,
2659 "derived mutual inductive universe telescope mismatch",
2660 ));
2661 }
2662 let motive = if large {
2663 Universe::Var(levels[0].clone())
2664 } else {
2665 Universe::Prop
2666 };
2667 let mut substitution: HashMap<String, Universe> = derived_levels[..motive_count]
2668 .iter()
2669 .cloned()
2670 .map(|name| (name, motive.clone()))
2671 .collect();
2672 let parameter_substitution: HashMap<String, Universe> = params
2673 .iter()
2674 .cloned()
2675 .zip(levels[motive_offset..].iter().cloned().map(Universe::Var))
2676 .collect();
2677 substitution.extend(parameter_substitution.clone());
2678 let ty = crate::instantiate_universes(&ty, &substitution);
2679 let body = match projections {
2680 Some(projections) => structure_recursor_body(
2681 &self.ctx,
2682 &ty,
2683 nparams,
2684 minor,
2685 prefix + 1,
2686 projections,
2687 )?,
2688 None => crate::instantiate_universes(&body, &substitution),
2689 };
2690 let exported = self.declaration_term(&rec["type"], &levels)?;
2691 lean_type(&self.ctx, &exported)?;
2692 exact(&self.ctx, &exported, &ty)?;
2693 if levels.is_empty() {
2694 self.ctx.declare(&name, exported, body).map_err(kernel)?;
2695 } else {
2696 self.ctx
2697 .declare_universe_poly(&name, levels.clone(), exported, body)
2698 .map_err(kernel)?;
2699 }
2700 self.declarations.insert(name.clone(), levels.len());
2701 checked.push((name, levels, ty, position, parameter_substitution));
2702 }
2703 for (member, (name, levels, ty, position, substitution)) in members.iter().zip(&checked) {
2705 let rules = array(&recs[*position]["rules"])?;
2706 if rules.len() != member.constructors.len() {
2707 return Err(rejected(
2708 DiagnosticCode::InvalidProof,
2709 "mutual computation rule inventory mismatch",
2710 ));
2711 }
2712 let mut seen = HashSet::new();
2713 for rule in rules {
2714 let constructor = self.name(&rule["ctor"])?;
2715 let Some((_, ctor_ty)) = member
2716 .declaration
2717 .constructors
2718 .iter()
2719 .find(|(n, _)| n == &constructor)
2720 else {
2721 return Err(rejected(
2722 DiagnosticCode::InvalidProof,
2723 "mutual rule names a foreign constructor",
2724 ));
2725 };
2726 let field_count = resolved_telescope_len(&self.ctx, ctor_ty)?
2727 .checked_sub(nparams)
2728 .ok_or_else(|| {
2729 rejected(
2730 DiagnosticCode::InvalidProof,
2731 "mutual constructor lacks shared parameters",
2732 )
2733 })?;
2734 if !seen.insert(constructor.clone()) || index(&rule["nfields"])? != field_count {
2735 return Err(rejected(
2736 DiagnosticCode::InvalidProof,
2737 "mutual rule duplicate constructor or wrong field count",
2738 ));
2739 }
2740 let value = if params.is_empty() {
2741 Term::Global(constructor)
2742 } else {
2743 Term::Const {
2744 name: constructor,
2745 levels: params.iter().map(|p| substitution[p].clone()).collect(),
2746 }
2747 };
2748 let ctor_ty = crate::instantiate_universes(ctor_ty, substitution);
2749 let expected = ctor_rule_applied(
2750 &self.ctx, name, levels, ty, value, ctor_ty, nparams, prefix, nparams,
2751 )?;
2752 let actual = self.declaration_term(&rule["rhs"], levels)?;
2753 let actual_type = lean_type(&self.ctx, &actual)?;
2754 let expected_type = infer_type(&self.ctx, &expected).map_err(kernel)?;
2755 exact(&self.ctx, &actual_type, &expected_type)?;
2756 exact(&self.ctx, &actual, &expected)?;
2757 }
2758 }
2759 Ok(())
2760 }
2761
2762 fn declaration_audit(
2763 &self,
2764 name: &str,
2765 kind: &str,
2766 source: &Value,
2767 ty: &Term,
2768 body: Option<&Term>,
2769 ) -> Result<DeclarationAudit> {
2770 self.trace_stage("audit", "begin");
2771 let main_type = infer_type(&self.ctx, ty).map_err(kernel)?;
2772 let independent_type = crate::recheck(&self.ctx, ty).map_err(|e| {
2773 independent_verdict(&e, format!("independent declaration-type check for {name}"))
2774 })?;
2775 independent_comparison(
2776 exact(&self.ctx, &main_type, &independent_type),
2777 format!("independent declaration-type disagreement for {name}"),
2778 )?;
2779 let known = |dependency: &str| {
2784 self.audited_names
2785 .get(dependency)
2786 .map(|i| self.report.declarations[*i].axioms.clone())
2787 };
2788 let mut usage = if kind == "axiom" {
2789 crate::prelude::AxiomUsage::default()
2790 } else {
2791 crate::prelude::axiom_dependencies_given(&self.ctx, ty, &known)
2792 };
2793 if let Some(body) = body {
2794 let independent = crate::recheck(&self.ctx, body).map_err(|e| {
2795 independent_verdict(&e, format!("independent declaration-body check for {name}"))
2796 })?;
2797 independent_comparison(
2798 exact(&self.ctx, &independent, ty),
2799 format!("independent declaration-body type disagreement for {name}"),
2800 )?;
2801 usage.absorb(crate::prelude::axiom_dependencies_given(&self.ctx, body, &known));
2802 }
2803 if !usage.unresolved.is_empty() {
2804 return Err(declined(
2805 DiagnosticCode::AuditIncomplete,
2806 format!(
2807 "unresolved declaration audit dependencies for {name}: {:?}",
2808 usage.unresolved
2809 ),
2810 ));
2811 }
2812 let mut axioms = std::collections::BTreeSet::new();
2813 for dependency in usage.axioms {
2814 if let Some(i) = self.audited_names.get(&dependency) {
2817 axioms.extend(self.report.declarations[*i].axioms.iter().cloned());
2818 } else {
2819 axioms.insert(dependency);
2820 }
2821 }
2822 if kind == "axiom" {
2823 axioms.insert(name.to_string());
2824 }
2825 self.trace_stage("audit", "done");
2826 Ok(DeclarationAudit {
2827 name: name.into(),
2828 kind: kind.into(),
2829 type_expression: index(&source["type"])?,
2830 value_expression: body.map(|_| index(&source["value"])).transpose()?,
2831 axioms: axioms.into_iter().collect(),
2832 })
2833 }
2834
2835 fn native_nested_recursors(
2836 &mut self,
2837 members: &[crate::NestedDecl],
2838 nparams: usize,
2839 family_levels: &[String],
2840 recs: &[Value],
2841 all: &[String],
2842 nested_count: usize,
2843 ) -> Result<()> {
2844 fn compare(ctx: &Context, actual: &Term, expected: &Term, stage: &str) -> Result<()> {
2845 exact(ctx, actual, expected).map_err(|error| error.located(stage))
2846 }
2847 let native = crate::inductive_compile::derive_nested_block_recursors(
2848 &self.ctx,
2849 &crate::inductive_compile::NestedBlock {
2850 members,
2851 num_params: nparams,
2852 levels: family_levels,
2853 },
2854 )
2855 .map_err(inductive_verdict)?;
2856 let motives = native.len();
2857 if nested_count.checked_add(members.len()) != Some(motives) || recs.len() != motives {
2858 return Err(rejected(
2859 DiagnosticCode::InvalidProof,
2860 "native nested recursor inventory mismatch",
2861 ));
2862 }
2863 let parameters = native
2866 .first()
2867 .map(|rec| rec.parameters.clone())
2868 .unwrap_or_default();
2869 if parameters.len() != nparams {
2870 return Err(declined(
2871 DiagnosticCode::UnsupportedFeature,
2872 "native nested reconstruction lost the uniform parameter telescope",
2873 ));
2874 }
2875 let mut local = self.ctx.clone();
2876 for (parameter, domain) in ¶meters {
2877 local = local.extend(parameter, domain.clone());
2878 }
2879 let family_sort = |carrier: &Term| -> Result<Universe> {
2881 let mut ty = normal(&local, &infer_type(&local, carrier).map_err(kernel)?)?;
2882 while let Term::Pi { body_type, .. } = ty {
2883 ty = *body_type;
2884 }
2885 match ty {
2886 Term::Sort(level) => Ok(level),
2887 _ => Err(rejected(
2888 DiagnosticCode::InvalidProof,
2889 "a nested carrier is not a family of types",
2890 )),
2891 }
2892 };
2893 let block_sort = family_sort(&native[0].carrier)?;
2897 let mut constructor_groups = Vec::new();
2898 let mut indices = Vec::new();
2899 let mut minors = 0usize;
2900 for rec in &native {
2901 if !family_sort(&rec.carrier)?.equiv(&block_sort) {
2902 return Err(declined(
2903 DiagnosticCode::UnsupportedFeature,
2904 "native nested container outside the family's universe",
2905 ));
2906 }
2907 let mut head = &rec.carrier;
2908 let mut arguments = Vec::new();
2909 while let Term::App(f, a) = head {
2910 arguments.push((**a).clone());
2911 head = f;
2912 }
2913 arguments.reverse();
2914 let (parent, levels) = match head {
2915 Term::Global(name) => (name, Vec::new()),
2916 Term::Const { name, levels } => (name, levels.clone()),
2917 _ => {
2918 return Err(declined(
2919 DiagnosticCode::UnsupportedFeature,
2920 "native nested carrier has no inductive head",
2921 ))
2922 }
2923 };
2924 if self.ctx.inductive_num_params(parent) != arguments.len() {
2925 return Err(declined(
2926 DiagnosticCode::UnsupportedFeature,
2927 "native nested container parameters are not fully fixed",
2928 ));
2929 }
2930 indices.push(self.ctx.inductive_arity(parent) - arguments.len());
2931 let mut constructors = Vec::new();
2932 for (name, _) in self.ctx.get_constructors(parent) {
2933 let mut value = match head {
2934 Term::Const { .. } => Term::Const {
2935 name: name.into(),
2936 levels: levels.clone(),
2937 },
2938 _ => Term::Global(name.into()),
2939 };
2940 let mut ty = infer_type(&self.ctx, &value).map_err(kernel)?;
2941 for argument in &arguments {
2942 let Term::Pi {
2943 param, body_type, ..
2944 } = ty
2945 else {
2946 return Err(declined(
2947 DiagnosticCode::UnsupportedFeature,
2948 "native constructor parameter telescope unavailable",
2949 ));
2950 };
2951 ty = crate::substitute(&body_type, ¶m, argument);
2952 value = apply(value, argument.clone());
2953 }
2954 let fields = resolved_telescope_len(&local, &ty)?;
2955 constructors.push((name.to_string(), value, ty, arguments.len(), fields));
2956 }
2957 minors = minors.checked_add(constructors.len()).ok_or_else(|| {
2958 declined(
2959 DiagnosticCode::ResourceLimit,
2960 "native minor inventory overflow",
2961 )
2962 })?;
2963 constructor_groups.push(constructors);
2964 }
2965 let prefix = nparams
2966 .checked_add(motives)
2967 .and_then(|n| n.checked_add(minors))
2968 .ok_or_else(|| {
2969 declined(
2970 DiagnosticCode::ResourceLimit,
2971 "native recursor prefix overflow",
2972 )
2973 })?;
2974 let mut checked = Vec::new();
2975 for (i, rec) in native.into_iter().enumerate() {
2978 let (owner, suffix) = if i < members.len() {
2981 (&members[i].name, "rec".to_string())
2982 } else {
2983 (&members[0].name, format!("rec_{}", i + 1 - members.len()))
2984 };
2985 let name = format!("{}/s:{}", owner, serde_json::to_string(&suffix).unwrap());
2986 let position = recs
2987 .iter()
2988 .position(|r| self.name(&r["name"]).ok().as_deref() == Some(name.as_str()))
2989 .ok_or_else(|| {
2990 rejected(
2991 DiagnosticCode::InvalidProof,
2992 "missing canonical native nested recursor",
2993 )
2994 })?;
2995 let exported = &recs[position];
2996 if index(&exported["numParams"])? != nparams
2997 || index(&exported["numIndices"])? != indices[i]
2998 || index(&exported["numMotives"])? != motives
2999 || index(&exported["numMinors"])? != minors
3000 {
3001 return Err(rejected(
3002 DiagnosticCode::InvalidProof,
3003 "native nested recursor telescope counts mismatch",
3004 ));
3005 }
3006 if array(&exported["all"])?
3007 .iter()
3008 .map(|n| self.name(n))
3009 .collect::<Result<Vec<_>>>()?
3010 != all
3011 {
3012 return Err(rejected(
3013 DiagnosticCode::InvalidProof,
3014 "native nested recursor block membership mismatch",
3015 ));
3016 }
3017 if exported["k"].as_bool() != Some(false) {
3018 return Err(rejected(
3019 DiagnosticCode::InvalidProof,
3020 "a nested recursor has an invalid K flag",
3021 ));
3022 }
3023 let large = Universe::Type(0).is_subtype_of(&block_sort);
3027 let levels = self.parameters(exported)?;
3028 if levels.len() != usize::from(large) + family_levels.len() {
3029 return Err(rejected(
3030 DiagnosticCode::InvalidProof,
3031 "native nested recursor universe telescope mismatch",
3032 ));
3033 }
3034 let derived_motives = rec
3035 .levels
3036 .len()
3037 .checked_sub(family_levels.len())
3038 .filter(|&m| rec.levels[m..] == *family_levels)
3039 .ok_or_else(|| {
3040 declined(
3041 DiagnosticCode::UnsupportedFeature,
3042 "native reconstruction has unexpected motive universe parameters",
3043 )
3044 })?;
3045 let offset = usize::from(large);
3049 let substitution = rec
3050 .levels
3051 .iter()
3052 .enumerate()
3053 .map(|(i, p)| {
3054 let lean = if i < derived_motives {
3055 if large {
3056 Universe::Var(levels[0].clone())
3057 } else {
3058 Universe::Prop
3059 }
3060 } else {
3061 Universe::Var(levels[offset + (i - derived_motives)].clone())
3062 };
3063 (p.clone(), lean)
3064 })
3065 .collect();
3066 let ty = crate::instantiate_universes(&rec.ty, &substitution);
3067 let body = crate::instantiate_universes(&rec.body, &substitution);
3068 let expected = self.declaration_term(&exported["type"], &levels)?;
3069 lean_type(&self.ctx, &expected)?;
3070 compare(
3071 &self.ctx,
3072 &expected,
3073 &ty,
3074 &format!("native recursor {name} type"),
3075 )?;
3076 if levels.is_empty() {
3077 self.ctx.declare(&name, expected, body).map_err(kernel)?;
3078 } else {
3079 self.ctx
3080 .declare_universe_poly(&name, levels.clone(), expected, body)
3081 .map_err(kernel)?;
3082 }
3083 self.declarations.insert(name.clone(), levels.len());
3084 checked.push((name, levels, ty, position));
3085 }
3086 let rule_parameters: Vec<(String, Term)> = parameters
3089 .iter()
3090 .enumerate()
3091 .map(|(i, (parameter, _))| (parameter.clone(), Term::Var(format!("arena.rule.{i}"))))
3092 .collect();
3093 let offset = usize::from(Universe::Type(0).is_subtype_of(&block_sort));
3094 let at_rule = |term: &Term, levels: &[String]| {
3095 let universes = family_levels
3096 .iter()
3097 .cloned()
3098 .zip(levels[offset..].iter().cloned().map(Universe::Var))
3099 .collect();
3100 rule_parameters.iter().fold(
3101 crate::instantiate_universes(term, &universes),
3102 |term, (parameter, binder)| crate::substitute(&term, parameter, binder),
3103 )
3104 };
3105 for ((name, levels, ty, position), constructors) in checked.iter().zip(&constructor_groups)
3106 {
3107 let rules = array(&recs[*position]["rules"])?;
3108 if rules.len() != constructors.len() {
3109 return Err(rejected(
3110 DiagnosticCode::InvalidProof,
3111 "native nested recursor computation rule inventory mismatch",
3112 ));
3113 }
3114 let mut seen = HashSet::new();
3115 for rule in rules {
3116 let constructor = self.name(&rule["ctor"])?;
3117 let Some((_, value, ctor_ty, result_parameters, fields)) =
3118 constructors.iter().find(|(n, ..)| n == &constructor)
3119 else {
3120 return Err(rejected(
3121 DiagnosticCode::InvalidProof,
3122 "native nested rule names a foreign constructor",
3123 ));
3124 };
3125 if !seen.insert(constructor) || index(&rule["nfields"])? != *fields {
3126 return Err(rejected(
3127 DiagnosticCode::InvalidProof,
3128 "native nested rule has duplicate constructor or wrong field count",
3129 ));
3130 }
3131 let expected = ctor_rule_applied(
3132 &self.ctx,
3133 name,
3134 levels,
3135 ty,
3136 at_rule(value, levels),
3137 at_rule(ctor_ty, levels),
3138 0,
3139 prefix,
3140 *result_parameters,
3141 )?;
3142 let actual = self.declaration_term(&rule["rhs"], levels)?;
3143 let actual_ty = lean_type(&self.ctx, &actual)?;
3144 let expected_ty = infer_type(&self.ctx, &expected).map_err(kernel)?;
3145 compare(
3146 &self.ctx,
3147 &actual_ty,
3148 &expected_ty,
3149 &format!("native recursor {name} rule type"),
3150 )?;
3151 compare(
3152 &self.ctx,
3153 &actual,
3154 &expected,
3155 &format!("native recursor {name} rule value"),
3156 )?;
3157 }
3158 }
3159 Ok(())
3160 }
3161
3162 fn trace_stage(&self, stage: &str, event: &str) {
3165 emit_stage(
3166 self.trace_sink,
3167 self.trace_declaration.as_ref(),
3168 stage,
3169 event,
3170 );
3171 }
3172
3173 fn record(&mut self, r: Value, line: usize) -> Result<()> {
3174 if self.trace_sink.is_none() {
3175 return self.record_inner(r);
3176 }
3177 let declaration = r
3178 .as_object()
3179 .filter(|object| object.len() == 1)
3180 .and_then(|object| object.iter().next())
3181 .filter(|(kind, _)| {
3182 matches!(
3183 kind.as_str(),
3184 "def" | "axiom" | "thm" | "opaque" | "inductive" | "quot"
3185 )
3186 });
3187 if let Some((kind, source)) = declaration {
3188 let name_source = if kind == "inductive" {
3189 &source["types"][0]["name"]
3190 } else {
3191 &source["name"]
3192 };
3193 let name = self
3194 .name(name_source)
3195 .unwrap_or_else(|_| "<unresolved>".into());
3196 self.trace_declaration = Some((line, name, kind.clone(), name_source.as_u64()));
3197 self.trace_stage("declaration", "begin");
3198 }
3199 let result = self.record_inner(r);
3200 self.trace_stage("declaration", if result.is_ok() { "done" } else { "error" });
3201 self.trace_declaration = None;
3202 result
3203 }
3204
3205 fn record_inner(&mut self, r: Value) -> Result<()> {
3206 let obj = r
3207 .as_object()
3208 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "record is not an object"))?;
3209 if let Some(i) = r.get("in") {
3210 if obj.len() != 2 {
3211 return Err(declined(
3212 DiagnosticCode::MalformedExport,
3213 "unknown name record fields",
3214 ));
3215 }
3216 let i = index(i)?;
3217 if self.names.contains_key(&i) {
3218 return Err(declined(
3219 DiagnosticCode::MalformedExport,
3220 "duplicate name index",
3221 ));
3222 }
3223 let (kind, x) = if let Some(x) = r.get("str") {
3224 ("s", x)
3225 } else if let Some(x) = r.get("num") {
3226 ("n", x)
3227 } else {
3228 return Err(declined(
3229 DiagnosticCode::MalformedExport,
3230 "unknown name kind",
3231 ));
3232 };
3233 let pre = self
3234 .names
3235 .get(&index(&x["pre"])?)
3236 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "unknown name prefix"))?;
3237 let part =
3238 if kind == "s" {
3239 serde_json::to_string(x["str"].as_str().ok_or_else(|| {
3240 declined(DiagnosticCode::MalformedExport, "bad name string")
3241 })?)
3242 .unwrap()
3243 } else {
3244 index(&x["i"])?.to_string()
3245 };
3246 self.names.insert(i, format!("{pre}/{kind}:{part}"));
3247 return Ok(());
3248 }
3249 if let Some(i) = r.get("il") {
3250 if obj.len() != 2 {
3251 return Err(declined(
3252 DiagnosticCode::MalformedExport,
3253 "unknown level record fields",
3254 ));
3255 }
3256 let i = index(i)?;
3257 if self.levels.contains_key(&i) {
3258 return Err(declined(
3259 DiagnosticCode::MalformedExport,
3260 "duplicate level index",
3261 ));
3262 }
3263 let mut vars = HashSet::new();
3264 let mut depth = 0;
3265 let mut get = |v: &Value| -> Result<Universe> {
3266 let j = index(v)?;
3267 let u = self.level(v, None)?;
3268 vars.extend(self.level_vars[&j].iter().cloned());
3269 depth = depth.max(self.level_depth[&j] + 1);
3270 if depth > 256 {
3271 return Err(declined(
3272 DiagnosticCode::ResourceLimit,
3273 "universe depth budget",
3274 ));
3275 }
3276 Ok(u)
3277 };
3278 let u = if let Some(v) = r.get("succ") {
3279 get(v)?.succ()
3280 } else if let Some(v) = r.get("param") {
3281 let n = self.name(v)?;
3282 vars.insert(n.clone());
3283 Universe::Var(n)
3284 } else if let Some(v) = r.get("max").or_else(|| r.get("imax")) {
3285 let pair = array(v)?;
3286 if pair.len() != 2 {
3287 return Err(declined(DiagnosticCode::MalformedExport, "bad level pair"));
3288 }
3289 let a = Box::new(get(&pair[0])?);
3290 let b = Box::new(get(&pair[1])?);
3291 if r.get("max").is_some() {
3292 Universe::Max(a, b)
3293 } else {
3294 Universe::IMax(a, b)
3295 }
3296 } else {
3297 return Err(declined(
3298 DiagnosticCode::MalformedExport,
3299 "unknown level kind",
3300 ));
3301 };
3302 self.levels.insert(i, u);
3303 self.level_vars.insert(i, vars);
3304 self.level_depth.insert(i, depth);
3305 return Ok(());
3306 }
3307 if let Some(i) = r.get("ie") {
3308 if obj.len() != 2 {
3309 return Err(declined(
3310 DiagnosticCode::MalformedExport,
3311 "unknown expression record fields",
3312 ));
3313 }
3314 let i = index(i)?;
3315 if self.exprs.contains_key(&i) {
3316 return Err(declined(
3317 DiagnosticCode::MalformedExport,
3318 "duplicate expression index",
3319 ));
3320 }
3321 metadata::check(&r)?;
3323 let refs: Vec<&Value> = if let Some(x) = r.get("app") {
3324 vec![&x["fn"], &x["arg"]]
3325 } else if let Some(x) = r.get("mdata") {
3326 vec![&x["expr"]]
3327 } else if let Some(x) = r.get("proj") {
3328 vec![&x["struct"]]
3329 } else if let Some(x) = r.get("letE") {
3330 vec![&x["type"], &x["value"], &x["body"]]
3331 } else if let Some(x) = r.get("lam").or_else(|| r.get("forallE")) {
3332 source_binder_kind(x)?;
3333 vec![&x["type"], &x["body"]]
3334 } else if r.get("sort").is_some()
3335 || r.get("bvar").is_some()
3336 || r.get("const").is_some()
3337 || r.get("natVal").is_some()
3338 || r.get("strVal").is_some()
3339 {
3340 vec![]
3341 } else {
3342 let code = if r.get("mvar").is_some() || r.get("fvar").is_some() {
3345 DiagnosticCode::UnresolvedMetavariable
3346 } else {
3347 DiagnosticCode::UnknownRecordKind
3348 };
3349 return Err(declined(code, "unsupported expression record"));
3350 };
3351 let mut depth = if r.get("strVal").is_some() {
3352 strings::layout(&r)?.1 - 1
3353 } else {
3354 0
3355 };
3356 for v in refs {
3357 let j = index(v)?;
3358 let d = self.expr_depth.get(&j).ok_or_else(|| {
3359 declined(
3360 DiagnosticCode::MalformedExport,
3361 "expression is not a backwards DAG reference",
3362 )
3363 })?;
3364 depth = depth.max(d + 1);
3365 }
3366 if let Some(data) = r.get("mdata").and_then(|x| x["data"].as_object()) {
3367 for (key, value) in data {
3368 self.report.metadata_losses.push(MetadataLoss {
3369 expression: i,
3370 key: key.clone(),
3371 value: value.as_str().expect("validated metadata value").into(),
3372 });
3373 }
3374 }
3375 self.expr_depth.insert(i, depth);
3376 self.exprs.insert(i, r);
3377 return Ok(());
3378 }
3379 if obj.len() != 1 {
3380 return Err(declined(
3381 DiagnosticCode::MalformedExport,
3382 "unknown declaration record fields",
3383 ));
3384 }
3385 let (kind, d) = obj.iter().next().unwrap();
3386 if kind == "quot" {
3387 self.trace_stage("registration", "begin");
3388 self.quotient(d)?;
3389 self.trace_stage("registration", "done");
3390 return Ok(());
3391 }
3392 if kind == "inductive" {
3393 self.trace_stage("registration", "begin");
3394 self.inductive(d)?;
3395 for field in ["types", "ctors", "recs"] {
3396 for declaration in array(&d[field])? {
3397 self.install_binder_kinds(declaration)?;
3398 }
3399 }
3400 self.trace_stage("registration", "done");
3401 return Ok(());
3402 }
3403 if !["def", "axiom", "thm", "opaque"].contains(&kind.as_str()) {
3404 return Err(declined(
3405 DiagnosticCode::UnknownRecordKind,
3406 format!("unsupported declaration: {kind}"),
3407 ));
3408 }
3409 let mut runtime_safety = match d.get("safety") {
3410 None => None,
3411 Some(Value::String(s)) if s == "safe" => None,
3412 Some(Value::String(s)) if s == "unsafe" => Some("unsafe"),
3413 Some(Value::String(s)) if s == "partial" => Some("partial"),
3414 _ => {
3415 return Err(declined(
3416 DiagnosticCode::MalformedExport,
3417 "unknown declaration safety",
3418 ))
3419 }
3420 };
3421 if let Some(flag) = d.get("isUnsafe") {
3422 let flag = flag.as_bool().ok_or_else(|| {
3423 declined(
3424 DiagnosticCode::MalformedExport,
3425 "malformed declaration safety",
3426 )
3427 })?;
3428 if flag {
3429 if runtime_safety == Some("partial") {
3430 return Err(rejected(
3431 DiagnosticCode::MalformedExport,
3432 "conflicting declaration safety",
3433 ));
3434 }
3435 runtime_safety = Some("unsafe");
3436 }
3437 }
3438 if kind == "thm" && runtime_safety.is_some() {
3439 return Err(rejected(
3440 DiagnosticCode::InvalidProof,
3441 "theorem cannot be an unsafe or partial runtime declaration",
3442 ));
3443 }
3444 let name = self.name(&d["name"])?;
3445 if name.starts_with("kernel.quot.") {
3446 return Err(rejected(
3447 DiagnosticCode::InvalidProof,
3448 "quotient identities require a canonical primitive declaration",
3449 ));
3450 }
3451 if matches!(name.as_str(), "Nat" | "Zero" | "Succ") {
3452 return Err(declined(
3453 DiagnosticCode::UnsupportedFeature,
3454 "primitive Nat identities require a canonical inductive block",
3455 ));
3456 }
3457 if self.declarations.contains_key(&name) {
3458 return Err(rejected(
3459 DiagnosticCode::DuplicateDeclaration,
3460 "duplicate declaration",
3461 ));
3462 }
3463 let params = array(&d["levelParams"])?
3464 .iter()
3465 .map(|v| self.name(v))
3466 .collect::<Result<Vec<_>>>()?;
3467 let bound = params.iter().cloned().collect::<HashSet<_>>();
3468 if bound.len() != params.len() {
3469 return Err(rejected(
3470 DiagnosticCode::InvalidProof,
3471 "duplicate universe parameter",
3472 ));
3473 }
3474 if let Some(safety) = runtime_safety {
3475 self.trace_stage("runtime.quarantine", "begin");
3476 let type_expression = index(&d["type"])?;
3477 let value_expression = if kind == "axiom" {
3478 None
3479 } else {
3480 Some(index(&d["value"])?)
3481 };
3482 if !self.exprs.contains_key(&type_expression)
3483 || value_expression.is_some_and(|id| !self.exprs.contains_key(&id))
3484 {
3485 return Err(declined(
3486 DiagnosticCode::MalformedExport,
3487 "unknown quarantined runtime expression",
3488 ));
3489 }
3490 self.runtime_names.insert(name.clone(), safety);
3493 self.declarations.insert(name.clone(), params.len());
3494 self.report.runtime_declarations.push(RuntimeDeclaration {
3495 name,
3496 kind: kind.clone(),
3497 safety: safety.into(),
3498 type_expression,
3499 value_expression,
3500 });
3501 self.runtime_target_pending = true;
3502 self.trace_stage("runtime.quarantine", "done");
3503 return Ok(());
3504 }
3505 let roots = if kind == "axiom" {
3506 vec![&d["type"]]
3507 } else {
3508 vec![&d["type"], &d["value"]]
3509 };
3510 self.check_runtime_references(&roots)?;
3511 let mut budget = 100_000;
3512 self.trace_stage("type.parse", "begin");
3513 let ty = self.shared_term(&d["type"], &mut Vec::new(), &bound, &mut budget)?;
3514 self.trace_stage("type.parse", "done");
3515 self.trace_stage("type.infer", "begin");
3516 lean_type(&self.ctx, &ty)?;
3517 let u = sort(&self.ctx, &ty)?;
3518 self.trace_stage("type.infer", "done");
3519 if kind == "thm" && !u.equiv(&Universe::Prop) {
3520 return Err(rejected(
3521 DiagnosticCode::InvalidProof,
3522 "theorem type is not a proposition",
3523 ));
3524 }
3525 let mut audit = None;
3526 if kind == "axiom" {
3527 if self.audit_declarations {
3528 audit = Some(self.declaration_audit(&name, kind, d, &ty, None)?);
3529 }
3530 self.trace_stage("registration", "begin");
3531 self.ctx
3532 .add_universe_poly_declaration(&name, params.clone(), ty)
3533 .map_err(kernel)?;
3534 } else {
3535 self.trace_stage("body.parse", "begin");
3536 let body = self.shared_term(&d["value"], &mut Vec::new(), &bound, &mut budget)?;
3537 self.trace_stage("body.parse", "done");
3538 if kind == "def" && params.is_empty() && !self.audit_declarations {
3539 let sink = self.trace_sink;
3540 let declaration = self.trace_declaration.clone();
3541 let retain = self.retain.covers(&name, kind);
3542 let mut retained = None;
3543 self.ctx
3544 .declare_with_validation::<Verdict>(&name, ty, body, |ctx, ty, body| {
3545 emit_stage(sink, declaration.as_ref(), "body.infer", "begin");
3546 let actual = lean_type(ctx, body)?;
3547 emit_stage(sink, declaration.as_ref(), "body.infer", "done");
3548 emit_stage(sink, declaration.as_ref(), "exact", "begin");
3549 exact(ctx, &actual, ty)?;
3550 emit_stage(sink, declaration.as_ref(), "exact", "done");
3551 if retain {
3552 retained = Some((name.clone(), ty.clone(), body.clone()));
3553 }
3554 emit_stage(sink, declaration.as_ref(), "registration", "begin");
3555 Ok(())
3556 })?;
3557 self.retained.extend(retained);
3558 } else {
3559 self.trace_stage("body.infer", "begin");
3560 let actual = lean_type(&self.ctx, &body)?;
3561 self.trace_stage("body.infer", "done");
3562 self.trace_stage("exact", "begin");
3564 exact(&self.ctx, &actual, &ty)?;
3565 self.trace_stage("exact", "done");
3566 if self.audit_declarations {
3567 audit = Some(self.declaration_audit(&name, kind, d, &ty, Some(&body))?);
3568 }
3569 if self.retain.covers(&name, kind) {
3572 self.retained.push((name.clone(), ty.clone(), body.clone()));
3573 }
3574 self.trace_stage("registration", "begin");
3575 if kind == "thm" {
3578 self.ctx
3579 .declare_theorem_checked(&name, params.clone(), ty, body)
3580 .map_err(kernel)?;
3581 } else if kind == "opaque" {
3582 self.ctx
3583 .declare_opaque_checked(&name, params.clone(), ty, body)
3584 .map_err(kernel)?;
3585 } else if params.is_empty() {
3586 self.ctx.declare(&name, ty, body).map_err(kernel)?;
3587 } else {
3588 self.ctx
3589 .declare_universe_poly(&name, params.clone(), ty, body)
3590 .map_err(kernel)?;
3591 }
3592 }
3593 if !self.ctx.pending_obligations().is_empty() {
3594 return Err(declined(
3595 DiagnosticCode::UnsupportedFeature,
3596 "unresolved kernel obligation",
3597 ));
3598 }
3599 }
3600 self.install_binder_kinds(d)?;
3601 if let Some(audit) = audit {
3602 self.audited_names
3603 .insert(name.clone(), self.report.declarations.len());
3604 self.report.declarations.push(audit);
3605 }
3606 self.declarations.insert(name, params.len());
3607 if kind != "axiom" {
3610 self.runtime_target_pending = false;
3611 }
3612 self.trace_stage("registration", "done");
3613 Ok(())
3614 }
3615}
3616pub(crate) fn canonical_name(encoded: &str) -> String {
3622 match encoded {
3623 "/s:\"Quot\"" => crate::quotient::Kind::Type.name().into(),
3624 "/s:\"Quot\"/s:\"mk\"" => crate::quotient::Kind::Constructor.name().into(),
3625 "/s:\"Quot\"/s:\"lift\"" => crate::quotient::Kind::Lift.name().into(),
3626 "/s:\"Quot\"/s:\"ind\"" => crate::quotient::Kind::Induction.name().into(),
3627 "/s:\"Nat\"" => "Nat".into(),
3628 "/s:\"Nat\"/s:\"zero\"" => "Zero".into(),
3629 "/s:\"Nat\"/s:\"succ\"" => "Succ".into(),
3630 _ => match encoded.strip_prefix("/s:\"Nat\"/") {
3631 Some(suffix) => format!("Nat/{suffix}"),
3632 None => encoded.to_string(),
3633 },
3634 }
3635}
3636fn declared_name_index(r: &Value) -> Option<usize> {
3638 let object = r.as_object().filter(|o| o.len() == 1)?;
3639 let (kind, source) = object.iter().next()?;
3640 let name = match kind.as_str() {
3641 "def" | "axiom" | "thm" | "opaque" | "quot" => &source["name"],
3642 "inductive" => &source["types"][0]["name"],
3643 _ => return None,
3644 };
3645 name.as_u64().and_then(|n| usize::try_from(n).ok())
3646}
3647fn record_children(r: &Value) -> Vec<&Value> {
3648 if let Some(x) = r.get("app") {
3649 vec![&x["fn"], &x["arg"]]
3650 } else if let Some(x) = r.get("mdata") {
3651 vec![&x["expr"]]
3652 } else if let Some(x) = r.get("proj") {
3653 vec![&x["struct"]]
3654 } else if let Some(x) = r.get("letE") {
3655 vec![&x["type"], &x["value"], &x["body"]]
3656 } else if let Some(x) = r.get("lam").or_else(|| r.get("forallE")) {
3657 vec![&x["type"], &x["body"]]
3658 } else {
3659 Vec::new()
3660 }
3661}
3662struct ImportOptions<'a> {
3663 trace: Option<StageSink<'a>>,
3664 audit_declarations: bool,
3665 retain: RetainSource,
3666 binder_names: bool,
3667 native_decisions: bool,
3669}
3670#[derive(Clone, Debug, Default, PartialEq, Eq)]
3673pub enum RetainSource {
3674 #[default]
3676 Nothing,
3677 Named(HashSet<String>),
3679 EveryProof,
3681}
3682impl RetainSource {
3683 fn covers(&self, name: &str, kind: &str) -> bool {
3684 match self {
3685 RetainSource::Nothing => false,
3686 RetainSource::Named(names) => names.contains(name),
3687 RetainSource::EveryProof => kind == "thm" || kind == "opaque",
3688 }
3689 }
3690}
3691#[derive(Clone, Debug, PartialEq, Eq)]
3693pub struct ImportRequest {
3694 pub audit: bool,
3697 pub retain: RetainSource,
3698 pub lean_binder_names: bool,
3702}
3703impl Default for ImportRequest {
3704 fn default() -> Self {
3705 ImportRequest {
3706 audit: false,
3707 retain: RetainSource::Nothing,
3708 lean_binder_names: true,
3709 }
3710 }
3711}
3712pub fn lean_binder_name(param: &str) -> Option<&str> {
3714 let rest = param.strip_prefix("arena.local.")?;
3715 let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
3716 (digits > 0).then_some(())?;
3717 rest[digits..].strip_prefix('.').filter(|n| !n.is_empty())
3718}
3719fn check_records(
3720 input: &str,
3721 options: ImportOptions<'_>,
3722) -> Result<(ImportedContext, ImportReport)> {
3723 let mut importer = Importer::new(options.audit_declarations);
3724 importer.trace_sink = options.trace;
3725 importer.retain = options.retain;
3726 importer.binder_names = options.binder_names;
3727 importer.lean_version = input
3728 .lines()
3729 .next()
3730 .and_then(|header| serde_json::from_str::<Value>(header).ok())
3731 .and_then(|header| {
3732 header["meta"]["lean"]["version"]
3733 .as_str()
3734 .map(str::to_owned)
3735 })
3736 .unwrap_or_default();
3737 let native = options
3738 .native_decisions
3739 .then(|| native_decide::NativeDecisions::read(input));
3740 for (line, s) in input.lines().skip(1).enumerate() {
3741 let line = line + 2;
3742 let r = serde_json::from_str::<Value>(s).map_err(|e| {
3743 declined(DiagnosticCode::MalformedExport, format!("line {line}: {e}")).at_line(line)
3744 })?;
3745 let r = match &native {
3746 Some(native) => native.rederive(r),
3747 None => r,
3748 };
3749 let declared = declared_name_index(&r);
3750 importer.record(r, line).map_err(|verdict| {
3751 let verdict = match &native {
3752 Some(native) => native.explain(verdict, declared),
3753 None => verdict,
3754 };
3755 let verdict = verdict.located(format!("line {line}")).at_line(line);
3756 match declared.and_then(|i| importer.names.get(&i)) {
3757 Some(name) => verdict.in_declaration(intake::lean_name(&canonical_name(name))),
3758 None => verdict,
3759 }
3760 })?;
3761 }
3762 if importer.runtime_target_pending {
3763 return Err(declined(
3764 DiagnosticCode::UnsupportedFeature,
3765 RUNTIME_UNCHECKED,
3766 ));
3767 }
3768 let depth = importer.expr_depth.values().copied().max().unwrap_or(0);
3769 importer.report.native_decisions = native
3770 .map(|native| native.rederivations())
3771 .unwrap_or_default();
3772 Ok((
3773 ImportedContext {
3774 context: Some(importer.ctx),
3775 depth,
3776 retained: importer.retained,
3777 requested: None,
3778 },
3779 importer.report,
3780 ))
3781}
3782pub fn check_str(input: &str) -> Result<()> {
3783 check_str_with_report(input).map(|_| ())
3784}
3785pub fn check_str_with_report(input: &str) -> Result<ImportReport> {
3786 check_import(input, false).map(|(_, report)| report)
3787}
3788fn check_import(input: &str, audit_declarations: bool) -> Result<(ImportedContext, ImportReport)> {
3789 check_import_with_trace(input, audit_declarations, None)
3790}
3791fn check_import_with_trace(
3792 input: &str,
3793 audit_declarations: bool,
3794 trace: Option<StageSink<'_>>,
3795) -> Result<(ImportedContext, ImportReport)> {
3796 check_import_with_options(
3797 input,
3798 ImportOptions {
3799 audit_declarations,
3800 trace,
3801 retain: RetainSource::Nothing,
3802 binder_names: false,
3803 native_decisions: false,
3804 },
3805 )
3806}
3807fn check_import_with_options(
3808 input: &str,
3809 options: ImportOptions<'_>,
3810) -> Result<(ImportedContext, ImportReport)> {
3811 let mut lines = input.lines();
3812 let header: Value = serde_json::from_str(
3813 lines
3814 .next()
3815 .ok_or_else(|| declined(DiagnosticCode::MalformedExport, "missing header"))?,
3816 )
3817 .map_err(|e| declined(DiagnosticCode::MalformedExport, e.to_string()).at_line(1))?;
3818 if header["meta"]["format"]["version"] != "3.1.0" {
3819 return Err(declined(
3820 DiagnosticCode::UnsupportedExportVersion,
3821 "unsupported NDJSON format version",
3822 )
3823 .at_line(1));
3824 }
3825 let mut depths = HashMap::<usize, usize>::new();
3829 let mut depth = 0usize;
3830 for (line, s) in lines.enumerate() {
3831 let r = serde_json::from_str::<Value>(s).map_err(|e| {
3832 declined(
3833 DiagnosticCode::MalformedExport,
3834 format!("line {}: {e}", line + 2),
3835 )
3836 .at_line(line + 2)
3837 })?;
3838 if let Some(id) = r.get("ie").and_then(|v| index(v).ok()) {
3839 let children = record_children(&r);
3840 let d = if r.get("strVal").is_some() {
3841 strings::layout(&r)?.1
3842 } else {
3843 children
3844 .iter()
3845 .filter_map(|v| index(v).ok())
3846 .filter_map(|id| depths.get(&id))
3847 .copied()
3848 .max()
3849 .unwrap_or(0)
3850 .saturating_add(1)
3851 };
3852 depths.insert(id, d);
3853 depth = depth.max(d);
3854 }
3855 }
3856 drop(depths);
3857 if depth <= crate::term_store::INLINE_DEPTH {
3858 return check_records(input, options);
3859 }
3860 std::thread::scope(|scope| {
3863 std::thread::Builder::new()
3864 .name("lean-arena-import".into())
3865 .stack_size(crate::term_store::stack_bytes_for_depth(depth))
3866 .spawn_scoped(scope, move || check_records(input, options))
3867 .map_err(|e| {
3868 declined(
3869 DiagnosticCode::Infrastructure,
3870 format!("cannot reserve importer stack: {e}"),
3871 )
3872 })?
3873 .join()
3874 .unwrap_or_else(|p| std::panic::resume_unwind(p))
3875 })
3876}
3877#[derive(Debug, Default, PartialEq, Eq)]
3878pub struct ImportReport {
3879 pub internal_helpers: Vec<InternalHelper>,
3880 pub metadata_losses: Vec<MetadataLoss>,
3881 pub declarations: Vec<DeclarationAudit>,
3882 pub declaration_audit_requested: bool,
3883 pub runtime_declarations: Vec<RuntimeDeclaration>,
3884 pub native_decisions: Vec<native_decide::Rederivation>,
3886}
3887#[derive(Debug, PartialEq, Eq)]
3889pub struct InternalHelper {
3890 pub name: String,
3891 pub source_expression: usize,
3892 pub level_parameters: Vec<String>,
3893 pub captured_parameters: usize,
3894}
3895#[derive(Debug, PartialEq, Eq)]
3897pub struct RuntimeDeclaration {
3898 pub name: String,
3899 pub kind: String,
3900 pub safety: String,
3901 pub type_expression: usize,
3902 pub value_expression: Option<usize>,
3903}
3904#[derive(Debug, PartialEq, Eq)]
3905pub struct DeclarationAudit {
3906 pub name: String,
3907 pub kind: String,
3908 pub type_expression: usize,
3909 pub value_expression: Option<usize>,
3910 pub axioms: Vec<String>,
3911}
3912pub struct ImportedContext {
3914 context: Option<Context>,
3915 depth: usize,
3916 retained: Vec<(String, Term, Term)>,
3918 requested: Option<String>,
3920}
3921impl ImportedContext {
3922 pub fn retained_source(&self) -> Option<(&str, &Term, &Term)> {
3924 let requested = self.requested.as_deref()?;
3925 self.retained(requested)
3926 .map(|(ty, body)| (requested, ty, body))
3927 }
3928 pub fn retained(&self, name: &str) -> Option<(&Term, &Term)> {
3931 self.retained
3932 .iter()
3933 .find(|(n, ..)| n == name)
3934 .map(|(_, ty, body)| (ty, body))
3935 }
3936 pub fn retained_declarations(&self) -> impl Iterator<Item = (&str, &Term, &Term)> {
3938 self.retained
3939 .iter()
3940 .map(|(name, ty, body)| (name.as_str(), ty, body))
3941 }
3942}
3943impl std::ops::Deref for ImportedContext {
3944 type Target = Context;
3945 fn deref(&self) -> &Context {
3946 self.context.as_ref().expect("live imported context")
3947 }
3948}
3949impl Drop for ImportedContext {
3950 fn drop(&mut self) {
3951 let mut context = self.context.take();
3952 let source = std::mem::take(&mut self.retained);
3953 if self.depth <= crate::term_store::INLINE_DEPTH {
3954 drop(context);
3955 drop(source);
3956 return;
3957 }
3958 std::thread::scope(|scope| {
3962 std::thread::Builder::new()
3963 .name("lean-import-drop".into())
3964 .stack_size(crate::term_store::stack_bytes_for_depth(self.depth))
3965 .spawn_scoped(scope, move || {
3966 drop(context.take());
3967 drop(source);
3968 })
3969 .expect("reserve imported-context destruction stack")
3970 .join()
3971 .unwrap_or_else(|p| std::panic::resume_unwind(p));
3972 });
3973 }
3974}
3975pub fn import_audited(input: &str) -> Result<(ImportedContext, ImportReport)> {
3976 check_import(input, true)
3977}
3978pub fn import_audited_retaining_source(
3981 input: &str,
3982 name: &str,
3983) -> Result<(ImportedContext, ImportReport)> {
3984 let imported = check_import_with_options(
3985 input,
3986 ImportOptions {
3987 audit_declarations: true,
3988 trace: None,
3989 retain: RetainSource::Named([name.to_string()].into_iter().collect()),
3990 binder_names: false,
3991 native_decisions: false,
3992 },
3993 )?;
3994 let imported = request(imported, name)?;
3995 if imported.0.retained(name).is_none() {
3996 return Err(declined(
3997 DiagnosticCode::MissingDeclaration,
3998 "requested source declaration has no checked body",
3999 ));
4000 }
4001 Ok(imported)
4002}
4003pub fn import_audited_retaining(
4007 input: &str,
4008 names: &[String],
4009) -> Result<(ImportedContext, ImportReport)> {
4010 import_with(
4011 input,
4012 ImportRequest {
4013 audit: true,
4014 retain: RetainSource::Named(names.iter().cloned().collect()),
4015 lean_binder_names: true,
4016 },
4017 )
4018}
4019pub fn import(input: &str) -> Result<(ImportedContext, ImportReport)> {
4022 import_with(input, ImportRequest::default())
4023}
4024pub fn import_with(input: &str, request: ImportRequest) -> Result<(ImportedContext, ImportReport)> {
4028 check_import_with_options(
4029 input,
4030 ImportOptions {
4031 audit_declarations: request.audit,
4032 trace: None,
4033 retain: request.retain,
4034 binder_names: request.lean_binder_names,
4035 native_decisions: true,
4036 },
4037 )
4038}
4039pub fn import_retaining_closure(
4042 input: &str,
4043 target: &str,
4044 closure: &[String],
4045) -> Result<(ImportedContext, ImportReport)> {
4046 request(import_audited_retaining(input, closure)?, target)
4047}
4048const RUNTIME_UNCHECKED: &str =
4050 "runtime declarations remain unchecked without a subsequent checked safe target";
4051fn request(
4055 mut imported: (ImportedContext, ImportReport),
4056 target: &str,
4057) -> Result<(ImportedContext, ImportReport)> {
4058 if imported
4059 .1
4060 .runtime_declarations
4061 .iter()
4062 .any(|runtime| runtime.name == target)
4063 {
4064 return Err(declined(
4065 DiagnosticCode::UnsupportedFeature,
4066 RUNTIME_UNCHECKED,
4067 ));
4068 }
4069 imported.0.requested = Some(target.to_string());
4070 Ok(imported)
4071}
4072pub fn check_str_with_declaration_audit(input: &str) -> Result<ImportReport> {
4073 check_import(input, true).map(|(_, report)| report)
4074}
4075
4076#[derive(Debug, PartialEq, Eq)]
4077pub struct MetadataLoss {
4078 pub expression: usize,
4079 pub key: String,
4080 pub value: String,
4081}
4082impl ImportReport {
4083 pub fn to_json(&self) -> Value {
4084 serde_json::json!({
4085 "schema": "logos-lean-import-report/1",
4086 "acceptance_scope": "safe_kernel_environment",
4087 "internal_helpers": self.internal_helpers.iter().map(|h|serde_json::json!({
4088 "name":h.name, "source_expression":h.source_expression,
4089 "level_parameters":h.level_parameters, "captured_parameters":h.captured_parameters,
4090 "status":"independently_checked_definition",
4091 })).collect::<Vec<_>>(),
4092 "runtime_declarations": self.runtime_declarations.iter().map(|d| serde_json::json!({
4093 "name": d.name, "kind": d.kind, "safety": d.safety,
4094 "type_expression": d.type_expression, "value_expression": d.value_expression,
4095 "status": "quarantined_unchecked",
4096 })).collect::<Vec<_>>(),
4097 "declaration_audit": {
4098 "requested": self.declaration_audit_requested,
4099 "scope": ["def", "thm", "opaque", "axiom"],
4100 "declarations": self.declarations.iter().map(|d| serde_json::json!({
4101 "name": d.name, "kind": d.kind, "type_expression": d.type_expression,
4102 "value_expression": d.value_expression, "axioms": d.axioms,
4103 })).collect::<Vec<_>>(),
4104 },
4105 "losses": self.metadata_losses.iter().map(|loss| serde_json::json!({
4106 "category": "SOURCE_METADATA",
4107 "expression": loss.expression,
4108 "key": loss.key,
4109 "exported_value": loss.value,
4110 })).collect::<Vec<_>>(),
4111 "native_decisions": self.native_decisions.iter()
4112 .map(native_decide::Rederivation::to_json).collect::<Vec<_>>(),
4113 })
4114 }
4115}
4116
4117#[cfg(test)]
4118#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/lean_type_stack_controls.rs"]
4119mod lean_type_stack_controls;
4120
4121#[cfg(test)]
4122#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/exact_decision_controls.rs"]
4123mod exact_decision_controls;
4124#[cfg(test)]
4125#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/function_type_head_controls.rs"]
4126mod function_type_head_controls;
4127#[cfg(test)]
4128#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/independent_verdict_controls.rs"]
4129mod independent_verdict_controls;
4130
4131mod source_recursor;
4133
4134#[cfg(test)]
4135#[path = "../../../../logicaffeine_tests/unit/logicaffeine_kernel/interface/lean/reference_projection.rs"]
4136mod reference_projection;
4137
4138mod source_recursor_assembly;
4139
4140mod reference_projection_shared;
4141
4142mod runtime_refusal;