ranges 0.3.3

This crate provides a generic alternative to core/std ranges, set-operations to work with them and a range set that can efficiently store them with the least amount of memory possible.
Documentation
use core::ops::{Bound, Not};

use crate::{Arrangement, Domain, GenericRange, OperationResult};

impl<T> GenericRange<T> {
    /// Inverts a range border, so a start becomes an end and vice versa.
    ///
    /// # Example
    /// ```
    /// use core::ops::Bound;
    /// use ranges::GenericRange;
    ///
    /// assert_eq!(GenericRange::invert_border(Bound::<usize>::Unbounded), Bound::Unbounded);
    /// assert_eq!(GenericRange::invert_border(Bound::Excluded(42)), Bound::Included(42));
    /// assert_eq!(GenericRange::invert_border(Bound::Included(42)), Bound::Excluded(42));
    /// ```
    #[must_use]
    #[allow(clippy::missing_const_for_fn)]
    // we can not make this const yet, because `bound` gets dropped,
    // might be a false positive
    pub fn invert_border(bound: Bound<T>) -> Bound<T> {
        match bound {
            Bound::Unbounded => Bound::Unbounded,
            Bound::Excluded(val) => Bound::Included(val),
            Bound::Included(val) => Bound::Excluded(val),
        }
    }

    /// Inverts a range border, so a start becomes an end and vice versa.
    /// Explicitly clones the contents.
    #[must_use]
    pub fn invert_border_cloned(bound: &Bound<T>) -> Bound<T>
    where
        T: Clone,
    {
        Self::invert_border(bound.clone())
    }

    /// Inverts the given range and returns two if no bound is unbounded.
    ///
    /// # Example
    /// ```
    /// use ranges::{GenericRange, OperationResult};
    ///
    /// assert_eq!(!GenericRange::<usize>::full(), OperationResult::Empty);
    /// assert_eq!(
    ///     !GenericRange::new_at_most(5),
    ///     OperationResult::Single(GenericRange::new_greater_than(5))
    /// );
    /// assert_eq!(
    ///     !GenericRange::new_at_least(42),
    ///     OperationResult::Single(GenericRange::new_less_than(42))
    /// );
    /// assert_eq!(
    ///     !GenericRange::from(5..42),
    ///     OperationResult::Double(GenericRange::new_less_than(5), GenericRange::new_at_least(42))
    /// )
    /// ```
    ///
    /// # Panics
    /// An inversion is not possible if the domain range is empty and thus panics.
    pub fn invert(self) -> OperationResult<T>
    where
        T: Domain,
    {
        let domain_range = Self {
            start: T::minimum(),
            end: T::maximum(),
        };

        match self.arrangement(&domain_range) {
            Arrangement::Disjoint { .. }
            | Arrangement::Touching { .. }
            | Arrangement::Overlapping { .. }
            | Arrangement::Containing { self_shorter: false } => {
                unreachable!("constructor guarantees broken: self is outside of domain")
            }
            Arrangement::Containing { self_shorter: true } => OperationResult::Double(
                Self {
                    start: T::minimum(),
                    end: Self::invert_border(self.start),
                },
                Self {
                    start: Self::invert_border(self.end),
                    end: T::maximum(),
                },
            ),
            Arrangement::Starting { .. } => OperationResult::Single(Self {
                start: Self::invert_border(self.end),
                end: T::maximum(),
            }),
            Arrangement::Ending { .. } => OperationResult::Single(Self {
                start: T::minimum(),
                end: Self::invert_border(self.start),
            }),
            Arrangement::Equal => OperationResult::Empty,
            Arrangement::Empty { self_empty: Some(true) } => {
                // ∅′ = U
                OperationResult::Single(domain_range)
            }
            #[allow(clippy::panic)]
            Arrangement::Empty {
                self_empty: Some(false),
            }
            | Arrangement::Empty { self_empty: None } => panic!("domain is empty, which makes inversion impossible"),
        }
    }
}

/// This calls [`self.invert()`](#method.invert).
impl<T: Domain> Not for GenericRange<T> {
    type Output = OperationResult<T>;

    #[must_use]
    fn not(self) -> Self::Output {
        self.invert()
    }
}

#[cfg(test)]
mod tests {
    use core::ops::Bound;

    use crate::{GenericRange, OperationResult};

    #[test]
    fn invert_border() {
        assert_eq!(GenericRange::invert_border(Bound::<usize>::Unbounded), Bound::Unbounded);
        assert_eq!(GenericRange::invert_border(Bound::Excluded(42)), Bound::Included(42));
        assert_eq!(GenericRange::invert_border(Bound::Included(42)), Bound::Excluded(42));
    }

    #[test]
    fn invert() {
        assert_eq!(GenericRange::<usize>::full().invert(), OperationResult::<usize>::Empty);
        assert_eq!(
            GenericRange::new_at_most(5).invert(),
            OperationResult::Single(GenericRange::new_greater_than(5))
        );
        assert_eq!(
            GenericRange::new_at_least(42).invert(),
            OperationResult::Single(GenericRange::new_less_than(42))
        );
        assert_eq!(
            GenericRange::from(5..42).invert(),
            OperationResult::Double(GenericRange::new_less_than(5), GenericRange::new_at_least(42))
        )
    }

    #[test]
    fn invert_via_not() {
        assert_eq!(!GenericRange::<usize>::full(), OperationResult::<usize>::Empty);
        assert_eq!(
            !GenericRange::new_at_most(5),
            OperationResult::Single(GenericRange::new_greater_than(5))
        );
        assert_eq!(
            !GenericRange::new_at_least(42),
            OperationResult::Single(GenericRange::new_less_than(42))
        );
        assert_eq!(
            !GenericRange::from(5..42),
            OperationResult::Double(GenericRange::new_less_than(5), GenericRange::new_at_least(42))
        )
    }
}