1use super::derive::{
25 check_and_derive_family, has_const_occ, recursor_matches, DerivedFamily, InductiveSpec,
26};
27use crate::declaration::{ConstantInfo, InductiveVal, RecursorRule, RecursorVal};
28use crate::expr_util::{get_app_fn_args, mk_app, replace_expr};
29use crate::instantiate::instantiate_type_lparams;
30use crate::subst::instantiate;
31use crate::{Environment, Expr, KernelError, Level, Name, NameView, TypeChecker};
32
33#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct NestedOcc {
42 pub container: Name,
44 pub container_levels: Vec<Level>,
46 pub args: Vec<Expr>,
49 pub family_positions: Vec<usize>,
54}
55
56pub fn detect(family_names: &[Name], ty: &Expr) -> Option<NestedOcc> {
72 let (head, args) = get_app_fn_args(ty);
73 let (container, container_levels) = match head {
74 Expr::Const(n, levels) => (n.clone(), levels.clone()),
75 _ => return None,
76 };
77 if family_names.contains(&container) {
78 return None;
80 }
81 let family_positions: Vec<usize> = args
82 .iter()
83 .copied()
84 .enumerate()
85 .filter(|(_, a)| has_const_occ(a, family_names))
86 .map(|(i, _)| i)
87 .collect();
88 if family_positions.is_empty() {
89 return None;
91 }
92 let args = args.into_iter().cloned().collect();
93 Some(NestedOcc {
94 container,
95 container_levels,
96 args,
97 family_positions,
98 })
99}
100
101#[derive(Clone, Debug)]
110pub struct AuxMap {
111 pub aux_type: Name,
113 pub container: Name,
115 pub container_levels: Vec<Level>,
117 pub params: Vec<Expr>,
120 pub aux_ctors: Vec<(Name, Name)>,
122 pub aux_rec: Name,
124 pub container_rec: Name,
126}
127
128#[derive(Clone, Debug)]
132pub struct ExpandedFamily {
133 pub specs: Vec<InductiveSpec>,
135 pub num_originals: usize,
137 pub aux_maps: Vec<AuxMap>,
139 pub lparams: Vec<Name>,
141 pub num_params: u32,
143}
144
145struct Found {
147 container: Name,
148 levels: Vec<Level>,
149 as_args: Vec<Expr>,
151 num_params: usize,
152}
153
154fn lookup_inductive<'a>(env: &'a Environment, name: &Name) -> Option<&'a InductiveVal> {
155 match env.find(name)? {
156 ConstantInfo::Inductive(iv) => Some(iv),
157 _ => None,
158 }
159}
160
161fn lookup_ctor(env: &Environment, name: &Name) -> Option<(Expr, Vec<Name>)> {
163 match env.find(name)? {
164 ConstantInfo::Constructor(cv) => {
165 Some((cv.common.ty.clone(), cv.common.level_params.clone()))
166 }
167 _ => None,
168 }
169}
170
171fn last_component(name: &Name) -> String {
172 match name.view() {
173 NameView::Str(_, s) => s.to_string(),
174 _ => "ctor".to_string(),
175 }
176}
177
178fn instantiate_leading(ty: &Expr, args: &[Expr]) -> Expr {
180 let mut cur = ty.clone();
181 for arg in args {
182 match cur {
183 Expr::Pi(_, _, _dom, body) => {
184 cur = instantiate(&body, arg);
185 }
186 _ => break,
187 }
188 }
189 cur
190}
191
192fn replace_container_app(
196 ty: &Expr,
197 container: &Name,
198 container_levels: &[Level],
199 as_args: &[Expr],
200 np: usize,
201 aux: &Name,
202) -> Expr {
203 replace_expr(ty, &mut |e, _depth| {
204 let (head, args) = get_app_fn_args(e);
205 if let Expr::Const(n, levels) = head {
206 if n == container && levels.as_slice() == container_levels && args.len() >= np {
207 let matches = as_args
208 .iter()
209 .zip(args.iter().take(np))
210 .all(|(a, b)| a == *b);
211 if matches {
212 let rest: Vec<Expr> = args[np..]
213 .iter()
214 .map(|x| {
215 replace_container_app(x, container, container_levels, as_args, np, aux)
216 })
217 .collect();
218 return Some(mk_app(Expr::Const(aux.clone(), vec![]), &rest));
219 }
220 }
221 }
222 None
223 })
224}
225
226#[allow(clippy::result_large_err)]
231fn scan(env: &Environment, all_family: &[Name], e: &Expr) -> Result<Option<Found>, KernelError> {
232 let (head, args) = get_app_fn_args(e);
233 if let Expr::Const(c, levels) = head {
234 if !all_family.contains(c) {
235 if let Some(iv) = lookup_inductive(env, c) {
236 let np = iv.num_params as usize;
237 if args.len() >= np {
238 let fam_in_param = args.iter().take(np).any(|a| has_const_occ(a, all_family));
239 let fam_in_index = args.iter().skip(np).any(|a| has_const_occ(a, all_family));
240 if fam_in_index {
241 return Err(KernelError::UnsupportedNestedInductive(
242 all_family.first().cloned().unwrap_or_default(),
243 ));
244 }
245 if fam_in_param {
246 let as_args = args.iter().take(np).map(|x| (**x).clone()).collect();
247 return Ok(Some(Found {
248 container: c.clone(),
249 levels: levels.clone(),
250 as_args,
251 num_params: np,
252 }));
253 }
254 }
255 }
256 }
257 }
258 match e {
259 Expr::App(f, a) => {
260 if let Some(x) = scan(env, all_family, f)? {
261 return Ok(Some(x));
262 }
263 scan(env, all_family, a)
264 }
265 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
266 if let Some(x) = scan(env, all_family, ty)? {
267 return Ok(Some(x));
268 }
269 scan(env, all_family, body)
270 }
271 Expr::Let(_, ty, val, body) => {
272 if let Some(x) = scan(env, all_family, ty)? {
273 return Ok(Some(x));
274 }
275 if let Some(x) = scan(env, all_family, val)? {
276 return Ok(Some(x));
277 }
278 scan(env, all_family, body)
279 }
280 Expr::Proj(_, _, inner) => scan(env, all_family, inner),
281 _ => Ok(None),
282 }
283}
284
285#[allow(clippy::result_large_err)]
292pub fn specialize(
293 env: &Environment,
294 lparams: &[Name],
295 num_params: u32,
296 specs: &[InductiveSpec],
297) -> Result<Option<ExpandedFamily>, KernelError> {
298 if num_params != 0 {
302 return Ok(None);
303 }
304
305 let orig_names: Vec<Name> = specs.iter().map(|s| s.name.clone()).collect();
306 let mut all_family = orig_names.clone();
307 let mut cur_specs = specs.to_vec();
308 let mut aux_maps: Vec<AuxMap> = Vec::new();
309 let mut aux_count = 0usize;
310 let mut fuel = 256usize;
311
312 loop {
313 fuel = fuel.checked_sub(1).ok_or_else(|| {
314 KernelError::Other("nested inductive specialization did not terminate".to_string())
315 })?;
316
317 let mut found: Option<Found> = None;
319 'outer: for spec in &cur_specs {
320 for (_, cty) in &spec.ctors {
321 if let Some(f) = scan(env, &all_family, cty)? {
322 found = Some(f);
323 break 'outer;
324 }
325 }
326 }
327 let Some(f) = found else { break };
328
329 let iv = match lookup_inductive(env, &f.container) {
330 Some(iv) if iv.all.len() == 1 => iv.clone(),
332 _ => return Ok(None),
333 };
334
335 let aux_name = Name::mk_str(orig_names[0].clone(), format!("_nested_{}", aux_count));
336 aux_count += 1;
337 let c_levels = f.levels.clone();
338 let as_args = f.as_args.clone();
339 let np = f.num_params;
340
341 let aux_ty = {
345 let t = instantiate_type_lparams(&iv.common.ty, &iv.common.level_params, &c_levels);
346 instantiate_leading(&t, &as_args)
347 };
348
349 let mut aux_ctors: Vec<(Name, Expr)> = Vec::with_capacity(iv.ctors.len());
352 let mut ctor_name_map: Vec<(Name, Name)> = Vec::with_capacity(iv.ctors.len());
353 for cn in &iv.ctors {
354 let (cty, clp) = lookup_ctor(env, cn).ok_or_else(|| {
355 KernelError::Other(format!(
356 "constructor '{}' of nested container '{}' not found",
357 cn, f.container
358 ))
359 })?;
360 let t = instantiate_type_lparams(&cty, &clp, &c_levels);
361 let t = instantiate_leading(&t, &as_args);
362 let t = replace_container_app(&t, &f.container, &c_levels, &as_args, np, &aux_name);
363 let aux_cn = Name::mk_str(aux_name.clone(), last_component(cn));
364 aux_ctors.push((aux_cn.clone(), t));
365 ctor_name_map.push((aux_cn, cn.clone()));
366 }
367
368 aux_maps.push(AuxMap {
369 aux_type: aux_name.clone(),
370 container: f.container.clone(),
371 container_levels: c_levels.clone(),
372 params: as_args.clone(),
373 aux_ctors: ctor_name_map,
374 aux_rec: Name::mk_str(aux_name.clone(), "rec".to_string()),
375 container_rec: Name::mk_str(f.container.clone(), "rec".to_string()),
376 });
377
378 all_family.push(aux_name.clone());
379 cur_specs.push(InductiveSpec::new(aux_name.clone(), aux_ty, aux_ctors));
380
381 for spec in cur_specs.iter_mut() {
383 for (_, cty) in spec.ctors.iter_mut() {
384 *cty = replace_container_app(cty, &f.container, &c_levels, &as_args, np, &aux_name);
385 }
386 }
387 }
388
389 if all_family.len() == orig_names.len() {
390 return Ok(None);
391 }
392
393 Ok(Some(ExpandedFamily {
394 specs: cur_specs,
395 num_originals: orig_names.len(),
396 aux_maps,
397 lparams: lparams.to_vec(),
398 num_params,
399 }))
400}
401
402#[allow(clippy::result_large_err)]
410pub fn check_and_derive_family_nested(
411 env: &Environment,
412 lparams: &[Name],
413 num_params: u32,
414 specs: &[InductiveSpec],
415) -> Result<(DerivedFamily, Option<ExpandedFamily>), KernelError> {
416 super::derive::check_specs_closed(specs)?;
418 match specialize(env, lparams, num_params, specs)? {
419 None => Ok((
420 check_and_derive_family(env, lparams, num_params, specs)?,
421 None,
422 )),
423 Some(exp) => {
424 let fam = check_and_derive_family(env, &exp.lparams, exp.num_params, &exp.specs)?;
425 Ok((fam, Some(exp)))
426 }
427 }
428}
429
430fn restore_expr(e: &Expr, aux_maps: &[AuxMap]) -> Expr {
440 replace_expr(e, &mut |sub, _depth| {
441 if let Expr::Const(name, _levels) = sub {
442 for m in aux_maps {
443 if name == &m.aux_type {
444 return Some(mk_app(
445 Expr::Const(m.container.clone(), m.container_levels.clone()),
446 &m.params,
447 ));
448 }
449 for (aux_ctor, real_ctor) in &m.aux_ctors {
450 if name == aux_ctor {
451 return Some(mk_app(
452 Expr::Const(real_ctor.clone(), m.container_levels.clone()),
453 &m.params,
454 ));
455 }
456 }
457 }
458 }
459 None
460 })
461}
462
463fn restore_ctor_name(ctor: &Name, aux_maps: &[AuxMap]) -> Name {
466 for m in aux_maps {
467 for (aux_ctor, real_ctor) in &m.aux_ctors {
468 if ctor == aux_ctor {
469 return real_ctor.clone();
470 }
471 }
472 }
473 ctor.clone()
474}
475
476pub fn restore(fam: DerivedFamily, exp: &ExpandedFamily) -> DerivedFamily {
484 let orig_names: Vec<Name> = exp.specs[..exp.num_originals]
485 .iter()
486 .map(|s| s.name.clone())
487 .collect();
488 let num_nested = exp.aux_maps.len() as u32;
489
490 let mut inductives = Vec::with_capacity(exp.num_originals);
491 for ci in fam.inductives.iter().take(exp.num_originals) {
492 if let ConstantInfo::Inductive(iv) = ci {
493 let mut iv = iv.clone();
494 iv.common.ty = restore_expr(&iv.common.ty, &exp.aux_maps);
495 iv.all = orig_names.clone();
496 iv.num_nested = num_nested;
497 inductives.push(ConstantInfo::Inductive(iv));
498 }
499 }
500
501 let mut constructors = Vec::new();
502 for ci in &fam.constructors {
503 if let ConstantInfo::Constructor(cv) = ci {
504 if orig_names.contains(&cv.induct) {
505 let mut cv = cv.clone();
506 cv.common.ty = restore_expr(&cv.common.ty, &exp.aux_maps);
507 constructors.push(ConstantInfo::Constructor(cv));
508 }
509 }
510 }
511
512 let mut recursors = Vec::with_capacity(fam.recursors.len());
513 for ci in &fam.recursors {
514 if let ConstantInfo::Recursor(rv) = ci {
515 let mut rv = rv.clone();
516 rv.common.ty = restore_expr(&rv.common.ty, &exp.aux_maps);
517 rv.all = orig_names.clone();
518 rv.rules = rv
519 .rules
520 .iter()
521 .map(|rule| RecursorRule {
522 ctor: restore_ctor_name(&rule.ctor, &exp.aux_maps),
523 nfields: rule.nfields,
524 rhs: restore_expr(&rule.rhs, &exp.aux_maps),
525 })
526 .collect();
527 recursors.push(ConstantInfo::Recursor(rv));
528 }
529 }
530
531 DerivedFamily {
532 inductives,
533 constructors,
534 recursors,
535 }
536}
537
538fn rule_ctor_set(rv: &RecursorVal) -> Vec<Name> {
547 let mut s: Vec<Name> = rv.rules.iter().map(|r| r.ctor.clone()).collect();
548 s.sort();
549 s
550}
551
552fn rename_recursors(fam: DerivedFamily, name_map: &[(Name, Name)]) -> DerivedFamily {
556 fn rename_expr(e: &Expr, name_map: &[(Name, Name)]) -> Expr {
557 replace_expr(e, &mut |sub, _depth| {
558 if let Expr::Const(n, levels) = sub {
559 for (from, to) in name_map {
560 if n == from {
561 return Some(Expr::Const(to.clone(), levels.clone()));
562 }
563 }
564 }
565 None
566 })
567 }
568 let mut recursors = Vec::with_capacity(fam.recursors.len());
569 for ci in fam.recursors {
570 if let ConstantInfo::Recursor(mut rv) = ci {
571 for (from, to) in name_map {
572 if rv.common.name == *from {
573 rv.common.name = to.clone();
574 break;
575 }
576 }
577 rv.common.ty = rename_expr(&rv.common.ty, name_map);
578 rv.rules = rv
579 .rules
580 .into_iter()
581 .map(|r| RecursorRule {
582 ctor: r.ctor,
583 nfields: r.nfields,
584 rhs: rename_expr(&r.rhs, name_map),
585 })
586 .collect();
587 recursors.push(ConstantInfo::Recursor(rv));
588 } else {
589 recursors.push(ci);
590 }
591 }
592 DerivedFamily {
593 inductives: fam.inductives,
594 constructors: fam.constructors,
595 recursors,
596 }
597}
598
599#[allow(clippy::result_large_err)]
615pub fn verify_nested_bundle(
616 env: &Environment,
617 lparams: &[Name],
618 num_params: u32,
619 specs: &[InductiveSpec],
620 exported_recs: &[RecursorVal],
621) -> Result<DerivedFamily, KernelError> {
622 let (raw, exp_opt) = check_and_derive_family_nested(env, lparams, num_params, specs)?;
623 let Some(exp) = exp_opt else {
624 return Err(KernelError::UnsupportedNestedInductive(
625 specs.first().map(|s| s.name.clone()).unwrap_or_default(),
626 ));
627 };
628 let restored = restore(raw, &exp);
629
630 if restored.recursors.len() != exported_recs.len() {
631 return Err(KernelError::InvalidRecursor(format!(
632 "nested inductive '{}': kernel derived {} recursors but the export lists {}",
633 specs[0].name,
634 restored.recursors.len(),
635 exported_recs.len()
636 )));
637 }
638
639 let mut name_map: Vec<(Name, Name)> = Vec::with_capacity(restored.recursors.len());
641 for ci in &restored.recursors {
642 if let ConstantInfo::Recursor(rv) = ci {
643 let set = rule_ctor_set(rv);
644 let er = exported_recs
645 .iter()
646 .find(|er| rule_ctor_set(er) == set)
647 .ok_or_else(|| {
648 KernelError::InvalidRecursor(format!(
649 "nested inductive '{}': no exported recursor eliminates the same \
650 constructors as the kernel-derived '{}'",
651 specs[0].name, rv.common.name
652 ))
653 })?;
654 name_map.push((rv.common.name.clone(), er.common.name.clone()));
655 }
656 }
657
658 let renamed = rename_recursors(restored, &name_map);
659
660 let mut tc = TypeChecker::new(env);
662 for er in exported_recs {
663 let mut ok = false;
664 for ci in &renamed.recursors {
665 if let ConstantInfo::Recursor(d) = ci {
666 if d.common.name == er.common.name && recursor_matches(&mut tc, d, er) {
667 ok = true;
668 break;
669 }
670 }
671 }
672 if !ok {
673 return Err(KernelError::InvalidRecursor(format!(
674 "exported recursor '{}' does not match the kernel-derived nested recursor",
675 er.common.name
676 )));
677 }
678 }
679
680 Ok(renamed)
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686 use crate::Node;
687
688 fn c(name: &str, levels: Vec<Level>) -> Expr {
689 Expr::Const(Name::str(name), levels)
690 }
691
692 fn app(f: Expr, a: Expr) -> Expr {
693 Expr::App(Node::new(f), Node::new(a))
694 }
695
696 #[test]
697 fn detects_list_of_family() {
698 let fam = [Name::str("T")];
700 let ty = app(c("List", vec![Level::zero()]), c("T", vec![]));
701 let occ = detect(&fam, &ty).expect("nested occurrence");
702 assert_eq!(occ.container, Name::str("List"));
703 assert_eq!(occ.container_levels, vec![Level::zero()]);
704 assert_eq!(occ.family_positions, vec![0]);
705 assert_eq!(occ.args.len(), 1);
706 }
707
708 #[test]
709 fn direct_occurrence_is_not_nested() {
710 let fam = [Name::str("T")];
712 let ty = app(c("T", vec![]), c("x", vec![]));
713 assert!(detect(&fam, &ty).is_none());
714 }
715
716 #[test]
717 fn foreign_container_without_family_is_not_nested() {
718 let fam = [Name::str("T")];
720 let ty = app(c("List", vec![Level::zero()]), c("Nat", vec![]));
721 assert!(detect(&fam, &ty).is_none());
722 }
723
724 #[test]
725 fn bare_const_is_not_nested() {
726 let fam = [Name::str("T")];
727 assert!(detect(&fam, &c("Nat", vec![])).is_none());
728 }
729
730 #[test]
731 fn family_in_later_argument_position() {
732 let fam = [Name::str("T")];
734 let ty = app(
735 app(
736 c("Prod", vec![Level::zero(), Level::zero()]),
737 c("Nat", vec![]),
738 ),
739 c("T", vec![]),
740 );
741 let occ = detect(&fam, &ty).expect("nested occurrence");
742 assert_eq!(occ.container, Name::str("Prod"));
743 assert_eq!(occ.family_positions, vec![1]);
744 assert_eq!(occ.args.len(), 2);
745 }
746
747 #[test]
748 fn family_nested_deeper_in_argument() {
749 let fam = [Name::str("T")];
751 let inner = app(c("List", vec![Level::zero()]), c("T", vec![]));
752 let ty = app(c("Array", vec![Level::zero()]), inner);
753 let occ = detect(&fam, &ty).expect("nested occurrence");
754 assert_eq!(occ.container, Name::str("Array"));
755 assert_eq!(occ.family_positions, vec![0]);
756 }
757
758 #[test]
759 fn multiple_family_members() {
760 let fam = [Name::str("T"), Name::str("U")];
762 let ty = app(
763 app(c("Sum", vec![Level::zero(), Level::zero()]), c("T", vec![])),
764 c("U", vec![]),
765 );
766 let occ = detect(&fam, &ty).expect("nested occurrence");
767 assert_eq!(occ.family_positions, vec![0, 1]);
768 }
769
770 #[test]
773 fn non_nested_family_uses_ordinary_path() {
774 let env = Environment::new();
776 let spec = InductiveSpec::new(
777 Name::str("Foo"),
778 Expr::Sort(Level::succ(Level::zero())),
779 vec![(Name::str("Foo.mk"), c("Foo", vec![]))],
780 );
781 let (fam, exp) =
782 check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("ordinary derivation");
783 assert!(exp.is_none(), "non-nested family must not expand");
784 assert_eq!(fam.recursors.len(), 1);
785 }
786
787 #[test]
788 fn nested_t_derives_via_specialization() {
789 let mut env = Environment::new();
791 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
792 let t = c("NestedT", vec![]);
793 let list_t = app(c("List", vec![Level::zero()]), t.clone());
794 let spec = InductiveSpec::new(
795 Name::str("NestedT"),
796 Expr::Sort(Level::succ(Level::zero())),
797 vec![(
798 Name::str("NestedT.mk"),
799 Expr::Pi(
800 crate::BinderInfo::Default,
801 Name::str("l"),
802 Node::new(list_t),
803 Node::new(t),
804 ),
805 )],
806 );
807
808 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec])
809 .expect("nested inductive now derives");
810 let exp = exp.expect("NestedT must be recognised as nested");
811
812 assert_eq!(exp.num_originals, 1);
814 assert_eq!(exp.specs.len(), 2);
815 assert_eq!(exp.aux_maps.len(), 1);
816 let m = &exp.aux_maps[0];
817 assert_eq!(m.container, Name::str("List"));
818 let real_ctors: Vec<Name> = m.aux_ctors.iter().map(|(_, r)| r.clone()).collect();
819 assert!(real_ctors.contains(&Name::str("List.nil")));
820 assert!(real_ctors.contains(&Name::str("List.cons")));
821
822 assert_eq!(fam.inductives.len(), 2);
824 assert_eq!(fam.constructors.len(), 3);
825 assert_eq!(fam.recursors.len(), 2);
826
827 let nestedt_rec = fam
830 .recursors
831 .iter()
832 .find_map(|ci| match ci {
833 ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("NestedT.mk")).is_some() => {
834 Some(rv)
835 }
836 _ => None,
837 })
838 .expect("a recursor eliminating NestedT.mk");
839 assert_eq!(nestedt_rec.num_motives, 2);
840 assert_eq!(nestedt_rec.num_minors, 3);
841 }
842
843 #[test]
844 fn nested_t_aux_recursor_iota_reduces() {
845 let mut env = crate::Environment::new();
848 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
849 let t = c("NestedT", vec![]);
850 let list_t = app(c("List", vec![Level::zero()]), t.clone());
851 let spec = InductiveSpec::new(
852 Name::str("NestedT"),
853 Expr::Sort(Level::succ(Level::zero())),
854 vec![(
855 Name::str("NestedT.mk"),
856 Expr::Pi(
857 crate::BinderInfo::Default,
858 Name::str("l"),
859 Node::new(list_t),
860 Node::new(t),
861 ),
862 )],
863 );
864 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
865 let exp = exp.expect("nested");
866
867 let mut env2 = env.clone();
870 for ci in fam.clone().into_constant_infos() {
871 env2.add_constant(ci).expect("install expanded family");
872 }
873
874 let aux = &exp.aux_maps[0];
875 let aux_nil = aux
876 .aux_ctors
877 .iter()
878 .find(|(_, r)| *r == Name::str("List.nil"))
879 .expect("aux nil ctor")
880 .0
881 .clone();
882
883 let ph = |s: &str| Expr::Const(Name::str(s), vec![]);
886 let e = mk_app(
887 Expr::Const(aux.aux_rec.clone(), vec![Level::zero()]),
888 &[
889 ph("mN"),
890 ph("mL"),
891 ph("minMk"),
892 ph("minNil"),
893 ph("minCons"),
894 Expr::Const(aux_nil, vec![]),
895 ],
896 );
897
898 let reduced = crate::TypeChecker::new(&env2).whnf(&e);
899 let head = get_app_fn_args(&reduced).0.clone();
900 assert_eq!(
901 head,
902 Expr::Const(Name::str("minNil"), vec![]),
903 "aux recursor did not iota-reduce to the nil minor: {:?}",
904 reduced
905 );
906 }
907
908 #[test]
909 fn reach_through_double_nesting_expands_to_three() {
910 let mut env = crate::Environment::new();
915 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
916 let two = c("Two", vec![]);
917 let list = |x: Expr| app(c("List", vec![Level::zero()]), x);
918 let spec = InductiveSpec::new(
919 Name::str("Two"),
920 Expr::Sort(Level::succ(Level::zero())),
921 vec![(
922 Name::str("Two.mk"),
923 Expr::Pi(
924 crate::BinderInfo::Default,
925 Name::str("l"),
926 Node::new(list(list(two.clone()))),
927 Node::new(two),
928 ),
929 )],
930 );
931 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
932 let exp = exp.expect("nested");
933 assert_eq!(exp.num_originals, 1);
934 assert_eq!(exp.specs.len(), 3, "Two + two List auxiliaries");
935 assert_eq!(exp.aux_maps.len(), 2);
936 let rec = fam
939 .recursors
940 .iter()
941 .find_map(|ci| match ci {
942 ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("Two.mk")).is_some() => {
943 Some(rv)
944 }
945 _ => None,
946 })
947 .expect("Two's recursor");
948 assert_eq!(rec.num_motives, 3);
949 assert_eq!(rec.num_minors, 5);
950 }
951
952 #[test]
955 fn nested_t_restore_produces_real_list_recursor() {
956 use crate::expr_util::collect_consts;
957 let mut env = crate::Environment::new();
958 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
959 let t = c("NestedT", vec![]);
960 let list_t = app(c("List", vec![Level::zero()]), t.clone());
961 let spec = InductiveSpec::new(
962 Name::str("NestedT"),
963 Expr::Sort(Level::succ(Level::zero())),
964 vec![(
965 Name::str("NestedT.mk"),
966 Expr::Pi(
967 crate::BinderInfo::Default,
968 Name::str("l"),
969 Node::new(list_t),
970 Node::new(t.clone()),
971 ),
972 )],
973 );
974 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
975 let exp = exp.expect("nested");
976 let aux_type = exp.aux_maps[0].aux_type.clone();
977 let aux_rec = exp.aux_maps[0].aux_rec.clone();
978 let restored = restore(fam, &exp);
979
980 assert_eq!(restored.inductives.len(), 1);
982 assert_eq!(restored.constructors.len(), 1);
983 assert_eq!(restored.recursors.len(), 2);
984
985 match &restored.inductives[0] {
986 ConstantInfo::Inductive(iv) => {
987 assert_eq!(iv.num_nested, 1);
988 assert_eq!(iv.all, vec![Name::str("NestedT")]);
989 }
990 _ => panic!("expected inductive"),
991 }
992
993 match &restored.constructors[0] {
995 ConstantInfo::Constructor(cv) => {
996 let consts = collect_consts(&cv.common.ty);
997 assert!(consts.contains(&Name::str("List")));
998 assert!(!consts.contains(&aux_type));
999 }
1000 _ => panic!("expected constructor"),
1001 }
1002
1003 let mut env2 = env.clone();
1006 for ci in restored.clone().into_constant_infos() {
1007 env2.add_constant(ci).expect("install restored family");
1008 }
1009 for ci in &restored.recursors {
1010 if let ConstantInfo::Recursor(rv) = ci {
1011 crate::TypeChecker::new(&env2)
1012 .infer_type(&rv.common.ty)
1013 .expect("restored recursor type re-checks");
1014 }
1015 }
1016
1017 let ph = |s: &str| Expr::Const(Name::str(s), vec![]);
1018 let nil_app = mk_app(
1019 Expr::Const(Name::str("List.nil"), vec![Level::zero()]),
1020 std::slice::from_ref(&t),
1021 );
1022
1023 let e_aux = mk_app(
1025 Expr::Const(aux_rec, vec![Level::zero()]),
1026 &[
1027 ph("mN"),
1028 ph("mL"),
1029 ph("minMk"),
1030 ph("minNil"),
1031 ph("minCons"),
1032 nil_app.clone(),
1033 ],
1034 );
1035 let r_aux = crate::TypeChecker::new(&env2).whnf(&e_aux);
1036 assert_eq!(
1037 get_app_fn_args(&r_aux).0.clone(),
1038 Expr::Const(Name::str("minNil"), vec![]),
1039 "restored aux recursor did not fire on real List.nil: {:?}",
1040 r_aux
1041 );
1042
1043 let main_rec = restored
1045 .recursors
1046 .iter()
1047 .find_map(|ci| match ci {
1048 ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("NestedT.mk")).is_some() => {
1049 Some(rv.common.name.clone())
1050 }
1051 _ => None,
1052 })
1053 .expect("NestedT.rec");
1054 let major = app(c("NestedT.mk", vec![]), nil_app);
1055 let e_main = mk_app(
1056 Expr::Const(main_rec, vec![Level::zero()]),
1057 &[
1058 ph("mN"),
1059 ph("mL"),
1060 ph("minMk"),
1061 ph("minNil"),
1062 ph("minCons"),
1063 major,
1064 ],
1065 );
1066 let r_main = crate::TypeChecker::new(&env2).whnf(&e_main);
1067 assert_eq!(
1068 get_app_fn_args(&r_main).0.clone(),
1069 Expr::Const(Name::str("minMk"), vec![]),
1070 "restored NestedT.rec did not fire on NestedT.mk: {:?}",
1071 r_main
1072 );
1073 }
1074
1075 fn nested_t_bundle() -> (crate::Environment, InductiveSpec, Vec<RecursorVal>) {
1080 let mut env = crate::Environment::new();
1081 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
1082 let t = c("NestedT", vec![]);
1083 let list_t = app(c("List", vec![Level::zero()]), t.clone());
1084 let spec = InductiveSpec::new(
1085 Name::str("NestedT"),
1086 Expr::Sort(Level::succ(Level::zero())),
1087 vec![(
1088 Name::str("NestedT.mk"),
1089 Expr::Pi(
1090 crate::BinderInfo::Default,
1091 Name::str("l"),
1092 Node::new(list_t),
1093 Node::new(t),
1094 ),
1095 )],
1096 );
1097 let (raw, exp) = check_and_derive_family_nested(&env, &[], 0, std::slice::from_ref(&spec))
1098 .expect("derive");
1099 let exp = exp.expect("nested");
1100 let restored = restore(raw, &exp);
1101 let name_map: Vec<(Name, Name)> = restored
1103 .recursors
1104 .iter()
1105 .filter_map(|ci| match ci {
1106 ConstantInfo::Recursor(rv) => {
1107 let exported = if rule_ctor_set(rv).contains(&Name::str("NestedT.mk")) {
1108 Name::mk_str(Name::str("NestedT"), "rec".to_string())
1109 } else {
1110 Name::mk_str(Name::str("NestedT"), "rec_1".to_string())
1111 };
1112 Some((rv.common.name.clone(), exported))
1113 }
1114 _ => None,
1115 })
1116 .collect();
1117 let canonical = rename_recursors(restored, &name_map);
1118 let exported_recs: Vec<RecursorVal> = canonical
1119 .recursors
1120 .iter()
1121 .filter_map(|ci| match ci {
1122 ConstantInfo::Recursor(rv) => Some(rv.clone()),
1123 _ => None,
1124 })
1125 .collect();
1126 (env, spec, exported_recs)
1127 }
1128
1129 #[test]
1130 fn verify_nested_bundle_accepts_matching_export() {
1131 let (env, spec, exported_recs) = nested_t_bundle();
1132 let out = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
1133 .expect("matching export verifies");
1134 let names: Vec<Name> = out
1136 .recursors
1137 .iter()
1138 .filter_map(|ci| match ci {
1139 ConstantInfo::Recursor(rv) => Some(rv.common.name.clone()),
1140 _ => None,
1141 })
1142 .collect();
1143 assert!(names.contains(&Name::mk_str(Name::str("NestedT"), "rec".to_string())));
1144 assert!(names.contains(&Name::mk_str(Name::str("NestedT"), "rec_1".to_string())));
1145 }
1146
1147 #[test]
1148 fn verify_nested_bundle_rejects_corrupted_recursor() {
1149 let (env, spec, mut exported_recs) = nested_t_bundle();
1150 exported_recs[0].num_minors += 1;
1152 let err = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
1153 .expect_err("corrupted export must be rejected");
1154 assert!(matches!(err, KernelError::InvalidRecursor(_)));
1155 }
1156
1157 #[test]
1158 fn verify_nested_bundle_rejects_wrong_recursor_count() {
1159 let (env, spec, mut exported_recs) = nested_t_bundle();
1160 exported_recs.pop();
1161 let err = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
1162 .expect_err("wrong recursor count must be rejected");
1163 assert!(matches!(err, KernelError::InvalidRecursor(_)));
1164 }
1165}