More tests for DefaultFilterable + impl for floats

This commit is contained in:
René Kijewski 2025-07-21 18:11:41 +02:00 committed by René Kijewski
parent ba7b0a7967
commit 564a8463ee

View File

@ -1,5 +1,5 @@
use core::convert::Infallible;
use core::num::{Saturating, Wrapping};
use core::num::{self, FpCategory, Saturating, Wrapping};
use core::ops::Deref;
use core::pin::Pin;
@ -58,6 +58,7 @@ impl_for_ref! {
}
}
/// A [pinned][Pin] reference has a value if the referenced data has a value.
impl<T> DefaultFilterable for Pin<T>
where
T: Deref,
@ -76,6 +77,7 @@ where
}
}
/// An [`Option`] has a value if it is `Some`.
impl<T> DefaultFilterable for Option<T> {
type Filtered<'a>
= &'a T
@ -90,6 +92,7 @@ impl<T> DefaultFilterable for Option<T> {
}
}
/// A [`Result`] has a value if it is `Ok`.
impl<T, E> DefaultFilterable for Result<T, E> {
type Filtered<'a>
= &'a T
@ -104,6 +107,7 @@ impl<T, E> DefaultFilterable for Result<T, E> {
}
}
/// A [`str`] has a value if it is not empty.
impl DefaultFilterable for str {
type Filtered<'a>
= &'a str
@ -121,6 +125,7 @@ impl DefaultFilterable for str {
}
}
/// A [`String`][alloc::string::String] has a value if it is not empty.
#[cfg(feature = "alloc")]
impl DefaultFilterable for alloc::string::String {
type Filtered<'a>
@ -136,6 +141,7 @@ impl DefaultFilterable for alloc::string::String {
}
}
/// A [`Cow`][alloc::borrow::Cow] has a value if it's borrowed data has a value.
#[cfg(feature = "alloc")]
impl<T: DefaultFilterable + alloc::borrow::ToOwned + ?Sized> DefaultFilterable
for alloc::borrow::Cow<'_, T>
@ -153,6 +159,7 @@ impl<T: DefaultFilterable + alloc::borrow::ToOwned + ?Sized> DefaultFilterable
}
}
/// A [`Wrapping`] integer has a value if it is not `0`.
impl<T: DefaultFilterable> DefaultFilterable for Wrapping<T> {
type Filtered<'a>
= T::Filtered<'a>
@ -167,6 +174,7 @@ impl<T: DefaultFilterable> DefaultFilterable for Wrapping<T> {
}
}
/// A [`Saturating`] integer has a value if it is not `0`.
impl<T: DefaultFilterable> DefaultFilterable for Saturating<T> {
type Filtered<'a>
= T::Filtered<'a>
@ -183,6 +191,7 @@ impl<T: DefaultFilterable> DefaultFilterable for Saturating<T> {
macro_rules! impl_for_int {
($($ty:ty)*) => { $(
#[doc = concat!("A [`", stringify!($ty), "`] has a value if it is not `0`.")]
impl DefaultFilterable for $ty {
type Filtered<'a> = $ty;
type Error = Infallible;
@ -205,7 +214,8 @@ impl_for_int!(
macro_rules! impl_for_non_zero {
($($name:ident : $ty:ty)*) => { $(
impl DefaultFilterable for core::num::$name {
#[doc = concat!("A [`", stringify!($name), "`][num::", stringify!($name),"] always has a value.")]
impl DefaultFilterable for num::$name {
type Filtered<'a> = $ty;
type Error = Infallible;
@ -222,6 +232,7 @@ impl_for_non_zero!(
NonZeroI8:i8 NonZeroI16:i16 NonZeroI32:i32 NonZeroI64:i64 NonZeroI128:i128 NonZeroIsize:isize
);
/// A `bool` has a value if it is [`true`].
impl DefaultFilterable for bool {
type Filtered<'a> = bool;
type Error = Infallible;
@ -234,3 +245,159 @@ impl DefaultFilterable for bool {
}
}
}
/// An `f32` has a value if it is [`Normal`][FpCategory::Normal], i.e. it is not zero,
/// not sub-normal, not infinite and not NaN.
impl DefaultFilterable for f32 {
type Filtered<'a>
= Self
where
Self: 'a;
type Error = Infallible;
#[inline]
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
Ok((self.classify() == FpCategory::Normal).then_some(*self))
}
}
/// An `f64` has a value if it is [`Normal`](FpCategory::Normal), i.e. it is not zero,
/// not sub-normal, not infinite and not NaN.
impl DefaultFilterable for f64 {
type Filtered<'a>
= Self
where
Self: 'a;
type Error = Infallible;
#[inline]
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
Ok((self.classify() == FpCategory::Normal).then_some(*self))
}
}
#[test]
#[cfg(feature = "std")]
fn test_default_filterable() {
use std::borrow::Cow;
use std::rc::Rc;
use std::string::ToString;
use std::sync::{Arc, Mutex};
use assert_matches::assert_matches;
// integers
assert_matches!(0_u8.as_filtered(), Ok(None));
assert_matches!(0_u16.as_filtered(), Ok(None));
assert_matches!(0_u32.as_filtered(), Ok(None));
assert_matches!(0_u64.as_filtered(), Ok(None));
assert_matches!(0_u128.as_filtered(), Ok(None));
assert_matches!(0_usize.as_filtered(), Ok(None));
assert_matches!(0_i8.as_filtered(), Ok(None));
assert_matches!(0_i16.as_filtered(), Ok(None));
assert_matches!(0_i32.as_filtered(), Ok(None));
assert_matches!(0_i64.as_filtered(), Ok(None));
assert_matches!(0_i128.as_filtered(), Ok(None));
assert_matches!(0_isize.as_filtered(), Ok(None));
assert_matches!(1_u8.as_filtered(), Ok(Some(1)));
assert_matches!(1_u16.as_filtered(), Ok(Some(1)));
assert_matches!(1_u32.as_filtered(), Ok(Some(1)));
assert_matches!(1_u64.as_filtered(), Ok(Some(1)));
assert_matches!(1_u128.as_filtered(), Ok(Some(1)));
assert_matches!(1_usize.as_filtered(), Ok(Some(1)));
assert_matches!(1_i8.as_filtered(), Ok(Some(1)));
assert_matches!(1_i16.as_filtered(), Ok(Some(1)));
assert_matches!(1_i32.as_filtered(), Ok(Some(1)));
assert_matches!(1_i64.as_filtered(), Ok(Some(1)));
assert_matches!(1_i128.as_filtered(), Ok(Some(1)));
assert_matches!(1_isize.as_filtered(), Ok(Some(1)));
assert_matches!((-1_i8).as_filtered(), Ok(Some(-1)));
assert_matches!((-1_i16).as_filtered(), Ok(Some(-1)));
assert_matches!((-1_i32).as_filtered(), Ok(Some(-1)));
assert_matches!((-1_i64).as_filtered(), Ok(Some(-1)));
assert_matches!((-1_i128).as_filtered(), Ok(Some(-1)));
assert_matches!((-1_isize).as_filtered(), Ok(Some(-1)));
// floats
// -> zero
assert_matches!(0_f32.as_filtered(), Ok(None));
assert_matches!(0_f64.as_filtered(), Ok(None));
// -> subnormal
assert_matches!((f32::MIN_POSITIVE / 2.0).as_filtered(), Ok(None));
assert_matches!((f64::MIN_POSITIVE / 2.0).as_filtered(), Ok(None));
// -> nan
assert_matches!(f32::NAN.as_filtered(), Ok(None));
assert_matches!(f64::NAN.as_filtered(), Ok(None));
// -> infinite
assert_matches!(f32::NEG_INFINITY.as_filtered(), Ok(None));
assert_matches!(f32::INFINITY.as_filtered(), Ok(None));
assert_matches!(f64::NEG_INFINITY.as_filtered(), Ok(None));
assert_matches!(f64::INFINITY.as_filtered(), Ok(None));
// -> normal
assert_matches!(1_f32.as_filtered(), Ok(Some(1.0)));
assert_matches!((-1_f32).as_filtered(), Ok(Some(-1.0)));
assert_matches!(f32::MIN.as_filtered(), Ok(Some(f32::MIN)));
assert_matches!(f32::MIN_POSITIVE.as_filtered(), Ok(Some(f32::MIN_POSITIVE)));
assert_matches!(f32::MAX.as_filtered(), Ok(Some(f32::MAX)));
assert_matches!(1_f64.as_filtered(), Ok(Some(1.0)));
assert_matches!((-1_f64).as_filtered(), Ok(Some(-1.0)));
assert_matches!(f64::MIN.as_filtered(), Ok(Some(f64::MIN)));
assert_matches!(f64::MIN_POSITIVE.as_filtered(), Ok(Some(f64::MIN_POSITIVE)));
assert_matches!(f64::MAX.as_filtered(), Ok(Some(f64::MAX)));
// non-zero integers
assert_matches!(num::NonZeroU8::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroU16::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroU32::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroU64::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroU128::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(
num::NonZeroUsize::new(1).unwrap().as_filtered(),
Ok(Some(1))
);
assert_matches!(num::NonZeroI8::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroI16::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroI32::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroI64::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(num::NonZeroI128::new(1).unwrap().as_filtered(), Ok(Some(1)));
assert_matches!(
num::NonZeroIsize::new(1).unwrap().as_filtered(),
Ok(Some(1))
);
// strings
assert_matches!("".as_filtered(), Ok(None));
assert_matches!("hello".as_filtered(), Ok(Some("hello")));
assert_matches!("".to_string().as_filtered(), Ok(None));
assert_matches!("hello".to_string().as_filtered(), Ok(Some("hello")));
assert_matches!(Cow::Borrowed("").as_filtered(), Ok(None));
assert_matches!(Cow::Borrowed("hello").as_filtered(), Ok(Some("hello")));
assert_matches!(Cow::<str>::Owned("".to_string()).as_filtered(), Ok(None));
assert_matches!(
Cow::<str>::Owned("hello".to_string()).as_filtered(),
Ok(Some("hello"))
);
// results + options
assert_matches!(Ok::<(), ()>(()).as_filtered(), Ok(Some(())));
assert_matches!(Err::<(), ()>(()).as_filtered(), Ok(None));
assert_matches!(Some(()).as_filtered(), Ok(Some(())));
assert_matches!(None::<()>.as_filtered(), Ok(None));
// references
assert_matches!(Arc::new("").as_filtered(), Ok(None));
assert_matches!(Arc::new("hello").as_filtered(), Ok(Some("hello")));
assert_matches!(Arc::pin("").as_filtered(), Ok(None));
assert_matches!(Arc::pin("hello").as_filtered(), Ok(Some("hello")));
assert_matches!(Rc::new("").as_filtered(), Ok(None));
assert_matches!(Rc::new("hello").as_filtered(), Ok(Some("hello")));
assert_matches!(Rc::pin("").as_filtered(), Ok(None));
assert_matches!(Rc::pin("hello").as_filtered(), Ok(Some("hello")));
assert_matches!(Mutex::new("").try_lock().unwrap().as_filtered(), Ok(None));
assert_matches!(
Mutex::new("hello").try_lock().unwrap().as_filtered(),
Ok(Some("hello"))
);
}