#[cfg(feature = "enabled")]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GhostState<T>(T);
#[cfg(not(feature = "enabled"))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GhostState<T>(::core::marker::PhantomData<T>);
#[cfg(feature = "enabled")]
impl<T> GhostState<T> {
pub fn new<F: Fn() -> T>(init: F) -> Self {
GhostState(init())
}
pub fn mutate<F: Fn(&mut T)>(&mut self, f: F) {
f(&mut self.0)
}
fn inner(&self) -> &T {
&self.0
}
}
#[cfg(not(feature = "enabled"))]
impl<T> GhostState<T> {
#[allow(unused_variables)]
pub fn new<F: Fn() -> T>(init: F) -> Self {
GhostState(::core::marker::PhantomData)
}
#[allow(unused_variables)]
pub fn mutate<F: Fn(&mut T)>(&mut self, f: F) {}
}
macro_rules! define_observe_helpers {
($( $name:ident ( $($ty:ident : $arg:ident),* ) ),* $(,)?) => {$(
#[cfg(feature = "enabled")]
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn $name<$($ty,)* F: Fn($(&$ty),*)>(
$($arg: &GhostState<$ty>,)* f: F,
) {
f($($arg.inner()),*)
}
#[cfg(not(feature = "enabled"))]
#[doc(hidden)]
#[allow(unused_variables, clippy::too_many_arguments)]
pub fn $name<$($ty,)* F: Fn($(&$ty),*)>(
$($arg: &GhostState<$ty>,)* f: F,
) {}
)*};
}
define_observe_helpers! {
__observe0(),
__observe1(T0: m0),
__observe2(T0: m0, T1: m1),
__observe3(T0: m0, T1: m1, T2: m2),
__observe4(T0: m0, T1: m1, T2: m2, T3: m3),
__observe5(T0: m0, T1: m1, T2: m2, T3: m3, T4: m4),
__observe6(T0: m0, T1: m1, T2: m2, T3: m3, T4: m4, T5: m5),
__observe7(T0: m0, T1: m1, T2: m2, T3: m3, T4: m4, T5: m5, T6: m6),
__observe8(T0: m0, T1: m1, T2: m2, T3: m3, T4: m4, T5: m5, T6: m6, T7: m7),
}
#[macro_export]
macro_rules! observe {
($closure:expr $(,)?) => {
$crate::ghost::__observe0($closure)
};
($m0:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe1(&$m0, $closure)
};
($m0:expr, $m1:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe2(&$m0, &$m1, $closure)
};
($m0:expr, $m1:expr, $m2:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe3(&$m0, &$m1, &$m2, $closure)
};
($m0:expr, $m1:expr, $m2:expr, $m3:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe4(&$m0, &$m1, &$m2, &$m3, $closure)
};
($m0:expr, $m1:expr, $m2:expr, $m3:expr, $m4:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe5(&$m0, &$m1, &$m2, &$m3, &$m4, $closure)
};
($m0:expr, $m1:expr, $m2:expr, $m3:expr, $m4:expr, $m5:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe6(&$m0, &$m1, &$m2, &$m3, &$m4, &$m5, $closure)
};
($m0:expr, $m1:expr, $m2:expr, $m3:expr, $m4:expr, $m5:expr, $m6:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe7(&$m0, &$m1, &$m2, &$m3, &$m4, &$m5, &$m6, $closure)
};
($m0:expr, $m1:expr, $m2:expr, $m3:expr, $m4:expr, $m5:expr, $m6:expr, $m7:expr, $closure:expr $(,)?) => {
$crate::ghost::__observe8(&$m0, &$m1, &$m2, &$m3, &$m4, &$m5, &$m6, &$m7, $closure)
};
($($rest:tt)*) => {
::std::compile_error!(
r#"Invalid syntax when calling macro `observe`.
Example usage:
`observe!(|| { /* read-only property checks */ })`
`observe!(ghost, |g: &T| { /* read-only checks over `g` and the environment */ })`
Up to 8 ghost states may be observed at once; the final argument must be the closure."#
);
};
}
#[cfg(test)]
mod tests {
use super::GhostState;
#[test]
fn compiles_and_runs_in_all_configs() {
let mut g = GhostState::new(|| 0i64);
g.mutate(|n| *n += 1);
observe!(g, |n: &i64| {
let _ = *n;
});
observe!(|| {});
}
#[cfg(feature = "enabled")]
#[test]
fn ghost_mutate_and_observe() {
let mut seen = GhostState::new(|| 0i64);
for _ in 0..3 {
seen.mutate(|n: &mut i64| *n += 1);
}
observe!(seen, |n: &i64| {
assert_eq!(*n, 3, "ghost state should have counted 3 events");
crate::expect_always!(*n >= 0, "seen count is never negative", { "seen": *n });
});
}
#[cfg(feature = "enabled")]
#[test]
fn ghost_multi_and_derives() {
let a = GhostState::new(|| 10u64);
let b = GhostState::new(|| String::from("hello"));
observe!(a, b, |x: &u64, y: &String| {
assert_eq!(*x, 10);
assert_eq!(y, "hello");
});
let b2 = b.clone(); assert_eq!(b, b2);
let a2 = a; assert_eq!(a, a2);
let d: GhostState<u64> = Default::default();
assert_eq!(format!("{d:?}"), "GhostState(0)");
}
#[cfg(feature = "enabled")]
#[test]
fn observe_no_ghost_state() {
let temperature = 42;
observe!(|| {
crate::expect_always!(temperature < 100, "not overheating", { "t": temperature });
});
}
}