extern crate alloc;
mod arbitrary;
pub mod fields;
pub mod hash;
mod impls;
mod instantiability;
pub mod multiset;
pub mod panic;
mod persist;
pub mod reflection;
pub mod registration;
mod scc;
mod shrink;
mod size;
mod swarm;
mod unavoidability;
mod union_find;
pub use {
pbt_macros::{Pbt, pbt},
wyrand::WyRand,
};
#[cfg(not(miri))]
pub const DEFAULT_N_CASES: usize = 1_000;
#[cfg(miri)]
pub const DEFAULT_N_CASES: usize = 50;
#[expect(
clippy::absolute_paths,
reason = "to avoid polluting the top-level namespace"
)]
pub trait Pbt: 'static + Clone + core::fmt::Debug {
fn construct<F>(parts: reflection::Parts<F>) -> Self
where
F: fields::Fields;
fn deconstruct(self) -> reflection::Parts<fields::Store>;
fn register(registration: &mut registration::Registration<'_>) -> reflection::Variants<Self>;
}
#[inline]
pub fn check_eta_expansion<T>()
where
T: PartialEq + Pbt,
{
let mut prng = wyrand::WyRand::new(getrandom());
let Ok(arbitrary) = arbitrary::arbitrary::<T>(&mut prng) else {
return;
};
for t in arbitrary.take(DEFAULT_N_CASES >> 2) {
let parts = t.clone().deconstruct();
let reconstructed = T::construct(parts);
pretty_assertions::assert_eq!(
reconstructed,
t,
"\r\n\r\n{t:?} -> Parts {{ .. }} -> {reconstructed:?} =/= {t:?}",
);
}
}
#[inline]
pub fn check_serialization<T>()
where
T: PartialEq + Pbt,
{
let mut prng = wyrand::WyRand::new(getrandom());
let Ok(arbitrary) = arbitrary::arbitrary::<T>(&mut prng) else {
return;
};
for t in arbitrary.take(DEFAULT_N_CASES >> 2) {
let json = t.clone().deconstruct().serialize();
let reconstructed: Option<T> = reflection::Parts::deserialize(&json);
pretty_assertions::assert_eq!(
reconstructed,
Some(t.clone()),
"\r\n\r\n{t:?} -> {json:?} -> {reconstructed:?} =/= Some({t:?})",
);
}
}
#[inline]
#[must_use]
#[expect(
clippy::expect_used,
reason = "Internal invariants: violations should fail loudly."
)]
pub fn getrandom() -> u64 {
getrandom::u64().expect("INTERNAL ERROR (`pbt`): `getrandom` failed")
}
#[inline]
pub fn witness<T, Property, Proof>(
property: Property,
cases: usize,
prng: &mut wyrand::WyRand,
) -> Option<(T, Proof)>
where
Property: Fn(&T) -> Option<Proof>,
T: Pbt,
{
let arbitrary = arbitrary::arbitrary::<T>(prng).ok()?;
for t in arbitrary.take(cases) {
if let Some(proof) = property(&t) {
return Some(shrink::to_minimal_witness(&property, t, proof));
}
}
None
}
#[cfg(test)]
mod tests {
use {super::*, pretty_assertions::assert_eq, wyrand::WyRand};
#[test]
fn witness_at_least_42() {
let mut prng = WyRand::new(42); assert_eq!(
witness(|i: &usize| i.checked_sub(42), DEFAULT_N_CASES, &mut prng),
Some((42, 0))
);
}
}