use core::{
num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize},
ops::{Bound, RangeBounds},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct RangeCfg<T: Copy + PartialOrd> {
start: Bound<T>,
end: Bound<T>,
}
impl<T: Copy + PartialOrd> From<core::ops::Range<T>> for RangeCfg<T> {
fn from(r: core::ops::Range<T>) -> Self {
Self::new(r)
}
}
impl<T: Copy + PartialOrd> From<core::ops::RangeInclusive<T>> for RangeCfg<T> {
fn from(r: core::ops::RangeInclusive<T>) -> Self {
Self::new(r)
}
}
impl<T: Copy + PartialOrd> From<core::ops::RangeFrom<T>> for RangeCfg<T> {
fn from(r: core::ops::RangeFrom<T>) -> Self {
Self::new(r)
}
}
impl<T: Copy + PartialOrd> From<core::ops::RangeTo<T>> for RangeCfg<T> {
fn from(r: core::ops::RangeTo<T>) -> Self {
Self::new(r)
}
}
impl<T: Copy + PartialOrd> From<core::ops::RangeToInclusive<T>> for RangeCfg<T> {
fn from(r: core::ops::RangeToInclusive<T>) -> Self {
Self::new(r)
}
}
impl<T: Copy + PartialOrd> From<core::ops::RangeFull> for RangeCfg<T> {
fn from(_: core::ops::RangeFull) -> Self {
Self::new(..)
}
}
macro_rules! impl_from_nonzero {
($nz_ty:ty, $ty:ty) => {
impl From<RangeCfg<$nz_ty>> for RangeCfg<$ty> {
fn from(value: RangeCfg<$nz_ty>) -> Self {
let start = match value.start {
Bound::Included(nz) => Bound::Included(nz.get()),
Bound::Excluded(nz) => Bound::Excluded(nz.get()),
Bound::Unbounded => Bound::Unbounded,
};
let end = match value.end {
Bound::Included(nz) => Bound::Included(nz.get()),
Bound::Excluded(nz) => Bound::Excluded(nz.get()),
Bound::Unbounded => Bound::Unbounded,
};
RangeCfg { start, end }
}
}
};
}
impl_from_nonzero!(NonZeroUsize, usize);
impl_from_nonzero!(NonZeroU8, u8);
impl_from_nonzero!(NonZeroU16, u16);
impl_from_nonzero!(NonZeroU32, u32);
impl_from_nonzero!(NonZeroU64, u64);
macro_rules! impl_from_nonzero_to_usize {
($from_ty:ty) => {
impl From<RangeCfg<$from_ty>> for RangeCfg<usize> {
fn from(value: RangeCfg<$from_ty>) -> Self {
let start = match value.start {
Bound::Included(v) => Bound::Included(
usize::try_from(v.get()).expect("range start exceeds usize"),
),
Bound::Excluded(v) => Bound::Excluded(
usize::try_from(v.get()).expect("range start exceeds usize"),
),
Bound::Unbounded => Bound::Unbounded,
};
let end = match value.end {
Bound::Included(v) => {
Bound::Included(usize::try_from(v.get()).expect("range end exceeds usize"))
}
Bound::Excluded(v) => {
Bound::Excluded(usize::try_from(v.get()).expect("range end exceeds usize"))
}
Bound::Unbounded => Bound::Unbounded,
};
RangeCfg { start, end }
}
}
};
}
impl_from_nonzero_to_usize!(NonZeroU8);
impl_from_nonzero_to_usize!(NonZeroU16);
impl_from_nonzero_to_usize!(NonZeroU32);
macro_rules! impl_nonzero_to_nonzero_usize {
($from_ty:ty) => {
impl From<RangeCfg<$from_ty>> for RangeCfg<NonZeroUsize> {
fn from(value: RangeCfg<$from_ty>) -> Self {
let start = match value.start {
Bound::Included(v) => Bound::Included(
NonZeroUsize::try_from(v).expect("range start exceeds usize"),
),
Bound::Excluded(v) => Bound::Excluded(
NonZeroUsize::try_from(v).expect("range start exceeds usize"),
),
Bound::Unbounded => Bound::Unbounded,
};
let end = match value.end {
Bound::Included(v) => {
Bound::Included(NonZeroUsize::try_from(v).expect("range end exceeds usize"))
}
Bound::Excluded(v) => {
Bound::Excluded(NonZeroUsize::try_from(v).expect("range end exceeds usize"))
}
Bound::Unbounded => Bound::Unbounded,
};
RangeCfg { start, end }
}
}
};
}
impl_nonzero_to_nonzero_usize!(NonZeroU8);
impl_nonzero_to_nonzero_usize!(NonZeroU16);
impl_nonzero_to_nonzero_usize!(NonZeroU32);
impl<T: Copy + PartialOrd> RangeCfg<T> {
pub fn new(r: impl RangeBounds<T>) -> Self {
Self {
start: r.start_bound().cloned(),
end: r.end_bound().cloned(),
}
}
pub const fn exact(value: T) -> Self {
Self {
start: Bound::Included(value),
end: Bound::Included(value),
}
}
pub fn contains(&self, value: &T) -> bool {
match &self.start {
Bound::Included(s) if value < s => return false,
Bound::Excluded(s) if value <= s => return false,
_ => {}
}
match &self.end {
Bound::Included(e) if value > e => return false,
Bound::Excluded(e) if value >= e => return false,
_ => {}
}
true
}
}
impl<T: Copy + PartialOrd> RangeBounds<T> for RangeCfg<T> {
fn start_bound(&self) -> Bound<&T> {
self.start.as_ref()
}
fn end_bound(&self) -> Bound<&T> {
self.end.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::ops::Bound::{Excluded, Included, Unbounded};
#[test]
fn test_range_cfg_from() {
let cfg_full: RangeCfg<usize> = (..).into();
assert_eq!(
cfg_full,
RangeCfg {
start: Unbounded,
end: Unbounded
}
);
let cfg_start_incl: RangeCfg<usize> = (5..).into();
assert_eq!(
cfg_start_incl,
RangeCfg {
start: Included(5),
end: Unbounded
}
);
let cfg_end_excl: RangeCfg<usize> = (..10).into();
assert_eq!(
cfg_end_excl,
RangeCfg {
start: Unbounded,
end: Excluded(10)
}
);
let cfg_end_incl: RangeCfg<usize> = (..=10).into();
assert_eq!(
cfg_end_incl,
RangeCfg {
start: Unbounded,
end: Included(10)
}
);
let cfg_incl_excl: RangeCfg<usize> = (5..10).into();
assert_eq!(
cfg_incl_excl,
RangeCfg {
start: Included(5),
end: Excluded(10)
}
);
let cfg_incl_incl: RangeCfg<usize> = (5..=10).into();
assert_eq!(
cfg_incl_incl,
RangeCfg {
start: Included(5),
end: Included(10)
}
);
struct ExclusiveStartRange(usize, usize);
impl RangeBounds<usize> for ExclusiveStartRange {
fn start_bound(&self) -> Bound<&usize> {
Excluded(&self.0)
}
fn end_bound(&self) -> Bound<&usize> {
Included(&self.1)
}
}
let cfg_excl_incl = RangeCfg::new(ExclusiveStartRange(5, 10));
assert_eq!(
cfg_excl_incl,
RangeCfg {
start: Excluded(5),
end: Included(10)
}
);
}
#[test]
fn test_range_cfg_contains() {
let cfg_unbounded: RangeCfg<usize> = (..).into();
assert!(cfg_unbounded.contains(&0));
assert!(cfg_unbounded.contains(&100));
assert!(cfg_unbounded.contains(&usize::MAX));
let cfg_start_incl: RangeCfg<usize> = (5..).into();
assert!(!cfg_start_incl.contains(&4));
assert!(cfg_start_incl.contains(&5));
assert!(cfg_start_incl.contains(&6));
assert!(cfg_start_incl.contains(&usize::MAX));
let cfg_end_excl: RangeCfg<usize> = (..10).into();
assert!(cfg_end_excl.contains(&0));
assert!(cfg_end_excl.contains(&9));
assert!(!cfg_end_excl.contains(&10));
assert!(!cfg_end_excl.contains(&11));
let cfg_end_incl: RangeCfg<usize> = (..=10).into();
assert!(cfg_end_incl.contains(&0));
assert!(cfg_end_incl.contains(&9));
assert!(cfg_end_incl.contains(&10));
assert!(!cfg_end_incl.contains(&11));
let cfg_incl_excl: RangeCfg<usize> = (5..10).into();
assert!(!cfg_incl_excl.contains(&4));
assert!(cfg_incl_excl.contains(&5));
assert!(cfg_incl_excl.contains(&9));
assert!(!cfg_incl_excl.contains(&10));
assert!(!cfg_incl_excl.contains(&11));
let cfg_incl_incl: RangeCfg<usize> = (5..=10).into();
assert!(!cfg_incl_incl.contains(&4));
assert!(cfg_incl_incl.contains(&5));
assert!(cfg_incl_incl.contains(&9));
assert!(cfg_incl_incl.contains(&10));
assert!(!cfg_incl_incl.contains(&11));
let cfg_excl_incl = RangeCfg {
start: Excluded(5),
end: Included(10),
};
assert!(!cfg_excl_incl.contains(&4));
assert!(!cfg_excl_incl.contains(&5)); assert!(cfg_excl_incl.contains(&6));
assert!(cfg_excl_incl.contains(&10)); assert!(!cfg_excl_incl.contains(&11));
let cfg_excl_excl = RangeCfg {
start: Excluded(5),
end: Excluded(10),
};
assert!(!cfg_excl_excl.contains(&5)); assert!(cfg_excl_excl.contains(&6));
assert!(cfg_excl_excl.contains(&9));
assert!(!cfg_excl_excl.contains(&10)); }
#[test]
fn test_contains_empty_range() {
let cfg_empty_excl: RangeCfg<usize> = (5..5).into();
assert!(!cfg_empty_excl.contains(&4));
assert!(!cfg_empty_excl.contains(&5));
assert!(!cfg_empty_excl.contains(&6));
#[allow(clippy::reversed_empty_ranges)]
let cfg_empty_incl: RangeCfg<usize> = (6..=5).into();
assert!(!cfg_empty_incl.contains(&5));
assert!(!cfg_empty_incl.contains(&6));
}
#[test]
fn test_range_cfg_u8() {
let cfg = RangeCfg::new(0u8..=255u8);
assert!(cfg.contains(&0));
assert!(cfg.contains(&128));
assert!(cfg.contains(&255));
let cfg_partial = RangeCfg::new(10u8..20u8);
assert!(!cfg_partial.contains(&9));
assert!(cfg_partial.contains(&10));
assert!(cfg_partial.contains(&19));
assert!(!cfg_partial.contains(&20));
}
#[test]
fn test_range_cfg_u16() {
let cfg = RangeCfg::new(100u16..=1000u16);
assert!(!cfg.contains(&99));
assert!(cfg.contains(&100));
assert!(cfg.contains(&500));
assert!(cfg.contains(&1000));
assert!(!cfg.contains(&1001));
}
#[test]
fn test_range_cfg_u32() {
let cfg = RangeCfg::new(0u32..1024u32);
assert!(cfg.contains(&0));
assert!(cfg.contains(&512));
assert!(!cfg.contains(&1024));
assert!(!cfg.contains(&2000));
}
#[test]
fn test_range_cfg_u64() {
let cfg = RangeCfg::new(1000u64..);
assert!(!cfg.contains(&999));
assert!(cfg.contains(&1000));
assert!(cfg.contains(&u64::MAX));
}
#[test]
fn test_type_inference() {
let cfg = RangeCfg::new(0u8..10u8);
assert!(cfg.contains(&5u8));
let cfg: RangeCfg<u32> = RangeCfg::new(0..1000);
assert!(cfg.contains(&500));
}
}