use crate::error::{KernelError, KernelResult};
use crate::term::Term;
pub fn check_positivity(inductive: &str, constructor: &str, ty: &Term) -> KernelResult<()> {
check_strictly_positive(inductive, constructor, ty)
}
fn check_strictly_positive(inductive: &str, constructor: &str, ty: &Term) -> KernelResult<()> {
match ty {
Term::Global(name) if name == inductive => Ok(()),
Term::Pi {
param_type,
body_type,
..
} => {
match param_type.as_ref() {
Term::Global(name) if name == inductive => {
}
_ => {
if occurs_in(inductive, param_type) {
return Err(KernelError::PositivityViolation {
inductive: inductive.to_string(),
constructor: constructor.to_string(),
reason: format!(
"'{}' occurs in negative position (inside parameter type)",
inductive
),
});
}
}
}
check_strictly_positive(inductive, constructor, body_type)
}
Term::App(func, arg) => {
check_strictly_positive(inductive, constructor, func)?;
check_strictly_positive(inductive, constructor, arg)
}
Term::Lambda {
param_type, body, ..
} => {
match param_type.as_ref() {
Term::Global(name) if name == inductive => {}
_ => {
if occurs_in(inductive, param_type) {
return Err(KernelError::PositivityViolation {
inductive: inductive.to_string(),
constructor: constructor.to_string(),
reason: format!(
"'{}' occurs in negative position (inside lambda parameter)",
inductive
),
});
}
}
}
check_strictly_positive(inductive, constructor, body)
}
Term::Sort(_) => Ok(()),
Term::Var(_) => Ok(()),
Term::Global(_) => Ok(()), Term::Lit(_) => Ok(()),
Term::Match {
discriminant,
motive,
cases,
} => {
check_strictly_positive(inductive, constructor, discriminant)?;
check_strictly_positive(inductive, constructor, motive)?;
for case in cases {
check_strictly_positive(inductive, constructor, case)?;
}
Ok(())
}
Term::Fix { body, .. } => check_strictly_positive(inductive, constructor, body),
Term::Hole => Ok(()),
}
}
fn occurs_in(inductive: &str, term: &Term) -> bool {
match term {
Term::Global(name) => name == inductive,
Term::Sort(_) | Term::Var(_) | Term::Lit(_) => false,
Term::Pi {
param_type,
body_type,
..
} => occurs_in(inductive, param_type) || occurs_in(inductive, body_type),
Term::Lambda {
param_type, body, ..
} => occurs_in(inductive, param_type) || occurs_in(inductive, body),
Term::App(func, arg) => occurs_in(inductive, func) || occurs_in(inductive, arg),
Term::Match {
discriminant,
motive,
cases,
} => {
occurs_in(inductive, discriminant)
|| occurs_in(inductive, motive)
|| cases.iter().any(|c| occurs_in(inductive, c))
}
Term::Fix { body, .. } => occurs_in(inductive, body),
Term::Hole => false, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_recursive_arg() {
let ty = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Nat".to_string())),
body_type: Box::new(Term::Global("Nat".to_string())),
};
assert!(check_positivity("Nat", "Succ", &ty).is_ok());
}
#[test]
fn test_negative_inside_arrow() {
let bad_to_false = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Bad".to_string())),
body_type: Box::new(Term::Global("False".to_string())),
};
let ty = Term::Pi {
param: "_".to_string(),
param_type: Box::new(bad_to_false),
body_type: Box::new(Term::Global("Bad".to_string())),
};
assert!(check_positivity("Bad", "Cons", &ty).is_err());
}
#[test]
fn test_nested_negative() {
let tricky_to_nat = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Tricky".to_string())),
body_type: Box::new(Term::Global("Nat".to_string())),
};
let inner = Term::Pi {
param: "_".to_string(),
param_type: Box::new(tricky_to_nat),
body_type: Box::new(Term::Global("Nat".to_string())),
};
let make_type = Term::Pi {
param: "_".to_string(),
param_type: Box::new(inner),
body_type: Box::new(Term::Global("Tricky".to_string())),
};
let result = check_positivity("Tricky", "Make", &make_type);
assert!(result.is_err(), "Should reject nested negative: {:?}", result);
}
#[test]
fn test_list_cons_valid() {
let ty = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Nat".to_string())),
body_type: Box::new(Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("List".to_string())),
body_type: Box::new(Term::Global("List".to_string())),
}),
};
assert!(check_positivity("List", "Cons", &ty).is_ok());
}
}