pub const MAX_COMPUTATION_BITS: usize = if usize::BITS >= 32 {
#[allow(clippy::as_conversions)] {
u32::MAX as usize
}
} else {
usize::MAX
};
#[macro_export]
macro_rules! detected_computable_with_infinite_value {
($msg:expr) => {
debug_assert!(
false,
concat!($msg, " - unexpected but may be valid for extended reals")
)
};
}
#[macro_export]
macro_rules! detected_computable_would_exhaust_memory {
($msg:expr) => {
panic!(concat!($msg, " - would exhaust memory if attempted"))
};
}
#[macro_export]
macro_rules! assert_sane_computation_size {
($val:expr) => {
let __val: usize = $val;
if __val > $crate::MAX_COMPUTATION_BITS {
$crate::detected_computable_would_exhaust_memory!(concat!(
stringify!($val),
" exceeds MAX_COMPUTATION_BITS"
));
}
};
}
#[macro_export]
macro_rules! sane_arithmetic {
($($guard:ident),+ ; $expr:expr) => {{
$(
#[allow(clippy::shadow_reuse)]
let $guard = $crate::Sane($guard);
)+
let $crate::Sane(__result) = { $expr };
$crate::assert_sane_computation_size!(__result);
__result
}};
}
pub fn bits_as_usize(bits: u64) -> usize {
#[allow(clippy::as_conversions)] let max = MAX_COMPUTATION_BITS as u64;
if bits > max {
detected_computable_would_exhaust_memory!("bit count exceeds MAX_COMPUTATION_BITS");
}
#[allow(clippy::as_conversions)] {
bits as usize
}
}
pub fn sub_one(n: std::num::NonZeroUsize) -> usize {
#[allow(clippy::arithmetic_side_effects)]
{
n.get() - 1
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Sane(pub usize);
const _: () = assert!(usize::BITS <= 64);
const _: () = assert!(u32::BITS <= usize::BITS);
macro_rules! impl_sane_binary_op {
($trait:ident, $method:ident, $checked:ident, $msg:literal) => {
impl core::ops::$trait for Sane {
type Output = Sane;
#[inline]
fn $method(self, rhs: Sane) -> Sane {
match self.0.$checked(rhs.0) {
Some(r) => Sane(r),
None => crate::detected_computable_would_exhaust_memory!($msg),
}
}
}
impl core::ops::$trait<u32> for Sane {
type Output = Sane;
#[inline]
fn $method(self, rhs: u32) -> Sane {
#[allow(clippy::as_conversions)]
core::ops::$trait::$method(self, Sane(rhs as usize))
}
}
impl core::ops::$trait<Sane> for u32 {
type Output = Sane;
#[inline]
fn $method(self, rhs: Sane) -> Sane {
#[allow(clippy::as_conversions)]
core::ops::$trait::$method(Sane(self as usize), rhs)
}
}
};
}
impl_sane_binary_op!(Add, add, checked_add, "Sane addition overflow");
impl_sane_binary_op!(Sub, sub, checked_sub, "Sane subtraction underflow");
impl_sane_binary_op!(Mul, mul, checked_mul, "Sane multiplication overflow");
impl_sane_binary_op!(Div, div, checked_div, "Sane division by zero");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detected_computable_with_infinite_value_macro_compiles() {
#[cfg(not(debug_assertions))]
{
detected_computable_with_infinite_value!("test message");
}
}
#[test]
#[should_panic(expected = "test message")]
#[cfg(debug_assertions)]
fn detected_computable_with_infinite_value_macro_panics_in_debug() {
detected_computable_with_infinite_value!("test message");
}
#[test]
#[should_panic(expected = "test message")]
fn detected_computable_would_exhaust_memory_macro_panics() {
detected_computable_would_exhaust_memory!("test message");
}
#[test]
fn sane_arithmetic_macro_works() {
let n: usize = 10;
let result = crate::sane_arithmetic!(n; 2 * n + 1);
assert_eq!(result, 21_usize);
}
#[test]
#[should_panic(expected = "Sane multiplication overflow")]
fn sane_mul_overflow_panics() {
let _ = Sane(usize::MAX) * Sane(2);
}
#[test]
#[should_panic(expected = "Sane subtraction underflow")]
fn sane_sub_underflow_panics() {
let _ = Sane(3) - Sane(5);
}
#[test]
#[should_panic(expected = "Sane division by zero")]
fn sane_div_by_zero_panics() {
let _ = Sane(10) / Sane(0);
}
#[test]
#[should_panic(expected = "exceeds MAX_COMPUTATION_BITS")]
fn sane_arithmetic_macro_rejects_large_result() {
let n: usize = MAX_COMPUTATION_BITS;
let _ = crate::sane_arithmetic!(n; n + 1);
}
}