use super::derive::{
check_and_derive_family, has_const_occ, recursor_matches, DerivedFamily, InductiveSpec,
};
use crate::declaration::{ConstantInfo, InductiveVal, RecursorRule, RecursorVal};
use crate::expr_util::{get_app_fn_args, mk_app, replace_expr};
use crate::instantiate::instantiate_type_lparams;
use crate::subst::instantiate;
use crate::{Environment, Expr, KernelError, Level, Name, NameView, TypeChecker};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NestedOcc {
pub container: Name,
pub container_levels: Vec<Level>,
pub args: Vec<Expr>,
pub family_positions: Vec<usize>,
}
pub fn detect(family_names: &[Name], ty: &Expr) -> Option<NestedOcc> {
let (head, args) = get_app_fn_args(ty);
let (container, container_levels) = match head {
Expr::Const(n, levels) => (n.clone(), levels.clone()),
_ => return None,
};
if family_names.contains(&container) {
return None;
}
let family_positions: Vec<usize> = args
.iter()
.copied()
.enumerate()
.filter(|(_, a)| has_const_occ(a, family_names))
.map(|(i, _)| i)
.collect();
if family_positions.is_empty() {
return None;
}
let args = args.into_iter().cloned().collect();
Some(NestedOcc {
container,
container_levels,
args,
family_positions,
})
}
#[derive(Clone, Debug)]
pub struct AuxMap {
pub aux_type: Name,
pub container: Name,
pub container_levels: Vec<Level>,
pub params: Vec<Expr>,
pub aux_ctors: Vec<(Name, Name)>,
pub aux_rec: Name,
pub container_rec: Name,
}
#[derive(Clone, Debug)]
pub struct ExpandedFamily {
pub specs: Vec<InductiveSpec>,
pub num_originals: usize,
pub aux_maps: Vec<AuxMap>,
pub lparams: Vec<Name>,
pub num_params: u32,
}
struct Found {
container: Name,
levels: Vec<Level>,
as_args: Vec<Expr>,
num_params: usize,
}
fn lookup_inductive<'a>(env: &'a Environment, name: &Name) -> Option<&'a InductiveVal> {
match env.find(name)? {
ConstantInfo::Inductive(iv) => Some(iv),
_ => None,
}
}
fn lookup_ctor(env: &Environment, name: &Name) -> Option<(Expr, Vec<Name>)> {
match env.find(name)? {
ConstantInfo::Constructor(cv) => {
Some((cv.common.ty.clone(), cv.common.level_params.clone()))
}
_ => None,
}
}
fn last_component(name: &Name) -> String {
match name.view() {
NameView::Str(_, s) => s.to_string(),
_ => "ctor".to_string(),
}
}
fn instantiate_leading(ty: &Expr, args: &[Expr]) -> Expr {
let mut cur = ty.clone();
for arg in args {
match cur {
Expr::Pi(_, _, _dom, body) => {
cur = instantiate(&body, arg);
}
_ => break,
}
}
cur
}
fn replace_container_app(
ty: &Expr,
container: &Name,
container_levels: &[Level],
as_args: &[Expr],
np: usize,
aux: &Name,
) -> Expr {
replace_expr(ty, &mut |e, _depth| {
let (head, args) = get_app_fn_args(e);
if let Expr::Const(n, levels) = head {
if n == container && levels.as_slice() == container_levels && args.len() >= np {
let matches = as_args
.iter()
.zip(args.iter().take(np))
.all(|(a, b)| a == *b);
if matches {
let rest: Vec<Expr> = args[np..]
.iter()
.map(|x| {
replace_container_app(x, container, container_levels, as_args, np, aux)
})
.collect();
return Some(mk_app(Expr::Const(aux.clone(), vec![]), &rest));
}
}
}
None
})
}
#[allow(clippy::result_large_err)]
fn scan(env: &Environment, all_family: &[Name], e: &Expr) -> Result<Option<Found>, KernelError> {
let (head, args) = get_app_fn_args(e);
if let Expr::Const(c, levels) = head {
if !all_family.contains(c) {
if let Some(iv) = lookup_inductive(env, c) {
let np = iv.num_params as usize;
if args.len() >= np {
let fam_in_param = args.iter().take(np).any(|a| has_const_occ(a, all_family));
let fam_in_index = args.iter().skip(np).any(|a| has_const_occ(a, all_family));
if fam_in_index {
return Err(KernelError::UnsupportedNestedInductive(
all_family.first().cloned().unwrap_or_default(),
));
}
if fam_in_param {
let as_args = args.iter().take(np).map(|x| (**x).clone()).collect();
return Ok(Some(Found {
container: c.clone(),
levels: levels.clone(),
as_args,
num_params: np,
}));
}
}
}
}
}
match e {
Expr::App(f, a) => {
if let Some(x) = scan(env, all_family, f)? {
return Ok(Some(x));
}
scan(env, all_family, a)
}
Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
if let Some(x) = scan(env, all_family, ty)? {
return Ok(Some(x));
}
scan(env, all_family, body)
}
Expr::Let(_, ty, val, body) => {
if let Some(x) = scan(env, all_family, ty)? {
return Ok(Some(x));
}
if let Some(x) = scan(env, all_family, val)? {
return Ok(Some(x));
}
scan(env, all_family, body)
}
Expr::Proj(_, _, inner) => scan(env, all_family, inner),
_ => Ok(None),
}
}
#[allow(clippy::result_large_err)]
pub fn specialize(
env: &Environment,
lparams: &[Name],
num_params: u32,
specs: &[InductiveSpec],
) -> Result<Option<ExpandedFamily>, KernelError> {
if num_params != 0 {
return Ok(None);
}
let orig_names: Vec<Name> = specs.iter().map(|s| s.name.clone()).collect();
let mut all_family = orig_names.clone();
let mut cur_specs = specs.to_vec();
let mut aux_maps: Vec<AuxMap> = Vec::new();
let mut aux_count = 0usize;
let mut fuel = 256usize;
loop {
fuel = fuel.checked_sub(1).ok_or_else(|| {
KernelError::Other("nested inductive specialization did not terminate".to_string())
})?;
let mut found: Option<Found> = None;
'outer: for spec in &cur_specs {
for (_, cty) in &spec.ctors {
if let Some(f) = scan(env, &all_family, cty)? {
found = Some(f);
break 'outer;
}
}
}
let Some(f) = found else { break };
let iv = match lookup_inductive(env, &f.container) {
Some(iv) if iv.all.len() == 1 => iv.clone(),
_ => return Ok(None),
};
let aux_name = Name::mk_str(orig_names[0].clone(), format!("_nested_{}", aux_count));
aux_count += 1;
let c_levels = f.levels.clone();
let as_args = f.as_args.clone();
let np = f.num_params;
let aux_ty = {
let t = instantiate_type_lparams(&iv.common.ty, &iv.common.level_params, &c_levels);
instantiate_leading(&t, &as_args)
};
let mut aux_ctors: Vec<(Name, Expr)> = Vec::with_capacity(iv.ctors.len());
let mut ctor_name_map: Vec<(Name, Name)> = Vec::with_capacity(iv.ctors.len());
for cn in &iv.ctors {
let (cty, clp) = lookup_ctor(env, cn).ok_or_else(|| {
KernelError::Other(format!(
"constructor '{}' of nested container '{}' not found",
cn, f.container
))
})?;
let t = instantiate_type_lparams(&cty, &clp, &c_levels);
let t = instantiate_leading(&t, &as_args);
let t = replace_container_app(&t, &f.container, &c_levels, &as_args, np, &aux_name);
let aux_cn = Name::mk_str(aux_name.clone(), last_component(cn));
aux_ctors.push((aux_cn.clone(), t));
ctor_name_map.push((aux_cn, cn.clone()));
}
aux_maps.push(AuxMap {
aux_type: aux_name.clone(),
container: f.container.clone(),
container_levels: c_levels.clone(),
params: as_args.clone(),
aux_ctors: ctor_name_map,
aux_rec: Name::mk_str(aux_name.clone(), "rec".to_string()),
container_rec: Name::mk_str(f.container.clone(), "rec".to_string()),
});
all_family.push(aux_name.clone());
cur_specs.push(InductiveSpec::new(aux_name.clone(), aux_ty, aux_ctors));
for spec in cur_specs.iter_mut() {
for (_, cty) in spec.ctors.iter_mut() {
*cty = replace_container_app(cty, &f.container, &c_levels, &as_args, np, &aux_name);
}
}
}
if all_family.len() == orig_names.len() {
return Ok(None);
}
Ok(Some(ExpandedFamily {
specs: cur_specs,
num_originals: orig_names.len(),
aux_maps,
lparams: lparams.to_vec(),
num_params,
}))
}
#[allow(clippy::result_large_err)]
pub fn check_and_derive_family_nested(
env: &Environment,
lparams: &[Name],
num_params: u32,
specs: &[InductiveSpec],
) -> Result<(DerivedFamily, Option<ExpandedFamily>), KernelError> {
super::derive::check_specs_closed(specs)?;
match specialize(env, lparams, num_params, specs)? {
None => Ok((
check_and_derive_family(env, lparams, num_params, specs)?,
None,
)),
Some(exp) => {
let fam = check_and_derive_family(env, &exp.lparams, exp.num_params, &exp.specs)?;
Ok((fam, Some(exp)))
}
}
}
fn restore_expr(e: &Expr, aux_maps: &[AuxMap]) -> Expr {
replace_expr(e, &mut |sub, _depth| {
if let Expr::Const(name, _levels) = sub {
for m in aux_maps {
if name == &m.aux_type {
return Some(mk_app(
Expr::Const(m.container.clone(), m.container_levels.clone()),
&m.params,
));
}
for (aux_ctor, real_ctor) in &m.aux_ctors {
if name == aux_ctor {
return Some(mk_app(
Expr::Const(real_ctor.clone(), m.container_levels.clone()),
&m.params,
));
}
}
}
}
None
})
}
fn restore_ctor_name(ctor: &Name, aux_maps: &[AuxMap]) -> Name {
for m in aux_maps {
for (aux_ctor, real_ctor) in &m.aux_ctors {
if ctor == aux_ctor {
return real_ctor.clone();
}
}
}
ctor.clone()
}
pub fn restore(fam: DerivedFamily, exp: &ExpandedFamily) -> DerivedFamily {
let orig_names: Vec<Name> = exp.specs[..exp.num_originals]
.iter()
.map(|s| s.name.clone())
.collect();
let num_nested = exp.aux_maps.len() as u32;
let mut inductives = Vec::with_capacity(exp.num_originals);
for ci in fam.inductives.iter().take(exp.num_originals) {
if let ConstantInfo::Inductive(iv) = ci {
let mut iv = iv.clone();
iv.common.ty = restore_expr(&iv.common.ty, &exp.aux_maps);
iv.all = orig_names.clone();
iv.num_nested = num_nested;
inductives.push(ConstantInfo::Inductive(iv));
}
}
let mut constructors = Vec::new();
for ci in &fam.constructors {
if let ConstantInfo::Constructor(cv) = ci {
if orig_names.contains(&cv.induct) {
let mut cv = cv.clone();
cv.common.ty = restore_expr(&cv.common.ty, &exp.aux_maps);
constructors.push(ConstantInfo::Constructor(cv));
}
}
}
let mut recursors = Vec::with_capacity(fam.recursors.len());
for ci in &fam.recursors {
if let ConstantInfo::Recursor(rv) = ci {
let mut rv = rv.clone();
rv.common.ty = restore_expr(&rv.common.ty, &exp.aux_maps);
rv.all = orig_names.clone();
rv.rules = rv
.rules
.iter()
.map(|rule| RecursorRule {
ctor: restore_ctor_name(&rule.ctor, &exp.aux_maps),
nfields: rule.nfields,
rhs: restore_expr(&rule.rhs, &exp.aux_maps),
})
.collect();
recursors.push(ConstantInfo::Recursor(rv));
}
}
DerivedFamily {
inductives,
constructors,
recursors,
}
}
fn rule_ctor_set(rv: &RecursorVal) -> Vec<Name> {
let mut s: Vec<Name> = rv.rules.iter().map(|r| r.ctor.clone()).collect();
s.sort();
s
}
fn rename_recursors(fam: DerivedFamily, name_map: &[(Name, Name)]) -> DerivedFamily {
fn rename_expr(e: &Expr, name_map: &[(Name, Name)]) -> Expr {
replace_expr(e, &mut |sub, _depth| {
if let Expr::Const(n, levels) = sub {
for (from, to) in name_map {
if n == from {
return Some(Expr::Const(to.clone(), levels.clone()));
}
}
}
None
})
}
let mut recursors = Vec::with_capacity(fam.recursors.len());
for ci in fam.recursors {
if let ConstantInfo::Recursor(mut rv) = ci {
for (from, to) in name_map {
if rv.common.name == *from {
rv.common.name = to.clone();
break;
}
}
rv.common.ty = rename_expr(&rv.common.ty, name_map);
rv.rules = rv
.rules
.into_iter()
.map(|r| RecursorRule {
ctor: r.ctor,
nfields: r.nfields,
rhs: rename_expr(&r.rhs, name_map),
})
.collect();
recursors.push(ConstantInfo::Recursor(rv));
} else {
recursors.push(ci);
}
}
DerivedFamily {
inductives: fam.inductives,
constructors: fam.constructors,
recursors,
}
}
#[allow(clippy::result_large_err)]
pub fn verify_nested_bundle(
env: &Environment,
lparams: &[Name],
num_params: u32,
specs: &[InductiveSpec],
exported_recs: &[RecursorVal],
) -> Result<DerivedFamily, KernelError> {
let (raw, exp_opt) = check_and_derive_family_nested(env, lparams, num_params, specs)?;
let Some(exp) = exp_opt else {
return Err(KernelError::UnsupportedNestedInductive(
specs.first().map(|s| s.name.clone()).unwrap_or_default(),
));
};
let restored = restore(raw, &exp);
if restored.recursors.len() != exported_recs.len() {
return Err(KernelError::InvalidRecursor(format!(
"nested inductive '{}': kernel derived {} recursors but the export lists {}",
specs[0].name,
restored.recursors.len(),
exported_recs.len()
)));
}
let mut name_map: Vec<(Name, Name)> = Vec::with_capacity(restored.recursors.len());
for ci in &restored.recursors {
if let ConstantInfo::Recursor(rv) = ci {
let set = rule_ctor_set(rv);
let er = exported_recs
.iter()
.find(|er| rule_ctor_set(er) == set)
.ok_or_else(|| {
KernelError::InvalidRecursor(format!(
"nested inductive '{}': no exported recursor eliminates the same \
constructors as the kernel-derived '{}'",
specs[0].name, rv.common.name
))
})?;
name_map.push((rv.common.name.clone(), er.common.name.clone()));
}
}
let renamed = rename_recursors(restored, &name_map);
let mut tc = TypeChecker::new(env);
for er in exported_recs {
let mut ok = false;
for ci in &renamed.recursors {
if let ConstantInfo::Recursor(d) = ci {
if d.common.name == er.common.name && recursor_matches(&mut tc, d, er) {
ok = true;
break;
}
}
}
if !ok {
return Err(KernelError::InvalidRecursor(format!(
"exported recursor '{}' does not match the kernel-derived nested recursor",
er.common.name
)));
}
}
Ok(renamed)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Node;
fn c(name: &str, levels: Vec<Level>) -> Expr {
Expr::Const(Name::str(name), levels)
}
fn app(f: Expr, a: Expr) -> Expr {
Expr::App(Node::new(f), Node::new(a))
}
#[test]
fn detects_list_of_family() {
let fam = [Name::str("T")];
let ty = app(c("List", vec![Level::zero()]), c("T", vec![]));
let occ = detect(&fam, &ty).expect("nested occurrence");
assert_eq!(occ.container, Name::str("List"));
assert_eq!(occ.container_levels, vec![Level::zero()]);
assert_eq!(occ.family_positions, vec![0]);
assert_eq!(occ.args.len(), 1);
}
#[test]
fn direct_occurrence_is_not_nested() {
let fam = [Name::str("T")];
let ty = app(c("T", vec![]), c("x", vec![]));
assert!(detect(&fam, &ty).is_none());
}
#[test]
fn foreign_container_without_family_is_not_nested() {
let fam = [Name::str("T")];
let ty = app(c("List", vec![Level::zero()]), c("Nat", vec![]));
assert!(detect(&fam, &ty).is_none());
}
#[test]
fn bare_const_is_not_nested() {
let fam = [Name::str("T")];
assert!(detect(&fam, &c("Nat", vec![])).is_none());
}
#[test]
fn family_in_later_argument_position() {
let fam = [Name::str("T")];
let ty = app(
app(
c("Prod", vec![Level::zero(), Level::zero()]),
c("Nat", vec![]),
),
c("T", vec![]),
);
let occ = detect(&fam, &ty).expect("nested occurrence");
assert_eq!(occ.container, Name::str("Prod"));
assert_eq!(occ.family_positions, vec![1]);
assert_eq!(occ.args.len(), 2);
}
#[test]
fn family_nested_deeper_in_argument() {
let fam = [Name::str("T")];
let inner = app(c("List", vec![Level::zero()]), c("T", vec![]));
let ty = app(c("Array", vec![Level::zero()]), inner);
let occ = detect(&fam, &ty).expect("nested occurrence");
assert_eq!(occ.container, Name::str("Array"));
assert_eq!(occ.family_positions, vec![0]);
}
#[test]
fn multiple_family_members() {
let fam = [Name::str("T"), Name::str("U")];
let ty = app(
app(c("Sum", vec![Level::zero(), Level::zero()]), c("T", vec![])),
c("U", vec![]),
);
let occ = detect(&fam, &ty).expect("nested occurrence");
assert_eq!(occ.family_positions, vec![0, 1]);
}
#[test]
fn non_nested_family_uses_ordinary_path() {
let env = Environment::new();
let spec = InductiveSpec::new(
Name::str("Foo"),
Expr::Sort(Level::succ(Level::zero())),
vec![(Name::str("Foo.mk"), c("Foo", vec![]))],
);
let (fam, exp) =
check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("ordinary derivation");
assert!(exp.is_none(), "non-nested family must not expand");
assert_eq!(fam.recursors.len(), 1);
}
#[test]
fn nested_t_derives_via_specialization() {
let mut env = Environment::new();
crate::builtin::init_builtin_env(&mut env).expect("builtin env");
let t = c("NestedT", vec![]);
let list_t = app(c("List", vec![Level::zero()]), t.clone());
let spec = InductiveSpec::new(
Name::str("NestedT"),
Expr::Sort(Level::succ(Level::zero())),
vec![(
Name::str("NestedT.mk"),
Expr::Pi(
crate::BinderInfo::Default,
Name::str("l"),
Node::new(list_t),
Node::new(t),
),
)],
);
let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec])
.expect("nested inductive now derives");
let exp = exp.expect("NestedT must be recognised as nested");
assert_eq!(exp.num_originals, 1);
assert_eq!(exp.specs.len(), 2);
assert_eq!(exp.aux_maps.len(), 1);
let m = &exp.aux_maps[0];
assert_eq!(m.container, Name::str("List"));
let real_ctors: Vec<Name> = m.aux_ctors.iter().map(|(_, r)| r.clone()).collect();
assert!(real_ctors.contains(&Name::str("List.nil")));
assert!(real_ctors.contains(&Name::str("List.cons")));
assert_eq!(fam.inductives.len(), 2);
assert_eq!(fam.constructors.len(), 3);
assert_eq!(fam.recursors.len(), 2);
let nestedt_rec = fam
.recursors
.iter()
.find_map(|ci| match ci {
ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("NestedT.mk")).is_some() => {
Some(rv)
}
_ => None,
})
.expect("a recursor eliminating NestedT.mk");
assert_eq!(nestedt_rec.num_motives, 2);
assert_eq!(nestedt_rec.num_minors, 3);
}
#[test]
fn nested_t_aux_recursor_iota_reduces() {
let mut env = crate::Environment::new();
crate::builtin::init_builtin_env(&mut env).expect("builtin env");
let t = c("NestedT", vec![]);
let list_t = app(c("List", vec![Level::zero()]), t.clone());
let spec = InductiveSpec::new(
Name::str("NestedT"),
Expr::Sort(Level::succ(Level::zero())),
vec![(
Name::str("NestedT.mk"),
Expr::Pi(
crate::BinderInfo::Default,
Name::str("l"),
Node::new(list_t),
Node::new(t),
),
)],
);
let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
let exp = exp.expect("nested");
let mut env2 = env.clone();
for ci in fam.clone().into_constant_infos() {
env2.add_constant(ci).expect("install expanded family");
}
let aux = &exp.aux_maps[0];
let aux_nil = aux
.aux_ctors
.iter()
.find(|(_, r)| *r == Name::str("List.nil"))
.expect("aux nil ctor")
.0
.clone();
let ph = |s: &str| Expr::Const(Name::str(s), vec![]);
let e = mk_app(
Expr::Const(aux.aux_rec.clone(), vec![Level::zero()]),
&[
ph("mN"),
ph("mL"),
ph("minMk"),
ph("minNil"),
ph("minCons"),
Expr::Const(aux_nil, vec![]),
],
);
let reduced = crate::TypeChecker::new(&env2).whnf(&e);
let head = get_app_fn_args(&reduced).0.clone();
assert_eq!(
head,
Expr::Const(Name::str("minNil"), vec![]),
"aux recursor did not iota-reduce to the nil minor: {:?}",
reduced
);
}
#[test]
fn reach_through_double_nesting_expands_to_three() {
let mut env = crate::Environment::new();
crate::builtin::init_builtin_env(&mut env).expect("builtin env");
let two = c("Two", vec![]);
let list = |x: Expr| app(c("List", vec![Level::zero()]), x);
let spec = InductiveSpec::new(
Name::str("Two"),
Expr::Sort(Level::succ(Level::zero())),
vec![(
Name::str("Two.mk"),
Expr::Pi(
crate::BinderInfo::Default,
Name::str("l"),
Node::new(list(list(two.clone()))),
Node::new(two),
),
)],
);
let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
let exp = exp.expect("nested");
assert_eq!(exp.num_originals, 1);
assert_eq!(exp.specs.len(), 3, "Two + two List auxiliaries");
assert_eq!(exp.aux_maps.len(), 2);
let rec = fam
.recursors
.iter()
.find_map(|ci| match ci {
ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("Two.mk")).is_some() => {
Some(rv)
}
_ => None,
})
.expect("Two's recursor");
assert_eq!(rec.num_motives, 3);
assert_eq!(rec.num_minors, 5);
}
#[test]
fn nested_t_restore_produces_real_list_recursor() {
use crate::expr_util::collect_consts;
let mut env = crate::Environment::new();
crate::builtin::init_builtin_env(&mut env).expect("builtin env");
let t = c("NestedT", vec![]);
let list_t = app(c("List", vec![Level::zero()]), t.clone());
let spec = InductiveSpec::new(
Name::str("NestedT"),
Expr::Sort(Level::succ(Level::zero())),
vec![(
Name::str("NestedT.mk"),
Expr::Pi(
crate::BinderInfo::Default,
Name::str("l"),
Node::new(list_t),
Node::new(t.clone()),
),
)],
);
let (fam, exp) = check_and_derive_family_nested(&env, &[], 0, &[spec]).expect("derive");
let exp = exp.expect("nested");
let aux_type = exp.aux_maps[0].aux_type.clone();
let aux_rec = exp.aux_maps[0].aux_rec.clone();
let restored = restore(fam, &exp);
assert_eq!(restored.inductives.len(), 1);
assert_eq!(restored.constructors.len(), 1);
assert_eq!(restored.recursors.len(), 2);
match &restored.inductives[0] {
ConstantInfo::Inductive(iv) => {
assert_eq!(iv.num_nested, 1);
assert_eq!(iv.all, vec![Name::str("NestedT")]);
}
_ => panic!("expected inductive"),
}
match &restored.constructors[0] {
ConstantInfo::Constructor(cv) => {
let consts = collect_consts(&cv.common.ty);
assert!(consts.contains(&Name::str("List")));
assert!(!consts.contains(&aux_type));
}
_ => panic!("expected constructor"),
}
let mut env2 = env.clone();
for ci in restored.clone().into_constant_infos() {
env2.add_constant(ci).expect("install restored family");
}
for ci in &restored.recursors {
if let ConstantInfo::Recursor(rv) = ci {
crate::TypeChecker::new(&env2)
.infer_type(&rv.common.ty)
.expect("restored recursor type re-checks");
}
}
let ph = |s: &str| Expr::Const(Name::str(s), vec![]);
let nil_app = mk_app(
Expr::Const(Name::str("List.nil"), vec![Level::zero()]),
std::slice::from_ref(&t),
);
let e_aux = mk_app(
Expr::Const(aux_rec, vec![Level::zero()]),
&[
ph("mN"),
ph("mL"),
ph("minMk"),
ph("minNil"),
ph("minCons"),
nil_app.clone(),
],
);
let r_aux = crate::TypeChecker::new(&env2).whnf(&e_aux);
assert_eq!(
get_app_fn_args(&r_aux).0.clone(),
Expr::Const(Name::str("minNil"), vec![]),
"restored aux recursor did not fire on real List.nil: {:?}",
r_aux
);
let main_rec = restored
.recursors
.iter()
.find_map(|ci| match ci {
ConstantInfo::Recursor(rv) if rv.get_rule(&Name::str("NestedT.mk")).is_some() => {
Some(rv.common.name.clone())
}
_ => None,
})
.expect("NestedT.rec");
let major = app(c("NestedT.mk", vec![]), nil_app);
let e_main = mk_app(
Expr::Const(main_rec, vec![Level::zero()]),
&[
ph("mN"),
ph("mL"),
ph("minMk"),
ph("minNil"),
ph("minCons"),
major,
],
);
let r_main = crate::TypeChecker::new(&env2).whnf(&e_main);
assert_eq!(
get_app_fn_args(&r_main).0.clone(),
Expr::Const(Name::str("minMk"), vec![]),
"restored NestedT.rec did not fire on NestedT.mk: {:?}",
r_main
);
}
fn nested_t_bundle() -> (crate::Environment, InductiveSpec, Vec<RecursorVal>) {
let mut env = crate::Environment::new();
crate::builtin::init_builtin_env(&mut env).expect("builtin env");
let t = c("NestedT", vec![]);
let list_t = app(c("List", vec![Level::zero()]), t.clone());
let spec = InductiveSpec::new(
Name::str("NestedT"),
Expr::Sort(Level::succ(Level::zero())),
vec![(
Name::str("NestedT.mk"),
Expr::Pi(
crate::BinderInfo::Default,
Name::str("l"),
Node::new(list_t),
Node::new(t),
),
)],
);
let (raw, exp) = check_and_derive_family_nested(&env, &[], 0, std::slice::from_ref(&spec))
.expect("derive");
let exp = exp.expect("nested");
let restored = restore(raw, &exp);
let name_map: Vec<(Name, Name)> = restored
.recursors
.iter()
.filter_map(|ci| match ci {
ConstantInfo::Recursor(rv) => {
let exported = if rule_ctor_set(rv).contains(&Name::str("NestedT.mk")) {
Name::mk_str(Name::str("NestedT"), "rec".to_string())
} else {
Name::mk_str(Name::str("NestedT"), "rec_1".to_string())
};
Some((rv.common.name.clone(), exported))
}
_ => None,
})
.collect();
let canonical = rename_recursors(restored, &name_map);
let exported_recs: Vec<RecursorVal> = canonical
.recursors
.iter()
.filter_map(|ci| match ci {
ConstantInfo::Recursor(rv) => Some(rv.clone()),
_ => None,
})
.collect();
(env, spec, exported_recs)
}
#[test]
fn verify_nested_bundle_accepts_matching_export() {
let (env, spec, exported_recs) = nested_t_bundle();
let out = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
.expect("matching export verifies");
let names: Vec<Name> = out
.recursors
.iter()
.filter_map(|ci| match ci {
ConstantInfo::Recursor(rv) => Some(rv.common.name.clone()),
_ => None,
})
.collect();
assert!(names.contains(&Name::mk_str(Name::str("NestedT"), "rec".to_string())));
assert!(names.contains(&Name::mk_str(Name::str("NestedT"), "rec_1".to_string())));
}
#[test]
fn verify_nested_bundle_rejects_corrupted_recursor() {
let (env, spec, mut exported_recs) = nested_t_bundle();
exported_recs[0].num_minors += 1;
let err = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
.expect_err("corrupted export must be rejected");
assert!(matches!(err, KernelError::InvalidRecursor(_)));
}
#[test]
fn verify_nested_bundle_rejects_wrong_recursor_count() {
let (env, spec, mut exported_recs) = nested_t_bundle();
exported_recs.pop();
let err = verify_nested_bundle(&env, &[], 0, &[spec], &exported_recs)
.expect_err("wrong recursor count must be rejected");
assert!(matches!(err, KernelError::InvalidRecursor(_)));
}
}