use crate::declaration::ConstantInfo;
use crate::expr_util::find_mentioned_name;
use crate::name::NameView;
use crate::{Environment, KernelError, Name};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum DerivedTerms {
Recursors,
All,
}
pub(crate) fn recursor_name(type_name: &Name, flat: bool) -> Name {
if flat {
Name::str(format!("{type_name}.rec"))
} else {
Name::mk_str(type_name.clone(), "rec")
}
}
pub(crate) fn is_flat_style(recursor: &Name) -> bool {
recursor.depth() <= 1
}
pub fn escaped_name(name: &Name) -> String {
match name.view() {
NameView::Anonymous => "[anonymous]".to_string(),
NameView::Str(parent, s) => {
let part = if s.contains('.') {
format!("«{s}»")
} else {
s.to_string()
};
if parent.is_anonymous() {
part
} else {
format!("{}.{part}", escaped_name(parent))
}
}
NameView::Num(parent, n) => {
if parent.is_anonymous() {
n.to_string()
} else {
format!("{}.{n}", escaped_name(parent))
}
}
}
}
struct Meant<'a> {
family: &'a [Name],
recursors: Vec<Name>,
auxiliary: Vec<Name>,
}
impl<'a> Meant<'a> {
fn new(family: &'a [Name], owner: &Name, recursor_owner: bool, num_nested: u32) -> Self {
let flat = recursor_owner && is_flat_style(owner);
let mut recursors: Vec<Name> = family.iter().map(|t| recursor_name(t, flat)).collect();
let mut auxiliary = Vec::new();
if let Some(first) = family.first() {
for k in 0..num_nested {
recursors.push(Name::mk_str(first.clone(), format!("rec_{}", k + 1)));
let aux_type = Name::mk_str(first.clone(), format!("_nested_{k}"));
auxiliary.push(Name::mk_str(aux_type.clone(), "rec"));
auxiliary.push(aux_type);
}
}
Meant {
family,
recursors,
auxiliary,
}
}
}
fn family_of<'a>(
ci: &'a ConstantInfo,
resolve: &dyn Fn(&Name) -> Option<&'a ConstantInfo>,
) -> Option<&'a [Name]> {
match ci {
ConstantInfo::Inductive(iv) => Some(&iv.all),
ConstantInfo::Recursor(rv) => Some(&rv.all),
ConstantInfo::Constructor(cv) => match resolve(&cv.induct) {
Some(ConstantInfo::Inductive(iv)) => Some(&iv.all),
_ => None,
},
_ => None,
}
}
#[allow(clippy::result_large_err)]
pub(crate) fn check_derived_references<'b>(
env: &'b Environment,
bundle: &[&'b ConstantInfo],
which: DerivedTerms,
) -> Result<(), KernelError> {
let resolve = |n: &Name| -> Option<&'b ConstantInfo> {
bundle
.iter()
.copied()
.find(|ci| ci.name() == n)
.or_else(|| env.find(n))
};
for ci in bundle.iter().copied() {
let is_recursor = matches!(ci, ConstantInfo::Recursor(_));
if which == DerivedTerms::Recursors && !is_recursor {
continue;
}
let Some(family) = family_of(ci, &resolve) else {
continue;
};
let num_nested = match family.first().and_then(&resolve) {
Some(ConstantInfo::Inductive(iv)) => iv.num_nested,
_ => 0,
};
let meant = Meant::new(family, ci.name(), is_recursor, num_nested);
let mut terms = vec![ci.ty()];
if let ConstantInfo::Recursor(rv) = ci {
terms.extend(rv.rules.iter().map(|r| &r.rhs));
}
for term in terms {
let mut why: Option<String> = None;
let hit = find_mentioned_name(term, |c| match judge_name(c, &meant, &resolve) {
Ok(()) => false,
Err(reason) => {
why = Some(reason);
true
}
});
if let Some(c) = hit {
return Err(refused(ci, c, &why.unwrap_or_default()));
}
}
if let ConstantInfo::Recursor(rv) = ci {
for rule in &rv.rules {
if !matches!(resolve(&rule.ctor), Some(ConstantInfo::Constructor(_))) {
return Err(refused(
ci,
&rule.ctor,
"a rule is keyed by it, and it is not a constructor in the environment \
after the install",
));
}
}
}
}
Ok(())
}
fn judge_name<'a>(
c: &Name,
meant: &Meant<'_>,
resolve: &dyn Fn(&Name) -> Option<&'a ConstantInfo>,
) -> Result<(), String> {
if meant.auxiliary.contains(c) {
return Err(
"an auxiliary constant of the nested derivation, which is never installed".into(),
);
}
let Some(found) = resolve(c) else {
return Err("it is not in the environment after the install".into());
};
let of_family = |all: &[Name]| all == meant.family;
if meant.recursors.contains(c) {
return match found {
ConstantInfo::Recursor(rv) if of_family(&rv.all) => Ok(()),
_ => Err(
"the derivation names a recursor of its family so, and the constant of \
that name is not one"
.into(),
),
};
}
if meant.family.contains(c) {
return match found {
ConstantInfo::Inductive(iv) if of_family(&iv.all) => Ok(()),
_ => Err(
"the derivation names a type of its family so, and the constant of that \
name is not one"
.into(),
),
};
}
Ok(())
}
fn refused(ci: &ConstantInfo, c: &Name, why: &str) -> KernelError {
let what = match ci {
ConstantInfo::Recursor(_) => "recursor",
ConstantInfo::Inductive(_) => "inductive type",
ConstantInfo::Constructor(_) => "constructor",
_ => "constant",
};
KernelError::InvalidRecursor(format!(
"the {what} '{}' the kernel derived names '{}': {why}; the bundle is not installed",
escaped_name(ci.name()),
escaped_name(c)
))
}
#[allow(clippy::result_large_err)]
pub fn add_derived_family(
env: &mut Environment,
group: Vec<ConstantInfo>,
) -> Result<(), KernelError> {
{
let bundle: Vec<&ConstantInfo> = group.iter().collect();
check_derived_references(env, &bundle, DerivedTerms::All)?;
}
env.add_constants(group)
.map_err(|e| KernelError::Other(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escaped_name_sets_a_dotted_component_apart() {
let flat = Name::str("T.rec");
let hierarchical = Name::mk_str(Name::str("T"), "rec");
assert_eq!(escaped_name(&flat), "«T.rec»");
assert_eq!(escaped_name(&hierarchical), "T.rec");
assert_eq!(flat.to_string(), hierarchical.to_string());
assert_eq!(escaped_name(&Name::mk_num(Name::str("x"), 3)), "x.3");
}
#[test]
fn the_names_meant_follow_the_owner_and_the_nesting() {
let family = vec![Name::str("A"), Name::str("C")];
let flat = Meant::new(&family, &Name::str("A.rec"), true, 0);
assert_eq!(
flat.recursors,
vec![Name::str("A.rec"), Name::str("C.rec")],
"a flat recursor's derivation names its siblings flat"
);
let hierarchical = Meant::new(&family, &recursor_name(&Name::str("C"), false), true, 0);
assert_eq!(
hierarchical.recursors,
vec![
recursor_name(&Name::str("A"), false),
recursor_name(&Name::str("C"), false)
]
);
let nested = Meant::new(&family[..1], &recursor_name(&family[0], false), true, 2);
assert_eq!(nested.recursors.len(), 3);
assert_eq!(nested.recursors[2].to_string(), "A.rec_2");
assert_eq!(nested.auxiliary.len(), 4);
}
}