use std::fmt::Debug;
use crate::{
Atomize, IsBot, IsTop, Lattice, LatticeBimorphism, LatticeMorphism, LatticeOrd, Merge,
NaiveLatticeOrd,
};
pub fn check_all<T: Lattice + Clone + PartialEq + Debug + Default>(items: &[T]) {
check_lattice_ord(items);
check_partial_ord_properties(items);
check_lattice_properties(items);
check_lattice_is_bot(items);
check_lattice_is_top(items);
check_lattice_default_is_bot::<T>();
}
pub fn check_lattice_ord<T: LatticeOrd + NaiveLatticeOrd + Debug>(items: &[T]) {
for [a, b] in cartesian_power(items) {
assert_eq!(a.naive_cmp(b), a.partial_cmp(b), "`{:?}`, `{:?}`", a, b);
}
}
#[expect(
clippy::eq_op,
clippy::double_comparisons,
reason = "testing comparison properties"
)]
pub fn check_partial_ord_properties<T: PartialOrd + PartialEq + Debug>(items: &[T]) {
use std::cmp::Ordering::*;
for a in items {
assert!(a == a, "Reflexivity: `{:?}` should equal itself.", a);
}
for [a, b] in cartesian_power(items) {
assert_eq!(a == b, b == a, "`{:?}`, `{:?}`", a, b);
}
for [a, b, c] in cartesian_power(items) {
if a == b && b == c {
assert_eq!(a == b && b == c, a == c, "`{:?}`, `{:?}`, `{:?}`", a, b, c);
}
}
for [a, b] in cartesian_power(items) {
assert_eq!(
a == b,
a.partial_cmp(b) == Some(Equal),
"`{:?}`, `{:?}`",
a,
b,
);
assert_eq!(
a < b,
a.partial_cmp(b) == Some(Less),
"`{:?}`, `{:?}`",
a,
b,
);
assert_eq!(
a > b,
a.partial_cmp(b) == Some(Greater),
"`{:?}`, `{:?}`",
a,
b,
);
assert_eq!(a <= b, a < b || a == b, "`{:?}`, `{:?}`", a, b);
assert_eq!(a >= b, a > b || a == b, "`{:?}`, `{:?}`", a, b);
{
assert_eq!(a != b, !(a == b), "`{:?}`, `{:?}`", a, b);
}
}
for [a, b, c] in cartesian_power(items) {
if a < b && b < c {
assert!(a < c, "`{:?}`, `{:?}`, `{:?}`", a, b, c);
}
if a == b && b == c {
assert!(a == c, "`{:?}`, `{:?}`, `{:?}`", a, b, c);
}
if a > b && b > c {
assert!(a > c, "`{:?}`, `{:?}`, `{:?}`", a, b, c);
}
}
for [a, b] in cartesian_power(items) {
assert_eq!(a < b, b > a, "`{:?}`, `{:?}`", a, b);
}
}
pub fn check_lattice_properties<T: Merge<T> + Clone + PartialEq + Debug>(items: &[T]) {
for x in items {
assert_eq!(
Merge::merge_owned(x.clone(), x.clone()),
x.clone(),
"`{:?}`",
x,
);
}
for [x, y] in cartesian_power(items) {
assert_eq!(
Merge::merge_owned(x.clone(), y.clone()),
Merge::merge_owned(y.clone(), x.clone()),
"`{:?}`, `{:?}`",
x,
y,
);
}
for [x, y, z] in cartesian_power(items) {
assert_eq!(
Merge::merge_owned(x.clone(), Merge::merge_owned(y.clone(), z.clone())),
Merge::merge_owned(Merge::merge_owned(x.clone(), y.clone()), z.clone()),
"`{:?}`, `{:?}`, `{:?}`",
x,
y,
z,
);
}
}
pub fn check_lattice_is_bot<T: IsBot + LatticeOrd + Debug>(items: &[T]) {
let Some(bot) = items.iter().find(|&x| IsBot::is_bot(x)) else {
return;
};
for x in items {
assert!(bot <= x);
assert_eq!(bot == x, x.is_bot(), "{:?}", x);
}
}
pub fn check_lattice_is_top<T: IsTop + LatticeOrd + Debug>(items: &[T]) {
let Some(top) = items.iter().find(|&x| IsTop::is_top(x)) else {
return;
};
for x in items {
assert!(x <= top);
assert_eq!(top == x, x.is_top(), "{:?}", x);
}
}
pub fn check_lattice_default_is_bot<T: IsBot + Default>() {
assert!(T::is_bot(&T::default()));
}
pub fn check_atomize_each<
T: Atomize + Merge<T::Atom> + LatticeOrd + IsBot + Default + Clone + Debug,
>(
items: &[T],
) where
T::Atom: Debug,
{
for item in items {
let mut reformed = T::default();
let mut atoms = item.clone().atomize().peekable();
assert_eq!(
atoms.peek().is_none(),
item.is_bot(),
"`{:?}` atomize should return empty iterator ({}) if and only if item is bot ({}).",
item,
atoms.peek().is_none(),
item.is_bot()
);
for atom in atoms {
assert!(
!atom.is_bot(),
"`{:?}` atomize illegally returned a bottom atom `{:?}`.",
item,
atom,
);
reformed.merge(atom);
}
assert_eq!(item, &reformed, "`{:?}` atomize failed to reform", item);
}
}
pub fn check_lattice_morphism<LatIn, Func>(mut func: Func, items: &[LatIn])
where
Func: LatticeMorphism<LatIn>,
LatIn: Merge<LatIn> + Clone + PartialEq + Debug,
Func::Output: Merge<Func::Output> + Clone + PartialEq + Debug,
{
for [a, b] in cartesian_power(items) {
assert_eq!(
func.call(Merge::merge_owned(a.clone(), b.clone())),
Merge::merge_owned(func.call(a.clone()), func.call(b.clone())),
"Func not a morphism: `f(a ⊔ b) != f(a) ⊔ f(b)`
\n`a = {:?}`, `b = {:?}`",
a,
b
)
}
}
pub fn check_lattice_bimorphism<LatA, LatB, Func>(
mut func: Func,
items_a: &[LatA],
items_b: &[LatB],
) where
Func: LatticeBimorphism<LatA, LatB>,
LatA: Merge<LatA> + Clone + PartialEq + Debug,
LatB: Merge<LatB> + Clone + PartialEq + Debug,
Func::Output: Merge<Func::Output> + Clone + PartialEq + Debug,
{
for b in items_b {
for [a, da] in cartesian_power(items_a) {
assert_eq!(
func.call(Merge::merge_owned(a.clone(), da.clone()), b.clone()),
Merge::merge_owned(
func.call(a.clone(), b.clone()),
func.call(da.clone(), b.clone())
),
"Left arg not a morphism: `f(a ⊔ da, b) != f(a, b) ⊔ f(da, b)`
\n`a = {:?}`, `da = {:?}`, `b = {:?}`",
a,
da,
b,
);
}
}
for a in items_a {
for [b, db] in cartesian_power(items_b) {
assert_eq!(
func.call(a.clone(), Merge::merge_owned(b.clone(), db.clone())),
Merge::merge_owned(
func.call(a.clone(), b.clone()),
func.call(a.clone(), db.clone())
),
"Right arg not a morphism: `f(a, b ⊔ db) != f(a, b) ⊔ f(a, db)`
\n`a = {:?}`, `b = {:?}`, `db = {:?}`",
a,
b,
db,
);
}
}
}
pub fn cartesian_power<T, const N: usize>(
items: &[T],
) -> impl ExactSizeIterator<Item = [&T; N]> + Clone {
struct CartesianPower<'a, T, const N: usize> {
items: &'a [T],
iters: [std::iter::Peekable<std::slice::Iter<'a, T>>; N],
}
impl<'a, T, const N: usize> Iterator for CartesianPower<'a, T, N> {
type Item = [&'a T; N];
fn next(&mut self) -> Option<Self::Item> {
if self.items.is_empty() {
return None;
}
let mut go_next = true;
let out = std::array::from_fn::<_, N, _>(|i| {
let iter = &mut self.iters[i];
let &item = iter.peek().unwrap();
if go_next {
iter.next();
if iter.peek().is_none() {
*iter = self.items.iter().peekable();
} else {
go_next = false;
}
}
item
});
if go_next {
self.items = &[];
};
Some(out)
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.items.is_empty() {
return (0, Some(0));
}
let mut pow = 1;
let mut passed = 0;
for iter in self.iters.iter() {
passed += pow * (self.items.len() - iter.len());
pow *= self.items.len();
}
let size = pow - passed;
(size, Some(size))
}
}
impl<T, const N: usize> ExactSizeIterator for CartesianPower<'_, T, N> {}
impl<T, const N: usize> Clone for CartesianPower<'_, T, N> {
fn clone(&self) -> Self {
Self {
items: self.items,
iters: self.iters.clone(),
}
}
}
let iters = std::array::from_fn::<_, N, _>(|_| items.iter().peekable());
CartesianPower { items, iters }
}
#[test]
fn test_cartesian_power() {
let items = &[1, 2, 3];
let mut iter = cartesian_power(items);
let mut len = 27;
assert_eq!(len, iter.len());
for x in items {
for y in items {
for z in items {
len -= 1;
assert_eq!(Some([z, y, x]), iter.next());
assert_eq!(len, iter.len());
}
}
}
}
#[test]
fn test_cartesian_power_zero() {
let mut iter = cartesian_power::<_, 0>(&[0, 1, 2]);
assert_eq!(1, iter.len());
assert_eq!(Some([]), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn test_cartesian_power_empty() {
let mut iter = cartesian_power::<_, 4>(&[] as &[usize]);
assert_eq!(0, iter.len());
assert_eq!(None, iter.next());
}