From 12d6f7f78c6062f474ae3ec4aaa54cb72efeb5b9 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Wed, 11 Dec 2024 21:56:13 -0500 Subject: [PATCH] util: Wrap std's range types instead of inventing our own This makes the type potentially more useful outside of just our current use case of just error messages. Ranges where the starting value is exclusive are no longer supported, but weren't used anyway. Signed-off-by: Andrew Gunnerson --- avbroot/src/util.rs | 253 +++++++++++++++++++++++++++++--------------- 1 file changed, 167 insertions(+), 86 deletions(-) diff --git a/avbroot/src/util.rs b/avbroot/src/util.rs index e2db1ad..2d984a3 100644 --- a/avbroot/src/util.rs +++ b/avbroot/src/util.rs @@ -4,7 +4,9 @@ use std::{ cmp::Ordering, fmt, mem, - ops::{Bound, Range, RangeBounds}, + ops::{ + Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, + }, path::Path, }; @@ -43,59 +45,118 @@ impl fmt::Debug for DebugString { } } -/// A single bound in a range. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum IntBound { - Included(T), - Excluded(T), +#[derive(Clone, Hash, PartialEq, Eq)] +pub enum AnyRange { + Range(Range), + RangeFrom(RangeFrom), + RangeFull(RangeFull), + RangeInclusive(RangeInclusive), + RangeTo(RangeTo), + RangeToInclusive(RangeToInclusive), } -/// A bounded primitive integer range. Unlike std's range types, this is a -/// single type that can represent open, closed, and half-open intervals. -#[derive(Clone, Copy, Debug)] -pub struct IntRange { - pub start: IntBound, - pub end: IntBound, -} - -impl IntRange { - /// Returns [`None`] if the range bounds cannot be represented by `T`. If - /// the start and end of `range` are unbounded, then it gets converted to - /// [`IntBound::Included`] with `N`'s minimum or maximum value. - pub fn new>(range: R) -> Option { - let start = match range.start_bound() { - Bound::Included(n) => IntBound::Included(T::from(*n)?), - Bound::Excluded(n) => IntBound::Excluded(T::from(*n)?), - Bound::Unbounded => IntBound::Included(T::from(N::min_value())?), +impl AnyRange { + pub fn with_bounds(start: Bound, end: Bound) -> Option { + let result = match (start, end) { + (Bound::Included(s), Bound::Excluded(e)) => Self::Range(s..e), + (Bound::Included(s), Bound::Unbounded) => Self::RangeFrom(s..), + (Bound::Unbounded, Bound::Unbounded) => Self::RangeFull(..), + (Bound::Included(s), Bound::Included(e)) => Self::RangeInclusive(s..=e), + (Bound::Unbounded, Bound::Excluded(e)) => Self::RangeTo(..e), + (Bound::Unbounded, Bound::Included(e)) => Self::RangeToInclusive(..=e), + (Bound::Excluded(_), _) => return None, }; - let end = match range.end_bound() { - Bound::Included(n) => IntBound::Included(T::from(*n)?), - Bound::Excluded(n) => IntBound::Excluded(T::from(*n)?), - Bound::Unbounded => IntBound::Included(T::from(N::max_value())?), - }; - - Some(Self { start, end }) + Some(result) } } -impl fmt::Display for IntRange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.start { - IntBound::Included(n) => write!(f, "[{n}, ")?, - IntBound::Excluded(n) => write!(f, "({n}, ")?, - } +impl> AnyRange { + pub fn contains(&self, item: &U) -> bool + where + T: PartialOrd, + U: ?Sized + PartialOrd, + { + >::contains(self, item) + } +} - match self.end { - IntBound::Included(n) => write!(f, "{n}]"), - IntBound::Excluded(n) => write!(f, "{n})"), +impl fmt::Debug for AnyRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Range(r) => r.fmt(f), + Self::RangeFrom(r) => r.fmt(f), + Self::RangeFull(r) => r.fmt(f), + Self::RangeInclusive(r) => r.fmt(f), + Self::RangeTo(r) => r.fmt(f), + Self::RangeToInclusive(r) => r.fmt(f), } } } +impl RangeBounds for AnyRange { + fn start_bound(&self) -> Bound<&T> { + match self { + Self::Range(r) => r.start_bound(), + Self::RangeFrom(r) => r.start_bound(), + Self::RangeFull(r) => r.start_bound(), + Self::RangeInclusive(r) => r.start_bound(), + Self::RangeTo(r) => r.start_bound(), + Self::RangeToInclusive(r) => r.start_bound(), + } + } + + fn end_bound(&self) -> Bound<&T> { + match self { + Self::Range(r) => r.end_bound(), + Self::RangeFrom(r) => r.end_bound(), + Self::RangeFull(r) => r.end_bound(), + Self::RangeInclusive(r) => r.end_bound(), + Self::RangeTo(r) => r.end_bound(), + Self::RangeToInclusive(r) => r.end_bound(), + } + } +} + +impl From> for AnyRange { + fn from(value: Range) -> Self { + Self::Range(value) + } +} + +impl From> for AnyRange { + fn from(value: RangeFrom) -> Self { + Self::RangeFrom(value) + } +} + +impl From for AnyRange { + fn from(value: RangeFull) -> Self { + Self::RangeFull(value) + } +} + +impl From> for AnyRange { + fn from(value: RangeInclusive) -> Self { + Self::RangeInclusive(value) + } +} + +impl From> for AnyRange { + fn from(value: RangeTo) -> Self { + Self::RangeTo(value) + } +} + +impl From> for AnyRange { + fn from(value: RangeToInclusive) -> Self { + Self::RangeToInclusive(value) + } +} + /// A non-generic type that can represent any 64-bit or smaller primitive /// integer. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum LargeInt { Signed(i64), Unsigned(u64), @@ -112,13 +173,13 @@ impl fmt::Display for LargeInt { /// A non-generic type that can represent any 64-bit or smaller primitive /// integer range. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, PartialEq, Eq)] pub enum LargeIntRange { - Signed(IntRange), - Unsigned(IntRange), + Signed(AnyRange), + Unsigned(AnyRange), } -impl fmt::Display for LargeIntRange { +impl fmt::Debug for LargeIntRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Signed(r) => r.fmt(f), @@ -128,8 +189,8 @@ impl fmt::Display for LargeIntRange { } /// An error returned when a value is not within a specific range. -#[derive(Clone, Copy, Debug, Error)] -#[error("Integer value {value} not in bounds: {range}")] +#[derive(Clone, Debug, Error)] +#[error("Integer value {value} not in bounds: {range:?}")] pub struct OutOfBoundsError { value: LargeInt, range: LargeIntRange, @@ -138,7 +199,7 @@ pub struct OutOfBoundsError { /// Verify that `value` is within `bounds` and then return `value` if it is. pub fn check_bounds( value: T, - bounds: impl RangeBounds, + range: impl Into>, ) -> Result { const { assert!( @@ -147,7 +208,9 @@ pub fn check_bounds( ); } - if !bounds.contains(&value) { + let range = range.into(); + + if !range.contains(&value) { let value = if T::min_value() != T::zero() { LargeInt::Signed(NumCast::from(value).unwrap()) } else { @@ -155,9 +218,33 @@ pub fn check_bounds( }; let range = if T::min_value() != T::zero() { - LargeIntRange::Signed(IntRange::new(bounds).unwrap()) + let start = match range.start_bound() { + Bound::Excluded(n) => Bound::Excluded(NumCast::from(*n).unwrap()), + Bound::Included(n) => Bound::Included(NumCast::from(*n).unwrap()), + Bound::Unbounded => Bound::Unbounded, + }; + + let end = match range.end_bound() { + Bound::Excluded(n) => Bound::Excluded(NumCast::from(*n).unwrap()), + Bound::Included(n) => Bound::Included(NumCast::from(*n).unwrap()), + Bound::Unbounded => Bound::Unbounded, + }; + + LargeIntRange::Signed(AnyRange::with_bounds(start, end).unwrap()) } else { - LargeIntRange::Unsigned(IntRange::new(bounds).unwrap()) + let start = match range.start_bound() { + Bound::Excluded(n) => Bound::Excluded(NumCast::from(*n).unwrap()), + Bound::Included(n) => Bound::Included(NumCast::from(*n).unwrap()), + Bound::Unbounded => Bound::Unbounded, + }; + + let end = match range.end_bound() { + Bound::Excluded(n) => Bound::Excluded(NumCast::from(*n).unwrap()), + Bound::Included(n) => Bound::Included(NumCast::from(*n).unwrap()), + Bound::Unbounded => Bound::Unbounded, + }; + + LargeIntRange::Unsigned(AnyRange::with_bounds(start, end).unwrap()) }; return Err(OutOfBoundsError { value, range }); @@ -184,9 +271,15 @@ pub fn try_cast(value: V) -> Result }; let range = if T::min_value() != T::zero() { - LargeIntRange::Signed(IntRange::new::(..).unwrap()) + let min = NumCast::from(T::min_value()).unwrap(); + let max = NumCast::from(T::max_value()).unwrap(); + + LargeIntRange::Signed((min..=max).into()) } else { - LargeIntRange::Unsigned(IntRange::new::(..).unwrap()) + let min = NumCast::from(T::min_value()).unwrap(); + let max = NumCast::from(T::max_value()).unwrap(); + + LargeIntRange::Unsigned((min..=max).into()) }; OutOfBoundsError { value, range } @@ -287,21 +380,27 @@ where #[cfg(test)] mod tests { - use assert_matches::assert_matches; - use super::*; #[test] - fn test_int_range() { - let range = IntRange::new::(..).unwrap(); - assert_eq!(range.start, IntBound::Included(u8::MIN)); - assert_eq!(range.end, IntBound::Included(u8::MAX)); + fn test_any_range() { + let range = AnyRange::with_bounds(Bound::Included(0), Bound::Excluded(1)).unwrap(); + assert_eq!(range, AnyRange::from(0..1)); - let range = IntRange::::new(-2i16..2i16).unwrap(); - assert_eq!(range.start, IntBound::Included(-2)); - assert_eq!(range.end, IntBound::Excluded(2)); + let range = AnyRange::with_bounds(Bound::Included(0), Bound::Unbounded).unwrap(); + assert_eq!(range, AnyRange::from(0..)); - assert!(IntRange::::new::(..).is_none()); + let range = AnyRange::::with_bounds(Bound::Unbounded, Bound::Unbounded).unwrap(); + assert_eq!(range, AnyRange::from(..)); + + let range = AnyRange::with_bounds(Bound::Included(0), Bound::Included(1)).unwrap(); + assert_eq!(range, AnyRange::from(0..=1)); + + let range = AnyRange::with_bounds(Bound::Unbounded, Bound::Excluded(1)).unwrap(); + assert_eq!(range, AnyRange::from(..1)); + + let range = AnyRange::with_bounds(Bound::Unbounded, Bound::Included(1)).unwrap(); + assert_eq!(range, AnyRange::from(..=1)); } #[test] @@ -313,24 +412,12 @@ mod tests { check_bounds(0, -1..=1).unwrap(); let err = check_bounds(i8::MAX, 0..=0).unwrap_err(); - assert_matches!(err.value, LargeInt::Signed(127)); - assert_matches!( - err.range, - LargeIntRange::Signed(IntRange { - start: IntBound::Included(0), - end: IntBound::Included(0), - }) - ); + assert_eq!(err.value, LargeInt::Signed(127)); + assert_eq!(err.range, LargeIntRange::Signed(AnyRange::from(0..=0))); let err = check_bounds(u8::MAX, 0..=0).unwrap_err(); - assert_matches!(err.value, LargeInt::Unsigned(255)); - assert_matches!( - err.range, - LargeIntRange::Unsigned(IntRange { - start: IntBound::Included(0), - end: IntBound::Included(0), - }) - ); + assert_eq!(err.value, LargeInt::Unsigned(255)); + assert_eq!(err.range, LargeIntRange::Unsigned(AnyRange::from(0..=0))); } #[test] @@ -339,14 +426,8 @@ mod tests { assert_eq!(value, 255); let err = try_cast::(256u16).unwrap_err(); - assert_matches!(err.value, LargeInt::Unsigned(256)); - assert_matches!( - err.range, - LargeIntRange::Signed(IntRange { - start: IntBound::Included(-128), - end: IntBound::Included(127), - }) - ); + assert_eq!(err.value, LargeInt::Unsigned(256)); + assert_eq!(err.range, LargeIntRange::Signed(AnyRange::from(-128..=127))); } #[test]