#![forbid(unsafe_code)]
#![no_std]
extern crate alloc;
pub mod symbolic;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Verdict<T> {
Proven { cases: u64 },
Refuted { counterexample: T, checked: u64 },
}
impl<T> Verdict<T> {
pub fn is_proven(&self) -> bool {
matches!(self, Verdict::Proven { .. })
}
pub fn cases(&self) -> u64 {
match self {
Verdict::Proven { cases } => *cases,
Verdict::Refuted { checked, .. } => *checked,
}
}
pub fn counterexample(&self) -> Option<&T> {
match self {
Verdict::Refuted { counterexample, .. } => Some(counterexample),
Verdict::Proven { .. } => None,
}
}
}
pub fn for_all<I, T, F>(inputs: I, pred: F) -> Verdict<T>
where
I: IntoIterator<Item = T>,
F: Fn(&T) -> bool,
{
let mut n = 0u64;
for x in inputs {
if !pred(&x) {
return Verdict::Refuted {
counterexample: x,
checked: n,
};
}
n += 1;
}
Verdict::Proven { cases: n }
}
pub fn for_all_where<I, T, P, F>(inputs: I, precond: P, pred: F) -> Verdict<T>
where
I: IntoIterator<Item = T>,
P: Fn(&T) -> bool,
F: Fn(&T) -> bool,
{
let mut n = 0u64;
for x in inputs {
if !precond(&x) {
continue;
}
if !pred(&x) {
return Verdict::Refuted {
counterexample: x,
checked: n,
};
}
n += 1;
}
Verdict::Proven { cases: n }
}
pub fn for_all_u8<F: Fn(u8) -> bool>(pred: F) -> Verdict<u8> {
for_all(0u8..=255, |&x| pred(x))
}
pub fn for_all_in<F: Fn(u64) -> bool>(lo: u64, hi: u64, pred: F) -> Verdict<u64> {
for_all(lo..=hi, |&x| pred(x))
}
pub fn for_all_pairs<A: Clone, B: Clone, F: Fn(&A, &B) -> bool>(
a: &[A],
b: &[B],
pred: F,
) -> Verdict<(A, B)> {
let mut n = 0u64;
for x in a {
for y in b {
if !pred(x, y) {
return Verdict::Refuted {
counterexample: (x.clone(), y.clone()),
checked: n,
};
}
n += 1;
}
}
Verdict::Proven { cases: n }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_all_proves_a_true_predicate_over_the_whole_domain() {
let v = for_all_u8(|x| (x as u16) + 1 > x as u16);
assert!(v.is_proven());
assert_eq!(v.cases(), 256, "every u8 checked — a proof, not a sample");
}
#[test]
fn for_all_refutes_and_returns_the_counterexample() {
let v = for_all_u8(|x| x < 200);
assert!(!v.is_proven());
assert_eq!(v.counterexample(), Some(&200u8));
assert_eq!(
v.cases(),
200,
"200 values passed before the counterexample"
);
}
#[test]
fn for_all_where_applies_a_precondition() {
let v = for_all_where(0u16..=255, |x| x % 2 == 0, |x| (x * 2) % 2 == 0);
assert!(v.is_proven());
assert_eq!(v.cases(), 128, "only the 128 even values were in scope");
}
#[test]
fn for_all_in_covers_a_bounded_range() {
let v = for_all_in(10, 20, |x| (10..=20).contains(&x));
assert!(v.is_proven());
assert_eq!(v.cases(), 11);
}
#[test]
fn for_all_pairs_covers_the_product_and_finds_a_bad_pair() {
let a = [1u32, 2, 3];
let b = [10u32, 20];
let good = for_all_pairs(&a, &b, |&x, &y| x + y == y + x);
assert!(good.is_proven());
assert_eq!(good.cases(), 6, "3 x 2 pairs all checked");
let bad = for_all_pairs(&a, &b, |&x, &y| x + y != 12);
assert_eq!(
bad.counterexample(),
Some(&(2u32, 10u32)),
"2+10==12 breaks it"
);
}
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
#[kani::unwind(18)]
fn for_all_in_is_sound() {
let b: u64 = kani::any();
kani::assume(b <= 16);
let v = for_all_in(0, b, |x| x <= b);
assert!(v.is_proven());
assert!(v.cases() == b + 1);
}
}