1use super::derive::{check_and_derive_family, has_const_occ, DerivedFamily, InductiveSpec};
25use super::supplied::{derived_under_supplied_names, recursor_matches, same_up_to_binders};
26use crate::declaration::{ConstantInfo, InductiveVal, RecursorRule, RecursorVal};
27use crate::expr_util::{get_app_fn_args, mk_app, replace_expr};
28use crate::instantiate::instantiate_type_lparams;
29use crate::subst::instantiate;
30use crate::{Environment, Expr, KernelError, Level, Name, NameView};
31
32#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct NestedOcc {
41 pub container: Name,
43 pub container_levels: Vec<Level>,
45 pub args: Vec<Expr>,
48 pub family_positions: Vec<usize>,
53}
54
55pub fn detect(family_names: &[Name], ty: &Expr) -> Option<NestedOcc> {
71 let (head, args) = get_app_fn_args(ty);
72 let (container, container_levels) = match head {
73 Expr::Const(n, levels) => (n.clone(), levels.clone()),
74 _ => return None,
75 };
76 if family_names.contains(&container) {
77 return None;
79 }
80 let family_positions: Vec<usize> = args
81 .iter()
82 .copied()
83 .enumerate()
84 .filter(|(_, a)| has_const_occ(a, family_names))
85 .map(|(i, _)| i)
86 .collect();
87 if family_positions.is_empty() {
88 return None;
90 }
91 let args = args.into_iter().cloned().collect();
92 Some(NestedOcc {
93 container,
94 container_levels,
95 args,
96 family_positions,
97 })
98}
99
100#[derive(Clone, Debug)]
109pub struct AuxMap {
110 pub aux_type: Name,
112 pub container: Name,
114 pub container_levels: Vec<Level>,
116 pub params: Vec<Expr>,
119 pub aux_ctors: Vec<(Name, Name)>,
121 pub aux_rec: Name,
123 pub container_rec: Name,
125}
126
127#[derive(Clone, Debug)]
131pub struct ExpandedFamily {
132 pub specs: Vec<InductiveSpec>,
134 pub num_originals: usize,
136 pub aux_maps: Vec<AuxMap>,
138 pub lparams: Vec<Name>,
140 pub num_params: u32,
142}
143
144struct Found {
146 container: Name,
147 levels: Vec<Level>,
148 as_args: Vec<Expr>,
150 num_params: usize,
151}
152
153fn lookup_inductive<'a>(env: &'a Environment, name: &Name) -> Option<&'a InductiveVal> {
154 match env.find(name)? {
155 ConstantInfo::Inductive(iv) => Some(iv),
156 _ => None,
157 }
158}
159
160fn lookup_ctor(env: &Environment, name: &Name) -> Option<(Expr, Vec<Name>)> {
162 match env.find(name)? {
163 ConstantInfo::Constructor(cv) => {
164 Some((cv.common.ty.clone(), cv.common.level_params.clone()))
165 }
166 _ => None,
167 }
168}
169
170fn last_component(name: &Name) -> String {
171 match name.view() {
172 NameView::Str(_, s) => s.to_string(),
173 _ => "ctor".to_string(),
174 }
175}
176
177fn instantiate_leading(ty: &Expr, args: &[Expr]) -> Expr {
179 let mut cur = ty.clone();
180 for arg in args {
181 match cur {
182 Expr::Pi(_, _, _dom, body) => {
183 cur = instantiate(&body, arg);
184 }
185 _ => break,
186 }
187 }
188 cur
189}
190
191fn replace_container_app(
195 ty: &Expr,
196 container: &Name,
197 container_levels: &[Level],
198 as_args: &[Expr],
199 np: usize,
200 aux: &Name,
201) -> Expr {
202 replace_expr(ty, &mut |e, _depth| {
203 let (head, args) = get_app_fn_args(e);
204 if let Expr::Const(n, levels) = head {
205 if n == container && levels.as_slice() == container_levels && args.len() >= np {
206 let matches = as_args
207 .iter()
208 .zip(args.iter().take(np))
209 .all(|(a, b)| a == *b);
210 if matches {
211 let rest: Vec<Expr> = args[np..]
212 .iter()
213 .map(|x| {
214 replace_container_app(x, container, container_levels, as_args, np, aux)
215 })
216 .collect();
217 return Some(mk_app(Expr::Const(aux.clone(), vec![]), &rest));
218 }
219 }
220 }
221 None
222 })
223}
224
225#[allow(clippy::result_large_err)]
230fn scan(env: &Environment, all_family: &[Name], e: &Expr) -> Result<Option<Found>, KernelError> {
231 let (head, args) = get_app_fn_args(e);
232 if let Expr::Const(c, levels) = head {
233 if !all_family.contains(c) {
234 if let Some(iv) = lookup_inductive(env, c) {
235 let np = iv.num_params as usize;
236 if args.len() >= np {
237 let fam_in_param = args.iter().take(np).any(|a| has_const_occ(a, all_family));
238 let fam_in_index = args.iter().skip(np).any(|a| has_const_occ(a, all_family));
239 if fam_in_index {
240 return Err(KernelError::UnsupportedNestedInductive(
241 all_family.first().cloned().unwrap_or_default(),
242 ));
243 }
244 if fam_in_param {
245 let as_args = args.iter().take(np).map(|x| (**x).clone()).collect();
246 return Ok(Some(Found {
247 container: c.clone(),
248 levels: levels.clone(),
249 as_args,
250 num_params: np,
251 }));
252 }
253 }
254 }
255 }
256 }
257 match e {
258 Expr::App(f, a) => {
259 if let Some(x) = scan(env, all_family, f)? {
260 return Ok(Some(x));
261 }
262 scan(env, all_family, a)
263 }
264 Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
265 if let Some(x) = scan(env, all_family, ty)? {
266 return Ok(Some(x));
267 }
268 scan(env, all_family, body)
269 }
270 Expr::Let(_, ty, val, body) => {
271 if let Some(x) = scan(env, all_family, ty)? {
272 return Ok(Some(x));
273 }
274 if let Some(x) = scan(env, all_family, val)? {
275 return Ok(Some(x));
276 }
277 scan(env, all_family, body)
278 }
279 Expr::Proj(_, _, inner) => scan(env, all_family, inner),
280 _ => Ok(None),
281 }
282}
283
284#[allow(clippy::result_large_err)]
291pub fn specialize(
292 env: &Environment,
293 lparams: &[Name],
294 num_params: u32,
295 specs: &[InductiveSpec],
296) -> Result<Option<ExpandedFamily>, KernelError> {
297 if num_params != 0 {
301 return Ok(None);
302 }
303
304 let orig_names: Vec<Name> = specs.iter().map(|s| s.name.clone()).collect();
305 let mut all_family = orig_names.clone();
306 let mut cur_specs = specs.to_vec();
307 let mut aux_maps: Vec<AuxMap> = Vec::new();
308 let mut aux_count = 0usize;
309 let mut fuel = 256usize;
310
311 loop {
312 fuel = fuel.checked_sub(1).ok_or_else(|| {
313 KernelError::Other("nested inductive specialization did not terminate".to_string())
314 })?;
315
316 let mut found: Option<Found> = None;
318 'outer: for spec in &cur_specs {
319 for (_, cty) in &spec.ctors {
320 if let Some(f) = scan(env, &all_family, cty)? {
321 found = Some(f);
322 break 'outer;
323 }
324 }
325 }
326 let Some(f) = found else { break };
327
328 let iv = match lookup_inductive(env, &f.container) {
329 Some(iv) if iv.all.len() == 1 => iv.clone(),
331 _ => return Ok(None),
332 };
333
334 let aux_name = Name::mk_str(orig_names[0].clone(), format!("_nested_{}", aux_count));
335 aux_count += 1;
336 let c_levels = f.levels.clone();
337 let as_args = f.as_args.clone();
338 let np = f.num_params;
339
340 let aux_ty = {
344 let t = instantiate_type_lparams(&iv.common.ty, &iv.common.level_params, &c_levels);
345 instantiate_leading(&t, &as_args)
346 };
347
348 let mut aux_ctors: Vec<(Name, Expr)> = Vec::with_capacity(iv.ctors.len());
351 let mut ctor_name_map: Vec<(Name, Name)> = Vec::with_capacity(iv.ctors.len());
352 for cn in &iv.ctors {
353 let (cty, clp) = lookup_ctor(env, cn).ok_or_else(|| {
354 KernelError::Other(format!(
355 "constructor '{}' of nested container '{}' not found",
356 cn, f.container
357 ))
358 })?;
359 let t = instantiate_type_lparams(&cty, &clp, &c_levels);
360 let t = instantiate_leading(&t, &as_args);
361 let t = replace_container_app(&t, &f.container, &c_levels, &as_args, np, &aux_name);
362 let aux_cn = Name::mk_str(aux_name.clone(), last_component(cn));
363 aux_ctors.push((aux_cn.clone(), t));
364 ctor_name_map.push((aux_cn, cn.clone()));
365 }
366
367 aux_maps.push(AuxMap {
368 aux_type: aux_name.clone(),
369 container: f.container.clone(),
370 container_levels: c_levels.clone(),
371 params: as_args.clone(),
372 aux_ctors: ctor_name_map,
373 aux_rec: Name::mk_str(aux_name.clone(), "rec".to_string()),
374 container_rec: Name::mk_str(f.container.clone(), "rec".to_string()),
375 });
376
377 all_family.push(aux_name.clone());
378 cur_specs.push(InductiveSpec::new(aux_name.clone(), aux_ty, aux_ctors));
379
380 for spec in cur_specs.iter_mut() {
382 for (_, cty) in spec.ctors.iter_mut() {
383 *cty = replace_container_app(cty, &f.container, &c_levels, &as_args, np, &aux_name);
384 }
385 }
386 }
387
388 if all_family.len() == orig_names.len() {
389 return Ok(None);
390 }
391
392 Ok(Some(ExpandedFamily {
393 specs: cur_specs,
394 num_originals: orig_names.len(),
395 aux_maps,
396 lparams: lparams.to_vec(),
397 num_params,
398 }))
399}
400
401#[allow(clippy::result_large_err)]
409pub fn check_and_derive_family_nested(
410 env: &Environment,
411 lparams: &[Name],
412 num_params: u32,
413 specs: &[InductiveSpec],
414) -> Result<(DerivedFamily, Option<ExpandedFamily>), KernelError> {
415 super::derive::check_specs_closed(specs)?;
417 match specialize(env, lparams, num_params, specs)? {
418 None => Ok((
419 check_and_derive_family(env, lparams, num_params, specs)?,
420 None,
421 )),
422 Some(exp) => {
423 let fam = check_and_derive_family(env, &exp.lparams, exp.num_params, &exp.specs)?;
424 Ok((fam, Some(exp)))
425 }
426 }
427}
428
429fn restore_expr(e: &Expr, aux_maps: &[AuxMap]) -> Expr {
439 replace_expr(e, &mut |sub, _depth| {
440 if let Expr::Const(name, _levels) = sub {
441 for m in aux_maps {
442 if name == &m.aux_type {
443 return Some(mk_app(
444 Expr::Const(m.container.clone(), m.container_levels.clone()),
445 &m.params,
446 ));
447 }
448 for (aux_ctor, real_ctor) in &m.aux_ctors {
449 if name == aux_ctor {
450 return Some(mk_app(
451 Expr::Const(real_ctor.clone(), m.container_levels.clone()),
452 &m.params,
453 ));
454 }
455 }
456 }
457 }
458 None
459 })
460}
461
462fn restore_ctor_name(ctor: &Name, aux_maps: &[AuxMap]) -> Name {
465 for m in aux_maps {
466 for (aux_ctor, real_ctor) in &m.aux_ctors {
467 if ctor == aux_ctor {
468 return real_ctor.clone();
469 }
470 }
471 }
472 ctor.clone()
473}
474
475pub fn restore(fam: DerivedFamily, exp: &ExpandedFamily) -> DerivedFamily {
483 let orig_names: Vec<Name> = exp.specs[..exp.num_originals]
484 .iter()
485 .map(|s| s.name.clone())
486 .collect();
487 let num_nested = exp.aux_maps.len() as u32;
488
489 let mut inductives = Vec::with_capacity(exp.num_originals);
490 for ci in fam.inductives.iter().take(exp.num_originals) {
491 if let ConstantInfo::Inductive(iv) = ci {
492 let mut iv = iv.clone();
493 iv.common.ty = restore_expr(&iv.common.ty, &exp.aux_maps);
494 iv.all = orig_names.clone();
495 iv.num_nested = num_nested;
496 inductives.push(ConstantInfo::Inductive(iv));
497 }
498 }
499
500 let mut constructors = Vec::new();
501 for ci in &fam.constructors {
502 if let ConstantInfo::Constructor(cv) = ci {
503 if orig_names.contains(&cv.induct) {
504 let mut cv = cv.clone();
505 cv.common.ty = restore_expr(&cv.common.ty, &exp.aux_maps);
506 constructors.push(ConstantInfo::Constructor(cv));
507 }
508 }
509 }
510
511 let mut recursors = Vec::with_capacity(fam.recursors.len());
512 for ci in &fam.recursors {
513 if let ConstantInfo::Recursor(rv) = ci {
514 let mut rv = rv.clone();
515 rv.common.ty = restore_expr(&rv.common.ty, &exp.aux_maps);
516 rv.all = orig_names.clone();
517 rv.rules = rv
518 .rules
519 .iter()
520 .map(|rule| RecursorRule {
521 ctor: restore_ctor_name(&rule.ctor, &exp.aux_maps),
522 nfields: rule.nfields,
523 rhs: restore_expr(&rule.rhs, &exp.aux_maps),
524 })
525 .collect();
526 recursors.push(ConstantInfo::Recursor(rv));
527 }
528 }
529
530 DerivedFamily {
531 inductives,
532 constructors,
533 recursors,
534 }
535}
536
537fn nested_recursor_name(specs: &[InductiveSpec], position: usize) -> Name {
555 match specs.get(position) {
556 Some(spec) => Name::mk_str(spec.name.clone(), "rec"),
557 None => {
558 let first = specs.first().map(|s| s.name.clone()).unwrap_or_default();
559 Name::mk_str(first, format!("rec_{}", position + 1 - specs.len()))
560 }
561 }
562}
563
564fn rename_recursors(fam: DerivedFamily, name_map: &[(Name, Name)]) -> DerivedFamily {
568 fn rename_expr(e: &Expr, name_map: &[(Name, Name)]) -> Expr {
569 replace_expr(e, &mut |sub, _depth| {
570 if let Expr::Const(n, levels) = sub {
571 for (from, to) in name_map {
572 if n == from {
573 return Some(Expr::Const(to.clone(), levels.clone()));
574 }
575 }
576 }
577 None
578 })
579 }
580 let mut recursors = Vec::with_capacity(fam.recursors.len());
581 for ci in fam.recursors {
582 if let ConstantInfo::Recursor(mut rv) = ci {
583 for (from, to) in name_map {
584 if rv.common.name == *from {
585 rv.common.name = to.clone();
586 break;
587 }
588 }
589 rv.common.ty = rename_expr(&rv.common.ty, name_map);
590 rv.rules = rv
591 .rules
592 .into_iter()
593 .map(|r| RecursorRule {
594 ctor: r.ctor,
595 nfields: r.nfields,
596 rhs: rename_expr(&r.rhs, name_map),
597 })
598 .collect();
599 recursors.push(ConstantInfo::Recursor(rv));
600 } else {
601 recursors.push(ci);
602 }
603 }
604 DerivedFamily {
605 inductives: fam.inductives,
606 constructors: fam.constructors,
607 recursors,
608 }
609}
610
611#[allow(clippy::result_large_err)]
625fn declared_family_restored(
626 specs: &[InductiveSpec],
627 restored: &DerivedFamily,
628) -> Result<(), KernelError> {
629 let family = specs.first().map(|s| s.name.clone()).unwrap_or_default();
630 let differs = |name: &Name| {
631 KernelError::InvalidInductive(format!(
632 "nested inductive '{family}': '{name}' is not declared with the type the kernel \
633 derives for it (a declaration must not name an auxiliary type of its own nesting)"
634 ))
635 };
636 if restored.inductives.len() != specs.len() {
637 return Err(differs(&family));
638 }
639 for (spec, ci) in specs.iter().zip(&restored.inductives) {
640 if ci.name() != &spec.name || !same_up_to_binders(ci.ty(), &spec.ty) {
641 return Err(differs(&spec.name));
642 }
643 }
644 let declared = specs.iter().flat_map(|spec| spec.ctors.iter());
645 if restored.constructors.len() != declared.clone().count() {
646 return Err(differs(&family));
647 }
648 for (ctor_name, ctor_ty) in declared {
649 let same = restored
650 .constructors
651 .iter()
652 .find(|ci| ci.name() == ctor_name)
653 .is_some_and(|ci| same_up_to_binders(ci.ty(), ctor_ty));
654 if !same {
655 return Err(differs(ctor_name));
656 }
657 }
658 Ok(())
659}
660
661#[allow(clippy::result_large_err)]
686pub fn verify_nested_bundle(
687 env: &Environment,
688 lparams: &[Name],
689 num_params: u32,
690 specs: &[InductiveSpec],
691 exported_recs: &[RecursorVal],
692) -> Result<DerivedFamily, KernelError> {
693 let (raw, exp_opt) = check_and_derive_family_nested(env, lparams, num_params, specs)?;
694 let Some(exp) = exp_opt else {
695 return Err(KernelError::UnsupportedNestedInductive(
696 specs.first().map(|s| s.name.clone()).unwrap_or_default(),
697 ));
698 };
699 let restored = restore(raw, &exp);
700 declared_family_restored(specs, &restored)?;
701
702 if restored.recursors.len() != exported_recs.len() {
703 return Err(KernelError::InvalidRecursor(format!(
704 "nested inductive '{}': kernel derived {} recursors but the export lists {}",
705 specs[0].name,
706 restored.recursors.len(),
707 exported_recs.len()
708 )));
709 }
710
711 let mut name_map: Vec<(Name, Name)> = Vec::with_capacity(restored.recursors.len());
716 for (position, ci) in restored.recursors.iter().enumerate() {
717 if let ConstantInfo::Recursor(rv) = ci {
718 let lean_name = nested_recursor_name(specs, position);
719 if !exported_recs.iter().any(|er| er.common.name == lean_name) {
720 return Err(KernelError::InvalidRecursor(format!(
721 "nested inductive '{}': the export has no recursor named '{}', which the \
722 kernel derives",
723 specs[0].name, lean_name
724 )));
725 }
726 name_map.push((rv.common.name.clone(), lean_name));
727 }
728 }
729
730 let renamed = rename_recursors(restored, &name_map);
731
732 let family: [&[ConstantInfo]; 3] = [
736 &renamed.inductives,
737 &renamed.constructors,
738 &renamed.recursors,
739 ];
740 let mut installed: Vec<ConstantInfo> = Vec::with_capacity(renamed.recursors.len());
741 for ci in &renamed.recursors {
742 let ConstantInfo::Recursor(d) = ci else {
743 continue;
744 };
745 let matched = exported_recs
746 .iter()
747 .filter(|er| er.common.name == d.common.name)
748 .find_map(|er| {
749 let under_names = derived_under_supplied_names(d, er)?;
750 recursor_matches(env, &family, &under_names, er).then_some(under_names)
751 });
752 match matched {
753 Some(under_names) => installed.push(ConstantInfo::Recursor(under_names)),
754 None => {
755 return Err(KernelError::InvalidRecursor(format!(
756 "exported recursor '{}' does not match the kernel-derived nested recursor",
757 d.common.name
758 )));
759 }
760 }
761 }
762
763 Ok(DerivedFamily {
764 inductives: renamed.inductives,
765 constructors: renamed.constructors,
766 recursors: installed,
767 })
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773 use crate::Node;
774
775 fn c(name: &str, levels: Vec<Level>) -> Expr {
776 Expr::Const(Name::str(name), levels)
777 }
778
779 fn app(f: Expr, a: Expr) -> Expr {
780 Expr::App(Node::new(f), Node::new(a))
781 }
782
783 #[test]
784 fn detects_list_of_family() {
785 let fam = [Name::str("T")];
787 let ty = app(c("List", vec![Level::zero()]), c("T", vec![]));
788 let occ = detect(&fam, &ty).expect("nested occurrence");
789 assert_eq!(occ.container, Name::str("List"));
790 assert_eq!(occ.container_levels, vec![Level::zero()]);
791 assert_eq!(occ.family_positions, vec![0]);
792 assert_eq!(occ.args.len(), 1);
793 }
794
795 #[test]
796 fn direct_occurrence_is_not_nested() {
797 let fam = [Name::str("T")];
799 let ty = app(c("T", vec![]), c("x", vec![]));
800 assert!(detect(&fam, &ty).is_none());
801 }
802
803 #[test]
804 fn foreign_container_without_family_is_not_nested() {
805 let fam = [Name::str("T")];
807 let ty = app(c("List", vec![Level::zero()]), c("Nat", vec![]));
808 assert!(detect(&fam, &ty).is_none());
809 }
810
811 #[test]
812 fn bare_const_is_not_nested() {
813 let fam = [Name::str("T")];
814 assert!(detect(&fam, &c("Nat", vec![])).is_none());
815 }
816
817 #[test]
818 fn family_in_later_argument_position() {
819 let fam = [Name::str("T")];
821 let ty = app(
822 app(
823 c("Prod", vec![Level::zero(), Level::zero()]),
824 c("Nat", vec![]),
825 ),
826 c("T", vec![]),
827 );
828 let occ = detect(&fam, &ty).expect("nested occurrence");
829 assert_eq!(occ.container, Name::str("Prod"));
830 assert_eq!(occ.family_positions, vec![1]);
831 assert_eq!(occ.args.len(), 2);
832 }
833
834 #[test]
835 fn family_nested_deeper_in_argument() {
836 let fam = [Name::str("T")];
838 let inner = app(c("List", vec![Level::zero()]), c("T", vec![]));
839 let ty = app(c("Array", vec![Level::zero()]), inner);
840 let occ = detect(&fam, &ty).expect("nested occurrence");
841 assert_eq!(occ.container, Name::str("Array"));
842 assert_eq!(occ.family_positions, vec![0]);
843 }
844
845 #[test]
846 fn multiple_family_members() {
847 let fam = [Name::str("T"), Name::str("U")];
849 let ty = app(
850 app(c("Sum", vec![Level::zero(), Level::zero()]), c("T", vec![])),
851 c("U", vec![]),
852 );
853 let occ = detect(&fam, &ty).expect("nested occurrence");
854 assert_eq!(occ.family_positions, vec![0, 1]);
855 }
856
857 #[test]
860 fn non_nested_family_uses_ordinary_path() {
861 let env = Environment::new();
863 let spec = InductiveSpec::new(
864 Name::str("Foo"),
865 Expr::Sort(Level::succ(Level::zero())),
866 vec![(Name::str("Foo.mk"), c("Foo", vec![]))],
867 );
868 let (fam, exp) =
869 check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("ordinary derivation");
870 assert!(exp.is_none(), "non-nested family must not expand");
871 assert_eq!(fam.recursors.len(), 1);
872 }
873
874 #[test]
875 fn nested_t_derives_via_specialization() {
876 let mut env = Environment::new();
878 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
879 let t = c("NestedT", vec![]);
880 let list_t = app(c("List", vec![Level::zero()]), t.clone());
881 let spec = InductiveSpec::new(
882 Name::str("NestedT"),
883 Expr::Sort(Level::succ(Level::zero())),
884 vec![(
885 Name::str("NestedT.mk"),
886 Expr::Pi(
887 crate::BinderInfo::Default,
888 Name::str("l"),
889 Node::new(list_t),
890 Node::new(t),
891 ),
892 )],
893 );
894
895 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec])
896 .expect("nested inductive now derives");
897 let exp = exp.expect("NestedT must be recognised as nested");
898
899 assert_eq!(exp.num_originals, 1);
901 assert_eq!(exp.specs.len(), 2);
902 assert_eq!(exp.aux_maps.len(), 1);
903 let m = &exp.aux_maps[0];
904 assert_eq!(m.container, Name::str("List"));
905 let real_ctors: Vec<Name> = m.aux_ctors.iter().map(|(_, r)| r.clone()).collect();
906 assert!(real_ctors.contains(&Name::str("List.nil")));
907 assert!(real_ctors.contains(&Name::str("List.cons")));
908
909 assert_eq!(fam.inductives.len(), 2);
911 assert_eq!(fam.constructors.len(), 3);
912 assert_eq!(fam.recursors.len(), 2);
913
914 let nestedt_rec = fam
917 .recursors
918 .iter()
919 .find_map(|ci| match ci {
920 ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("NestedT.mk")).is_some() => {
921 Some(rv)
922 }
923 _ => None,
924 })
925 .expect("a recursor eliminating NestedT.mk");
926 assert_eq!(nestedt_rec.num_motives, 2);
927 assert_eq!(nestedt_rec.num_minors, 3);
928 }
929
930 #[test]
931 fn nested_t_aux_recursor_iota_reduces() {
932 let mut env = crate::Environment::new();
935 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
936 let t = c("NestedT", vec![]);
937 let list_t = app(c("List", vec![Level::zero()]), t.clone());
938 let spec = InductiveSpec::new(
939 Name::str("NestedT"),
940 Expr::Sort(Level::succ(Level::zero())),
941 vec![(
942 Name::str("NestedT.mk"),
943 Expr::Pi(
944 crate::BinderInfo::Default,
945 Name::str("l"),
946 Node::new(list_t),
947 Node::new(t),
948 ),
949 )],
950 );
951 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
952 let exp = exp.expect("nested");
953
954 let mut env2 = env.clone();
957 for ci in fam.clone().into_constant_infos() {
958 env2.add_constant(ci).expect("install expanded family");
959 }
960
961 let aux = &exp.aux_maps[0];
962 let aux_nil = aux
963 .aux_ctors
964 .iter()
965 .find(|(_, r)| *r == Name::str("List.nil"))
966 .expect("aux nil ctor")
967 .0
968 .clone();
969
970 let ph = |s: &str| Expr::Const(Name::str(s), vec![]);
973 let e = mk_app(
974 Expr::Const(aux.aux_rec.clone(), vec![Level::zero()]),
975 &[
976 ph("mN"),
977 ph("mL"),
978 ph("minMk"),
979 ph("minNil"),
980 ph("minCons"),
981 Expr::Const(aux_nil, vec![]),
982 ],
983 );
984
985 let reduced = crate::TypeChecker::new(&env2).whnf(&e);
986 let head = get_app_fn_args(&reduced).0.clone();
987 assert_eq!(
988 head,
989 Expr::Const(Name::str("minNil"), vec![]),
990 "aux recursor did not iota-reduce to the nil minor: {:?}",
991 reduced
992 );
993 }
994
995 #[test]
996 fn reach_through_double_nesting_expands_to_three() {
997 let mut env = crate::Environment::new();
1002 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
1003 let two = c("Two", vec![]);
1004 let list = |x: Expr| app(c("List", vec![Level::zero()]), x);
1005 let spec = InductiveSpec::new(
1006 Name::str("Two"),
1007 Expr::Sort(Level::succ(Level::zero())),
1008 vec![(
1009 Name::str("Two.mk"),
1010 Expr::Pi(
1011 crate::BinderInfo::Default,
1012 Name::str("l"),
1013 Node::new(list(list(two.clone()))),
1014 Node::new(two),
1015 ),
1016 )],
1017 );
1018 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
1019 let exp = exp.expect("nested");
1020 assert_eq!(exp.num_originals, 1);
1021 assert_eq!(exp.specs.len(), 3, "Two + two List auxiliaries");
1022 assert_eq!(exp.aux_maps.len(), 2);
1023 let rec = fam
1026 .recursors
1027 .iter()
1028 .find_map(|ci| match ci {
1029 ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("Two.mk")).is_some() => {
1030 Some(rv)
1031 }
1032 _ => None,
1033 })
1034 .expect("Two's recursor");
1035 assert_eq!(rec.num_motives, 3);
1036 assert_eq!(rec.num_minors, 5);
1037 }
1038
1039 #[test]
1042 fn nested_t_restore_produces_real_list_recursor() {
1043 use crate::expr_util::collect_consts;
1044 let mut env = crate::Environment::new();
1045 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
1046 let t = c("NestedT", vec![]);
1047 let list_t = app(c("List", vec![Level::zero()]), t.clone());
1048 let spec = InductiveSpec::new(
1049 Name::str("NestedT"),
1050 Expr::Sort(Level::succ(Level::zero())),
1051 vec![(
1052 Name::str("NestedT.mk"),
1053 Expr::Pi(
1054 crate::BinderInfo::Default,
1055 Name::str("l"),
1056 Node::new(list_t),
1057 Node::new(t.clone()),
1058 ),
1059 )],
1060 );
1061 let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
1062 let exp = exp.expect("nested");
1063 let aux_type = exp.aux_maps[0].aux_type.clone();
1064 let aux_rec = exp.aux_maps[0].aux_rec.clone();
1065 let restored = restore(fam, &exp);
1066
1067 assert_eq!(restored.inductives.len(), 1);
1069 assert_eq!(restored.constructors.len(), 1);
1070 assert_eq!(restored.recursors.len(), 2);
1071
1072 match &restored.inductives[0] {
1073 ConstantInfo::Inductive(iv) => {
1074 assert_eq!(iv.num_nested, 1);
1075 assert_eq!(iv.all, vec![Name::str("NestedT")]);
1076 }
1077 _ => panic!("expected inductive"),
1078 }
1079
1080 match &restored.constructors[0] {
1082 ConstantInfo::Constructor(cv) => {
1083 let consts = collect_consts(&cv.common.ty);
1084 assert!(consts.contains(&Name::str("List")));
1085 assert!(!consts.contains(&aux_type));
1086 }
1087 _ => panic!("expected constructor"),
1088 }
1089
1090 let mut env2 = env.clone();
1093 for ci in restored.clone().into_constant_infos() {
1094 env2.add_constant(ci).expect("install restored family");
1095 }
1096 for ci in &restored.recursors {
1097 if let ConstantInfo::Recursor(rv) = ci {
1098 crate::TypeChecker::new(&env2)
1099 .infer_type(&rv.common.ty)
1100 .expect("restored recursor type re-checks");
1101 }
1102 }
1103
1104 let ph = |s: &str| Expr::Const(Name::str(s), vec![]);
1105 let nil_app = mk_app(
1106 Expr::Const(Name::str("List.nil"), vec![Level::zero()]),
1107 std::slice::from_ref(&t),
1108 );
1109
1110 let e_aux = mk_app(
1112 Expr::Const(aux_rec, vec![Level::zero()]),
1113 &[
1114 ph("mN"),
1115 ph("mL"),
1116 ph("minMk"),
1117 ph("minNil"),
1118 ph("minCons"),
1119 nil_app.clone(),
1120 ],
1121 );
1122 let r_aux = crate::TypeChecker::new(&env2).whnf(&e_aux);
1123 assert_eq!(
1124 get_app_fn_args(&r_aux).0.clone(),
1125 Expr::Const(Name::str("minNil"), vec![]),
1126 "restored aux recursor did not fire on real List.nil: {:?}",
1127 r_aux
1128 );
1129
1130 let main_rec = restored
1132 .recursors
1133 .iter()
1134 .find_map(|ci| match ci {
1135 ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("NestedT.mk")).is_some() => {
1136 Some(rv.common.name.clone())
1137 }
1138 _ => None,
1139 })
1140 .expect("NestedT.rec");
1141 let major = app(c("NestedT.mk", vec![]), nil_app);
1142 let e_main = mk_app(
1143 Expr::Const(main_rec, vec![Level::zero()]),
1144 &[
1145 ph("mN"),
1146 ph("mL"),
1147 ph("minMk"),
1148 ph("minNil"),
1149 ph("minCons"),
1150 major,
1151 ],
1152 );
1153 let r_main = crate::TypeChecker::new(&env2).whnf(&e_main);
1154 assert_eq!(
1155 get_app_fn_args(&r_main).0.clone(),
1156 Expr::Const(Name::str("minMk"), vec![]),
1157 "restored NestedT.rec did not fire on NestedT.mk: {:?}",
1158 r_main
1159 );
1160 }
1161
1162 fn nested_t_bundle() -> (crate::Environment, InductiveSpec, Vec<RecursorVal>) {
1167 let mut env = crate::Environment::new();
1168 crate::builtin::init_builtin_env(&mut env).expect("builtin env");
1169 let t = c("NestedT", vec![]);
1170 let list_t = app(c("List", vec![Level::zero()]), t.clone());
1171 let spec = InductiveSpec::new(
1172 Name::str("NestedT"),
1173 Expr::Sort(Level::succ(Level::zero())),
1174 vec![(
1175 Name::str("NestedT.mk"),
1176 Expr::Pi(
1177 crate::BinderInfo::Default,
1178 Name::str("l"),
1179 Node::new(list_t),
1180 Node::new(t),
1181 ),
1182 )],
1183 );
1184 let (raw, exp) = check_and_derive_family_nested(&env, &[], 0, std::slice::from_ref(&spec))
1185 .expect("derive");
1186 let exp = exp.expect("nested");
1187 let restored = restore(raw, &exp);
1188 let name_map: Vec<(Name, Name)> = restored
1190 .recursors
1191 .iter()
1192 .filter_map(|ci| match ci {
1193 ConstantInfo::Recursor(rv) => {
1194 let on_mk = rv.rules.iter().any(|r| r.ctor == Name::str("NestedT.mk"));
1195 let exported = if on_mk {
1196 Name::mk_str(Name::str("NestedT"), "rec".to_string())
1197 } else {
1198 Name::mk_str(Name::str("NestedT"), "rec_1".to_string())
1199 };
1200 Some((rv.common.name.clone(), exported))
1201 }
1202 _ => None,
1203 })
1204 .collect();
1205 let canonical = rename_recursors(restored, &name_map);
1206 let exported_recs: Vec<RecursorVal> = canonical
1207 .recursors
1208 .iter()
1209 .filter_map(|ci| match ci {
1210 ConstantInfo::Recursor(rv) => Some(rv.clone()),
1211 _ => None,
1212 })
1213 .collect();
1214 (env, spec, exported_recs)
1215 }
1216
1217 #[test]
1218 fn verify_nested_bundle_accepts_matching_export() {
1219 let (env, spec, exported_recs) = nested_t_bundle();
1220 let out = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
1221 .expect("matching export verifies");
1222 let names: Vec<Name> = out
1224 .recursors
1225 .iter()
1226 .filter_map(|ci| match ci {
1227 ConstantInfo::Recursor(rv) => Some(rv.common.name.clone()),
1228 _ => None,
1229 })
1230 .collect();
1231 assert!(names.contains(&Name::mk_str(Name::str("NestedT"), "rec".to_string())));
1232 assert!(names.contains(&Name::mk_str(Name::str("NestedT"), "rec_1".to_string())));
1233 }
1234
1235 #[test]
1236 fn verify_nested_bundle_rejects_corrupted_recursor() {
1237 let (env, spec, mut exported_recs) = nested_t_bundle();
1238 exported_recs[0].num_minors += 1;
1240 let err = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
1241 .expect_err("corrupted export must be rejected");
1242 assert!(matches!(err, KernelError::InvalidRecursor(_)));
1243 }
1244
1245 #[test]
1246 fn verify_nested_bundle_rejects_wrong_recursor_count() {
1247 let (env, spec, mut exported_recs) = nested_t_bundle();
1248 exported_recs.pop();
1249 let err = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
1250 .expect_err("wrong recursor count must be rejected");
1251 assert!(matches!(err, KernelError::InvalidRecursor(_)));
1252 }
1253}