1use crate::context::Context;
29use crate::error::{KernelError, KernelResult};
30use crate::infer_type;
31use crate::term::{Term, Universe};
32
33pub fn derive_recursor(ctx: &Context, ind: &str) -> KernelResult<(Term, Term)> {
37 if let Some(block) = ctx.mutual_block_of(ind) {
39 let block = block.to_vec();
40 return derive_mutual_recursor(ctx, ind, &block);
41 }
42
43 let ind_full_ty = ctx
44 .get_global(ind)
45 .ok_or_else(|| KernelError::UnboundVariable(ind.to_string()))?
46 .clone();
47 if !ctx.is_inductive(ind) {
48 return Err(KernelError::UnboundVariable(ind.to_string()));
49 }
50
51 let (arity_tele, ind_sort) = peel_pis(&ind_full_ty);
57 let num_params = ctx.inductive_num_params(ind).min(arity_tele.len());
58
59 let motive_target = match ind_sort {
65 Term::Sort(Universe::Prop)
66 if !crate::type_checker::is_subsingleton_prop(ctx, ind).unwrap_or(false) =>
67 {
68 Term::Sort(Universe::Prop)
69 }
70 _ => Term::Sort(Universe::Type(0)),
71 };
72 let param_tele: Vec<(String, Term)> = arity_tele[..num_params].to_vec();
73 let index_tele: Vec<(String, Term)> = arity_tele[num_params..].to_vec();
74 let k = index_tele.len();
75
76 let tp_names: Vec<String> = (0..num_params).map(|i| format!("A{i}")).collect();
77 let idx_names: Vec<String> = (0..k).map(|i| format!("Idx{i}")).collect();
78
79 let param_types: Vec<Term> = (0..num_params)
81 .map(|i| rename_tele_types(¶m_tele[i].1, ¶m_tele[..i], &tp_names))
82 .collect();
83 let index_types = |names: &[String]| -> Vec<Term> {
86 (0..k)
87 .map(|j| {
88 let with_params = rename_tele_types(&index_tele[j].1, ¶m_tele, &tp_names);
89 rename_index_refs(&with_params, &index_tele[..j], names)
90 })
91 .collect::<Vec<_>>()
92 };
93
94 let mut ind_params = Term::Global(ind.to_string());
96 for name in &tp_names {
97 ind_params = app(ind_params, var(name));
98 }
99
100 let idx_types_motive = index_types(&idx_names);
103 let mut ind_at_idx = ind_params.clone();
104 for name in &idx_names {
105 ind_at_idx = app(ind_at_idx, var(name));
106 }
107 let mut motive_ty = arrow(ind_at_idx.clone(), motive_target);
108 for j in (0..k).rev() {
109 motive_ty = pi(&idx_names[j], idx_types_motive[j].clone(), motive_ty);
110 }
111
112 let ctors: Vec<(String, Term)> = ctx
113 .get_constructors(ind)
114 .iter()
115 .map(|(n, t)| (n.to_string(), (*t).clone()))
116 .collect();
117
118 let mut minors: Vec<(String, Term)> = Vec::with_capacity(ctors.len());
119 let mut cases: Vec<Term> = Vec::with_capacity(ctors.len());
120 let block_single = [ind];
123 let motive_single = ["P".to_string()];
124 let rec_single = ["rec".to_string()];
125 for (i, (cname, ctype)) in ctors.iter().enumerate() {
126 let an = constructor_analysis(ctype, ind, &block_single, num_params, &tp_names)?;
127 minors.push((format!("f{i}"), minor_type("P", &motive_single, cname, &tp_names, &an)));
128 cases.push(case_term(cname, &tp_names, &an, &format!("f{i}"), &rec_single));
129 }
130
131 let mj_names: Vec<String> = (0..k).map(|i| format!("mj{i}")).collect();
135 let idx_types_match = index_types(&mj_names);
136 let mut ind_at_mj = ind_params.clone();
137 for name in &mj_names {
138 ind_at_mj = app(ind_at_mj, var(name));
139 }
140 let mut motive_body = var("P");
141 for name in &mj_names {
142 motive_body = app(motive_body, var(name));
143 }
144 motive_body = app(motive_body, var("x"));
145 let mut motive_fn = lam("x", ind_at_mj, motive_body);
146 for j in (0..k).rev() {
147 motive_fn = lam(&mj_names[j], idx_types_match[j].clone(), motive_fn);
148 }
149
150 let match_term = Term::Match {
152 discriminant: Box::new(var("x")),
153 motive: Box::new(motive_fn),
154 cases,
155 };
156 let mut fix_body = lam("x", ind_at_idx.clone(), match_term);
157 for j in (0..k).rev() {
158 fix_body = lam(&idx_names[j], idx_types_motive[j].clone(), fix_body);
159 }
160 let mut term = Term::Fix { name: "rec".to_string(), body: Box::new(fix_body) };
161
162 for (fname, fty) in minors.iter().rev() {
164 term = lam(fname, fty.clone(), term);
165 }
166 term = lam("P", motive_ty, term);
167 for i in (0..num_params).rev() {
168 term = lam(&tp_names[i], param_types[i].clone(), term);
169 }
170
171 let ty = infer_type(ctx, &term)?;
174 Ok((ty, term))
175}
176
177fn derive_mutual_recursor(ctx: &Context, ind: &str, block: &[String]) -> KernelResult<(Term, Term)> {
186 let m = block.len();
187 let bi = block.iter().position(|n| n == ind).ok_or_else(|| {
188 KernelError::CertificationError(format!("'{ind}' is not in its own mutual block"))
189 })?;
190 let block_refs: Vec<&str> = block.iter().map(|s| s.as_str()).collect();
191
192 let block0_ty = ctx
195 .get_global(&block[0])
196 .ok_or_else(|| KernelError::UnboundVariable(block[0].clone()))?
197 .clone();
198 let (arity0, _) = peel_pis(&block0_ty);
199 let num_params = ctx.inductive_num_params(&block[0]).min(arity0.len());
200 for name in block {
201 let full = ctx.get_global(name).ok_or_else(|| KernelError::UnboundVariable(name.clone()))?;
202 if ctx.inductive_num_params(name).min(peel_pis(full).0.len()) != num_params {
203 return Err(KernelError::CertificationError(format!(
204 "auto-recursor: mutual block members disagree on parameter count ('{name}')"
205 )));
206 }
207 }
208 let param_tele: Vec<(String, Term)> = arity0[..num_params].to_vec();
209 let tp_names: Vec<String> = (0..num_params).map(|i| format!("A{i}")).collect();
210 let param_types: Vec<Term> = (0..num_params)
211 .map(|i| rename_tele_types(¶m_tele[i].1, ¶m_tele[..i], &tp_names))
212 .collect();
213
214 let motive_names: Vec<String> = (0..m).map(|k| format!("P{k}")).collect();
215 let rec_names: Vec<String> = (0..m).map(|k| format!("rec{k}")).collect();
216
217 let ind_at_params = |name: &str| {
219 tp_names.iter().fold(Term::Global(name.to_string()), |acc, n| app(acc, var(n)))
220 };
221
222 struct MemberInfo {
224 idx_names: Vec<String>,
225 idx_types: Vec<Term>,
226 ind_at_idx: Term,
227 motive_ty: Term,
228 }
229 let mut infos: Vec<MemberInfo> = Vec::with_capacity(m);
230 for (k, name) in block.iter().enumerate() {
231 let full_ty = ctx
232 .get_global(name)
233 .ok_or_else(|| KernelError::UnboundVariable(name.clone()))?
234 .clone();
235 let (arity_tele, ind_sort) = peel_pis(&full_ty);
236 let index_tele: Vec<(String, Term)> = arity_tele[num_params..].to_vec();
237 let target = match ind_sort {
238 Term::Sort(Universe::Prop)
239 if !crate::type_checker::is_subsingleton_prop(ctx, name).unwrap_or(false) =>
240 {
241 Term::Sort(Universe::Prop)
242 }
243 _ => Term::Sort(Universe::Type(0)),
244 };
245 let idx_names: Vec<String> = (0..index_tele.len()).map(|j| format!("Idx{k}_{j}")).collect();
246 let idx_types: Vec<Term> = (0..index_tele.len())
248 .map(|j| {
249 let with_params = rename_tele_types(&index_tele[j].1, ¶m_tele, &tp_names);
250 rename_index_refs(&with_params, &index_tele[..j], &idx_names)
251 })
252 .collect();
253 let mut ind_at_idx = ind_at_params(name);
255 for n in &idx_names {
256 ind_at_idx = app(ind_at_idx, var(n));
257 }
258 let mut motive_ty = arrow(ind_at_idx.clone(), target);
260 for j in (0..idx_names.len()).rev() {
261 motive_ty = pi(&idx_names[j], idx_types[j].clone(), motive_ty);
262 }
263 infos.push(MemberInfo { idx_names, idx_types, ind_at_idx, motive_ty });
264 }
265
266 let mut minors: Vec<(String, Term)> = Vec::new();
269 let mut member_cases: Vec<Vec<Term>> = Vec::with_capacity(m);
270 for (k, name) in block.iter().enumerate() {
271 let ctors: Vec<(String, Term)> = ctx
272 .get_constructors(name)
273 .iter()
274 .map(|(n, t)| (n.to_string(), (*t).clone()))
275 .collect();
276 let mut cases = Vec::with_capacity(ctors.len());
277 for (cname, ctype) in &ctors {
278 let an = constructor_analysis(ctype, name, &block_refs, num_params, &tp_names)?;
279 let fi = format!("f{}", minors.len());
280 minors.push((
281 fi.clone(),
282 minor_type(&motive_names[k], &motive_names, cname, &tp_names, &an),
283 ));
284 cases.push(case_term(cname, &tp_names, &an, &fi, &rec_names));
285 }
286 member_cases.push(cases);
287 }
288
289 let mut defs: Vec<(String, Term)> = Vec::with_capacity(m);
291 for (k, name) in block.iter().enumerate() {
292 let info = &infos[k];
293 let mj_names: Vec<String> = (0..info.idx_names.len()).map(|j| format!("mj{k}_{j}")).collect();
295 let mut motive_body = var(&motive_names[k]);
296 for n in &mj_names {
297 motive_body = app(motive_body, var(n));
298 }
299 motive_body = app(motive_body, var("x"));
300 let mut ind_at_mj = ind_at_params(name);
301 for n in &mj_names {
302 ind_at_mj = app(ind_at_mj, var(n));
303 }
304 let mut motive_fn = lam("x", ind_at_mj, motive_body);
305 for j in (0..mj_names.len()).rev() {
306 let mut mj_ty = info.idx_types[j].clone();
308 for i in 0..j {
309 mj_ty = subst_name(&mj_ty, &info.idx_names[i], &var(&mj_names[i]));
310 }
311 motive_fn = lam(&mj_names[j], mj_ty, motive_fn);
312 }
313 let match_term = Term::Match {
314 discriminant: Box::new(var("x")),
315 motive: Box::new(motive_fn),
316 cases: member_cases[k].clone(),
317 };
318 let mut fix_body = lam("x", info.ind_at_idx.clone(), match_term);
319 for j in (0..info.idx_names.len()).rev() {
320 fix_body = lam(&info.idx_names[j], info.idx_types[j].clone(), fix_body);
321 }
322 defs.push((rec_names[k].clone(), fix_body));
323 }
324
325 let mut term = Term::MutualFix { defs, index: bi };
327 for (fname, fty) in minors.iter().rev() {
328 term = lam(fname, fty.clone(), term);
329 }
330 for k in (0..m).rev() {
331 term = lam(&motive_names[k], infos[k].motive_ty.clone(), term);
332 }
333 for i in (0..num_params).rev() {
334 term = lam(&tp_names[i], param_types[i].clone(), term);
335 }
336
337 let ty = infer_type(ctx, &term)?;
338 Ok((ty, term))
339}
340
341struct CtorAnalysis {
345 value_types: Vec<Term>,
346 recursive: Vec<Option<RecOcc>>,
351 result_indices: Vec<Term>,
353}
354
355struct RecOcc {
360 member: usize,
361 tele: Vec<(String, Term)>,
362 indices: Vec<Term>,
363}
364
365fn constructor_analysis(
373 ctor_type: &Term,
374 owner: &str,
375 block: &[&str],
376 num_params: usize,
377 tp_names: &[String],
378) -> KernelResult<CtorAnalysis> {
379 let (params, residual) = peel_pis(ctor_type);
380 if params.len() < num_params {
381 return Err(KernelError::CertificationError(format!(
382 "auto-recursor: constructor of '{}' has fewer parameters than the inductive",
383 owner
384 )));
385 }
386 if head_global(residual) != Some(owner) {
387 return Err(KernelError::CertificationError(format!(
388 "auto-recursor: constructor result {} is not the inductive '{}'",
389 residual, owner
390 )));
391 }
392 let (type_params, value_params) = params.split_at(num_params);
393
394 let rewrite = |t: &Term, upto: usize| -> Term {
397 let mut out = t.clone();
398 for (i, (tp_orig, _)) in type_params.iter().enumerate() {
399 if tp_orig != "_" {
400 out = subst_name(&out, tp_orig, &var(&tp_names[i]));
401 }
402 }
403 for (kk, (vk_orig, _)) in value_params.iter().enumerate().take(upto) {
404 if vk_orig != "_" {
405 out = subst_name(&out, vk_orig, &var(&format!("a{kk}")));
406 }
407 }
408 out
409 };
410
411 let mut value_types = Vec::with_capacity(value_params.len());
412 let mut recursive = Vec::with_capacity(value_params.len());
413 for (j, (_, ty)) in value_params.iter().enumerate() {
414 let t = rewrite(ty, j);
415 let (tele, occ) = peel_pis(&t);
420 if let Some(member) = head_global(occ).and_then(|h| block.iter().position(|m| *m == h)) {
423 let indices: Vec<Term> = app_args(occ).into_iter().skip(num_params).collect();
424 recursive.push(Some(RecOcc { member, tele, indices }));
425 } else {
426 recursive.push(None);
427 }
428 value_types.push(t);
429 }
430
431 let result_indices: Vec<Term> = app_args(residual)
433 .into_iter()
434 .skip(num_params)
435 .map(|e| rewrite(&e, value_params.len()))
436 .collect();
437
438 Ok(CtorAnalysis { value_types, recursive, result_indices })
439}
440
441fn minor_type(
445 owner_motive: &str,
446 motive_names: &[String],
447 cname: &str,
448 tp_names: &[String],
449 an: &CtorAnalysis,
450) -> Term {
451 let mut ctor_applied = Term::Global(cname.to_string());
452 for name in tp_names {
453 ctor_applied = app(ctor_applied, var(name));
454 }
455 for j in 0..an.value_types.len() {
456 ctor_applied = app(ctor_applied, var(&format!("a{j}")));
457 }
458 let mut body = var(owner_motive);
460 for e in &an.result_indices {
461 body = app(body, e.clone());
462 }
463 body = app(body, ctor_applied);
464 for j in (0..an.value_types.len()).rev() {
469 if let Some(occ) = &an.recursive[j] {
470 let mut ih = var(&motive_names[occ.member]);
471 for e in &occ.indices {
472 ih = app(ih, e.clone());
473 }
474 let mut aj = var(&format!("a{j}"));
475 for (tn, _) in &occ.tele {
476 aj = app(aj, var(tn));
477 }
478 ih = app(ih, aj);
479 for (tn, tt) in occ.tele.iter().rev() {
480 ih = pi(tn, tt.clone(), ih);
481 }
482 body = pi(&format!("ih{j}"), ih, body);
483 }
484 }
485 for j in (0..an.value_types.len()).rev() {
486 body = pi(&format!("a{j}"), an.value_types[j].clone(), body);
487 }
488 body
489}
490
491fn case_term(
496 cname: &str,
497 _tp_names: &[String],
498 an: &CtorAnalysis,
499 fi: &str,
500 rec_names: &[String],
501) -> Term {
502 let _ = cname;
503 let mut body = var(fi);
504 for j in 0..an.value_types.len() {
505 body = app(body, var(&format!("a{j}")));
506 }
507 for j in 0..an.value_types.len() {
508 if let Some(occ) = &an.recursive[j] {
509 let mut call = var(&rec_names[occ.member]);
516 for e in &occ.indices {
517 call = app(call, e.clone());
518 }
519 let mut aj = var(&format!("a{j}"));
520 for (tn, _) in &occ.tele {
521 aj = app(aj, var(tn));
522 }
523 call = app(call, aj);
524 for (tn, tt) in occ.tele.iter().rev() {
525 call = lam(tn, tt.clone(), call);
526 }
527 body = app(body, call);
528 }
529 }
530 for j in (0..an.value_types.len()).rev() {
531 body = lam(&format!("a{j}"), an.value_types[j].clone(), body);
532 }
533 body
534}
535
536fn rename_tele_types(ty: &Term, tele: &[(String, Term)], names: &[String]) -> Term {
539 let mut out = ty.clone();
540 for (i, (orig, _)) in tele.iter().enumerate() {
541 if orig != "_" {
542 out = subst_name(&out, orig, &var(&names[i]));
543 }
544 }
545 out
546}
547
548fn rename_index_refs(ty: &Term, tele: &[(String, Term)], names: &[String]) -> Term {
551 rename_tele_types(ty, tele, names)
552}
553
554fn app_args(t: &Term) -> Vec<Term> {
556 let mut args = Vec::new();
557 let mut cur = t;
558 while let Term::App(f, a) = cur {
559 args.push((**a).clone());
560 cur = f;
561 }
562 args.reverse();
563 args
564}
565
566fn peel_pis(t: &Term) -> (Vec<(String, Term)>, &Term) {
568 let mut params = Vec::new();
569 let mut cur = t;
570 while let Term::Pi { param, param_type, body_type } = cur {
571 params.push((param.clone(), (**param_type).clone()));
572 cur = body_type;
573 }
574 (params, cur)
575}
576
577fn head_global(t: &Term) -> Option<&str> {
579 let mut cur = t;
580 while let Term::App(f, _) = cur {
581 cur = f;
582 }
583 match cur {
584 Term::Global(n) => Some(n),
585 _ => None,
586 }
587}
588
589fn subst_name(t: &Term, old: &str, repl: &Term) -> Term {
591 match t {
592 Term::Var(n) if n == old => repl.clone(),
593 Term::Var(_) | Term::Global(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole
594 | Term::Const { .. } => t.clone(),
595 Term::Pi { param, param_type, body_type } => Term::Pi {
596 param: param.clone(),
597 param_type: Box::new(subst_name(param_type, old, repl)),
598 body_type: if param == old {
599 body_type.clone()
600 } else {
601 Box::new(subst_name(body_type, old, repl))
602 },
603 },
604 Term::Lambda { param, param_type, body } => Term::Lambda {
605 param: param.clone(),
606 param_type: Box::new(subst_name(param_type, old, repl)),
607 body: if param == old { body.clone() } else { Box::new(subst_name(body, old, repl)) },
608 },
609 Term::App(f, a) => {
610 Term::App(Box::new(subst_name(f, old, repl)), Box::new(subst_name(a, old, repl)))
611 }
612 Term::Match { discriminant, motive, cases } => Term::Match {
613 discriminant: Box::new(subst_name(discriminant, old, repl)),
614 motive: Box::new(subst_name(motive, old, repl)),
615 cases: cases.iter().map(|c| subst_name(c, old, repl)).collect(),
616 },
617 Term::Fix { name, body } => Term::Fix {
618 name: name.clone(),
619 body: if name == old { body.clone() } else { Box::new(subst_name(body, old, repl)) },
620 },
621 Term::MutualFix { defs, index } => {
622 if defs.iter().any(|(n, _)| n == old) {
624 t.clone()
625 } else {
626 Term::MutualFix {
627 defs: defs.iter().map(|(n, b)| (n.clone(), subst_name(b, old, repl))).collect(),
628 index: *index,
629 }
630 }
631 }
632 Term::Let { name, ty, value, body } => Term::Let {
633 name: name.clone(),
634 ty: Box::new(subst_name(ty, old, repl)),
635 value: Box::new(subst_name(value, old, repl)),
636 body: if name == old { body.clone() } else { Box::new(subst_name(body, old, repl)) },
637 },
638 }
639}
640
641fn var(n: &str) -> Term {
643 Term::Var(n.to_string())
644}
645fn app(f: Term, x: Term) -> Term {
646 Term::App(Box::new(f), Box::new(x))
647}
648fn lam(p: &str, ty: Term, body: Term) -> Term {
649 Term::Lambda { param: p.to_string(), param_type: Box::new(ty), body: Box::new(body) }
650}
651fn pi(p: &str, ty: Term, body: Term) -> Term {
652 Term::Pi { param: p.to_string(), param_type: Box::new(ty), body_type: Box::new(body) }
653}
654fn arrow(a: Term, b: Term) -> Term {
655 pi("_", a, b)
656}