use crate::context::Context;
use crate::error::{KernelError, KernelResult};
use crate::infer_type;
use crate::term::{Term, Universe};
pub fn derive_recursor(ctx: &Context, ind: &str) -> KernelResult<(Term, Term)> {
if let Some(block) = ctx.mutual_block_of(ind) {
let block = block.to_vec();
return derive_mutual_recursor(ctx, ind, &block);
}
let ind_full_ty = ctx
.get_global(ind)
.ok_or_else(|| KernelError::UnboundVariable(ind.to_string()))?
.clone();
if !ctx.is_inductive(ind) {
return Err(KernelError::UnboundVariable(ind.to_string()));
}
let (arity_tele, ind_sort) = peel_pis(&ind_full_ty);
let num_params = ctx.inductive_num_params(ind).min(arity_tele.len());
let motive_target = match ind_sort {
Term::Sort(Universe::Prop)
if !crate::type_checker::is_subsingleton_prop(ctx, ind).unwrap_or(false) =>
{
Term::Sort(Universe::Prop)
}
_ => Term::Sort(Universe::Type(0)),
};
let param_tele: Vec<(String, Term)> = arity_tele[..num_params].to_vec();
let index_tele: Vec<(String, Term)> = arity_tele[num_params..].to_vec();
let k = index_tele.len();
let tp_names: Vec<String> = (0..num_params).map(|i| format!("A{i}")).collect();
let idx_names: Vec<String> = (0..k).map(|i| format!("Idx{i}")).collect();
let param_types: Vec<Term> = (0..num_params)
.map(|i| rename_tele_types(¶m_tele[i].1, ¶m_tele[..i], &tp_names))
.collect();
let index_types = |names: &[String]| -> Vec<Term> {
(0..k)
.map(|j| {
let with_params = rename_tele_types(&index_tele[j].1, ¶m_tele, &tp_names);
rename_index_refs(&with_params, &index_tele[..j], names)
})
.collect::<Vec<_>>()
};
let mut ind_params = Term::Global(ind.to_string());
for name in &tp_names {
ind_params = app(ind_params, var(name));
}
let idx_types_motive = index_types(&idx_names);
let mut ind_at_idx = ind_params.clone();
for name in &idx_names {
ind_at_idx = app(ind_at_idx, var(name));
}
let mut motive_ty = arrow(ind_at_idx.clone(), motive_target);
for j in (0..k).rev() {
motive_ty = pi(&idx_names[j], idx_types_motive[j].clone(), motive_ty);
}
let ctors: Vec<(String, Term)> = ctx
.get_constructors(ind)
.iter()
.map(|(n, t)| (n.to_string(), (*t).clone()))
.collect();
let mut minors: Vec<(String, Term)> = Vec::with_capacity(ctors.len());
let mut cases: Vec<Term> = Vec::with_capacity(ctors.len());
let block_single = [ind];
let motive_single = ["P".to_string()];
let rec_single = ["rec".to_string()];
for (i, (cname, ctype)) in ctors.iter().enumerate() {
let an = constructor_analysis(ctype, ind, &block_single, num_params, &tp_names)?;
minors.push((format!("f{i}"), minor_type("P", &motive_single, cname, &tp_names, &an)));
cases.push(case_term(cname, &tp_names, &an, &format!("f{i}"), &rec_single));
}
let mj_names: Vec<String> = (0..k).map(|i| format!("mj{i}")).collect();
let idx_types_match = index_types(&mj_names);
let mut ind_at_mj = ind_params.clone();
for name in &mj_names {
ind_at_mj = app(ind_at_mj, var(name));
}
let mut motive_body = var("P");
for name in &mj_names {
motive_body = app(motive_body, var(name));
}
motive_body = app(motive_body, var("x"));
let mut motive_fn = lam("x", ind_at_mj, motive_body);
for j in (0..k).rev() {
motive_fn = lam(&mj_names[j], idx_types_match[j].clone(), motive_fn);
}
let match_term = Term::Match {
discriminant: Box::new(var("x")),
motive: Box::new(motive_fn),
cases,
};
let mut fix_body = lam("x", ind_at_idx.clone(), match_term);
for j in (0..k).rev() {
fix_body = lam(&idx_names[j], idx_types_motive[j].clone(), fix_body);
}
let mut term = Term::Fix { name: "rec".to_string(), body: Box::new(fix_body) };
for (fname, fty) in minors.iter().rev() {
term = lam(fname, fty.clone(), term);
}
term = lam("P", motive_ty, term);
for i in (0..num_params).rev() {
term = lam(&tp_names[i], param_types[i].clone(), term);
}
let ty = infer_type(ctx, &term)?;
Ok((ty, term))
}
fn derive_mutual_recursor(ctx: &Context, ind: &str, block: &[String]) -> KernelResult<(Term, Term)> {
let m = block.len();
let bi = block.iter().position(|n| n == ind).ok_or_else(|| {
KernelError::CertificationError(format!("'{ind}' is not in its own mutual block"))
})?;
let block_refs: Vec<&str> = block.iter().map(|s| s.as_str()).collect();
let block0_ty = ctx
.get_global(&block[0])
.ok_or_else(|| KernelError::UnboundVariable(block[0].clone()))?
.clone();
let (arity0, _) = peel_pis(&block0_ty);
let num_params = ctx.inductive_num_params(&block[0]).min(arity0.len());
for name in block {
let full = ctx.get_global(name).ok_or_else(|| KernelError::UnboundVariable(name.clone()))?;
if ctx.inductive_num_params(name).min(peel_pis(full).0.len()) != num_params {
return Err(KernelError::CertificationError(format!(
"auto-recursor: mutual block members disagree on parameter count ('{name}')"
)));
}
}
let param_tele: Vec<(String, Term)> = arity0[..num_params].to_vec();
let tp_names: Vec<String> = (0..num_params).map(|i| format!("A{i}")).collect();
let param_types: Vec<Term> = (0..num_params)
.map(|i| rename_tele_types(¶m_tele[i].1, ¶m_tele[..i], &tp_names))
.collect();
let motive_names: Vec<String> = (0..m).map(|k| format!("P{k}")).collect();
let rec_names: Vec<String> = (0..m).map(|k| format!("rec{k}")).collect();
let ind_at_params = |name: &str| {
tp_names.iter().fold(Term::Global(name.to_string()), |acc, n| app(acc, var(n)))
};
struct MemberInfo {
idx_names: Vec<String>,
idx_types: Vec<Term>,
ind_at_idx: Term,
motive_ty: Term,
}
let mut infos: Vec<MemberInfo> = Vec::with_capacity(m);
for (k, name) in block.iter().enumerate() {
let full_ty = ctx
.get_global(name)
.ok_or_else(|| KernelError::UnboundVariable(name.clone()))?
.clone();
let (arity_tele, ind_sort) = peel_pis(&full_ty);
let index_tele: Vec<(String, Term)> = arity_tele[num_params..].to_vec();
let target = match ind_sort {
Term::Sort(Universe::Prop)
if !crate::type_checker::is_subsingleton_prop(ctx, name).unwrap_or(false) =>
{
Term::Sort(Universe::Prop)
}
_ => Term::Sort(Universe::Type(0)),
};
let idx_names: Vec<String> = (0..index_tele.len()).map(|j| format!("Idx{k}_{j}")).collect();
let idx_types: Vec<Term> = (0..index_tele.len())
.map(|j| {
let with_params = rename_tele_types(&index_tele[j].1, ¶m_tele, &tp_names);
rename_index_refs(&with_params, &index_tele[..j], &idx_names)
})
.collect();
let mut ind_at_idx = ind_at_params(name);
for n in &idx_names {
ind_at_idx = app(ind_at_idx, var(n));
}
let mut motive_ty = arrow(ind_at_idx.clone(), target);
for j in (0..idx_names.len()).rev() {
motive_ty = pi(&idx_names[j], idx_types[j].clone(), motive_ty);
}
infos.push(MemberInfo { idx_names, idx_types, ind_at_idx, motive_ty });
}
let mut minors: Vec<(String, Term)> = Vec::new();
let mut member_cases: Vec<Vec<Term>> = Vec::with_capacity(m);
for (k, name) in block.iter().enumerate() {
let ctors: Vec<(String, Term)> = ctx
.get_constructors(name)
.iter()
.map(|(n, t)| (n.to_string(), (*t).clone()))
.collect();
let mut cases = Vec::with_capacity(ctors.len());
for (cname, ctype) in &ctors {
let an = constructor_analysis(ctype, name, &block_refs, num_params, &tp_names)?;
let fi = format!("f{}", minors.len());
minors.push((
fi.clone(),
minor_type(&motive_names[k], &motive_names, cname, &tp_names, &an),
));
cases.push(case_term(cname, &tp_names, &an, &fi, &rec_names));
}
member_cases.push(cases);
}
let mut defs: Vec<(String, Term)> = Vec::with_capacity(m);
for (k, name) in block.iter().enumerate() {
let info = &infos[k];
let mj_names: Vec<String> = (0..info.idx_names.len()).map(|j| format!("mj{k}_{j}")).collect();
let mut motive_body = var(&motive_names[k]);
for n in &mj_names {
motive_body = app(motive_body, var(n));
}
motive_body = app(motive_body, var("x"));
let mut ind_at_mj = ind_at_params(name);
for n in &mj_names {
ind_at_mj = app(ind_at_mj, var(n));
}
let mut motive_fn = lam("x", ind_at_mj, motive_body);
for j in (0..mj_names.len()).rev() {
let mut mj_ty = info.idx_types[j].clone();
for i in 0..j {
mj_ty = subst_name(&mj_ty, &info.idx_names[i], &var(&mj_names[i]));
}
motive_fn = lam(&mj_names[j], mj_ty, motive_fn);
}
let match_term = Term::Match {
discriminant: Box::new(var("x")),
motive: Box::new(motive_fn),
cases: member_cases[k].clone(),
};
let mut fix_body = lam("x", info.ind_at_idx.clone(), match_term);
for j in (0..info.idx_names.len()).rev() {
fix_body = lam(&info.idx_names[j], info.idx_types[j].clone(), fix_body);
}
defs.push((rec_names[k].clone(), fix_body));
}
let mut term = Term::MutualFix { defs, index: bi };
for (fname, fty) in minors.iter().rev() {
term = lam(fname, fty.clone(), term);
}
for k in (0..m).rev() {
term = lam(&motive_names[k], infos[k].motive_ty.clone(), term);
}
for i in (0..num_params).rev() {
term = lam(&tp_names[i], param_types[i].clone(), term);
}
let ty = infer_type(ctx, &term)?;
Ok((ty, term))
}
struct CtorAnalysis {
value_types: Vec<Term>,
recursive: Vec<Option<RecOcc>>,
result_indices: Vec<Term>,
}
struct RecOcc {
member: usize,
tele: Vec<(String, Term)>,
indices: Vec<Term>,
}
fn constructor_analysis(
ctor_type: &Term,
owner: &str,
block: &[&str],
num_params: usize,
tp_names: &[String],
) -> KernelResult<CtorAnalysis> {
let (params, residual) = peel_pis(ctor_type);
if params.len() < num_params {
return Err(KernelError::CertificationError(format!(
"auto-recursor: constructor of '{}' has fewer parameters than the inductive",
owner
)));
}
if head_global(residual) != Some(owner) {
return Err(KernelError::CertificationError(format!(
"auto-recursor: constructor result {} is not the inductive '{}'",
residual, owner
)));
}
let (type_params, value_params) = params.split_at(num_params);
let rewrite = |t: &Term, upto: usize| -> Term {
let mut out = t.clone();
for (i, (tp_orig, _)) in type_params.iter().enumerate() {
if tp_orig != "_" {
out = subst_name(&out, tp_orig, &var(&tp_names[i]));
}
}
for (kk, (vk_orig, _)) in value_params.iter().enumerate().take(upto) {
if vk_orig != "_" {
out = subst_name(&out, vk_orig, &var(&format!("a{kk}")));
}
}
out
};
let mut value_types = Vec::with_capacity(value_params.len());
let mut recursive = Vec::with_capacity(value_params.len());
for (j, (_, ty)) in value_params.iter().enumerate() {
let t = rewrite(ty, j);
let (tele, occ) = peel_pis(&t);
if let Some(member) = head_global(occ).and_then(|h| block.iter().position(|m| *m == h)) {
let indices: Vec<Term> = app_args(occ).into_iter().skip(num_params).collect();
recursive.push(Some(RecOcc { member, tele, indices }));
} else {
recursive.push(None);
}
value_types.push(t);
}
let result_indices: Vec<Term> = app_args(residual)
.into_iter()
.skip(num_params)
.map(|e| rewrite(&e, value_params.len()))
.collect();
Ok(CtorAnalysis { value_types, recursive, result_indices })
}
fn minor_type(
owner_motive: &str,
motive_names: &[String],
cname: &str,
tp_names: &[String],
an: &CtorAnalysis,
) -> Term {
let mut ctor_applied = Term::Global(cname.to_string());
for name in tp_names {
ctor_applied = app(ctor_applied, var(name));
}
for j in 0..an.value_types.len() {
ctor_applied = app(ctor_applied, var(&format!("a{j}")));
}
let mut body = var(owner_motive);
for e in &an.result_indices {
body = app(body, e.clone());
}
body = app(body, ctor_applied);
for j in (0..an.value_types.len()).rev() {
if let Some(occ) = &an.recursive[j] {
let mut ih = var(&motive_names[occ.member]);
for e in &occ.indices {
ih = app(ih, e.clone());
}
let mut aj = var(&format!("a{j}"));
for (tn, _) in &occ.tele {
aj = app(aj, var(tn));
}
ih = app(ih, aj);
for (tn, tt) in occ.tele.iter().rev() {
ih = pi(tn, tt.clone(), ih);
}
body = pi(&format!("ih{j}"), ih, body);
}
}
for j in (0..an.value_types.len()).rev() {
body = pi(&format!("a{j}"), an.value_types[j].clone(), body);
}
body
}
fn case_term(
cname: &str,
_tp_names: &[String],
an: &CtorAnalysis,
fi: &str,
rec_names: &[String],
) -> Term {
let _ = cname;
let mut body = var(fi);
for j in 0..an.value_types.len() {
body = app(body, var(&format!("a{j}")));
}
for j in 0..an.value_types.len() {
if let Some(occ) = &an.recursive[j] {
let mut call = var(&rec_names[occ.member]);
for e in &occ.indices {
call = app(call, e.clone());
}
let mut aj = var(&format!("a{j}"));
for (tn, _) in &occ.tele {
aj = app(aj, var(tn));
}
call = app(call, aj);
for (tn, tt) in occ.tele.iter().rev() {
call = lam(tn, tt.clone(), call);
}
body = app(body, call);
}
}
for j in (0..an.value_types.len()).rev() {
body = lam(&format!("a{j}"), an.value_types[j].clone(), body);
}
body
}
fn rename_tele_types(ty: &Term, tele: &[(String, Term)], names: &[String]) -> Term {
let mut out = ty.clone();
for (i, (orig, _)) in tele.iter().enumerate() {
if orig != "_" {
out = subst_name(&out, orig, &var(&names[i]));
}
}
out
}
fn rename_index_refs(ty: &Term, tele: &[(String, Term)], names: &[String]) -> Term {
rename_tele_types(ty, tele, names)
}
fn app_args(t: &Term) -> Vec<Term> {
let mut args = Vec::new();
let mut cur = t;
while let Term::App(f, a) = cur {
args.push((**a).clone());
cur = f;
}
args.reverse();
args
}
fn peel_pis(t: &Term) -> (Vec<(String, Term)>, &Term) {
let mut params = Vec::new();
let mut cur = t;
while let Term::Pi { param, param_type, body_type } = cur {
params.push((param.clone(), (**param_type).clone()));
cur = body_type;
}
(params, cur)
}
fn head_global(t: &Term) -> Option<&str> {
let mut cur = t;
while let Term::App(f, _) = cur {
cur = f;
}
match cur {
Term::Global(n) => Some(n),
_ => None,
}
}
fn subst_name(t: &Term, old: &str, repl: &Term) -> Term {
match t {
Term::Var(n) if n == old => repl.clone(),
Term::Var(_) | Term::Global(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole
| Term::Const { .. } => t.clone(),
Term::Pi { param, param_type, body_type } => Term::Pi {
param: param.clone(),
param_type: Box::new(subst_name(param_type, old, repl)),
body_type: if param == old {
body_type.clone()
} else {
Box::new(subst_name(body_type, old, repl))
},
},
Term::Lambda { param, param_type, body } => Term::Lambda {
param: param.clone(),
param_type: Box::new(subst_name(param_type, old, repl)),
body: if param == old { body.clone() } else { Box::new(subst_name(body, old, repl)) },
},
Term::App(f, a) => {
Term::App(Box::new(subst_name(f, old, repl)), Box::new(subst_name(a, old, repl)))
}
Term::Match { discriminant, motive, cases } => Term::Match {
discriminant: Box::new(subst_name(discriminant, old, repl)),
motive: Box::new(subst_name(motive, old, repl)),
cases: cases.iter().map(|c| subst_name(c, old, repl)).collect(),
},
Term::Fix { name, body } => Term::Fix {
name: name.clone(),
body: if name == old { body.clone() } else { Box::new(subst_name(body, old, repl)) },
},
Term::MutualFix { defs, index } => {
if defs.iter().any(|(n, _)| n == old) {
t.clone()
} else {
Term::MutualFix {
defs: defs.iter().map(|(n, b)| (n.clone(), subst_name(b, old, repl))).collect(),
index: *index,
}
}
}
Term::Let { name, ty, value, body } => Term::Let {
name: name.clone(),
ty: Box::new(subst_name(ty, old, repl)),
value: Box::new(subst_name(value, old, repl)),
body: if name == old { body.clone() } else { Box::new(subst_name(body, old, repl)) },
},
}
}
fn var(n: &str) -> Term {
Term::Var(n.to_string())
}
fn app(f: Term, x: Term) -> Term {
Term::App(Box::new(f), Box::new(x))
}
fn lam(p: &str, ty: Term, body: Term) -> Term {
Term::Lambda { param: p.to_string(), param_type: Box::new(ty), body: Box::new(body) }
}
fn pi(p: &str, ty: Term, body: Term) -> Term {
Term::Pi { param: p.to_string(), param_type: Box::new(ty), body_type: Box::new(body) }
}
fn arrow(a: Term, b: Term) -> Term {
pi("_", a, b)
}