ranges 0.4.0

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;

use crate::{Domain, GenericRange};

impl<T: Domain> GenericRange<T> {
    /// Returns true if the range is empty.
    ///
    /// # Examples
    /// ```
    /// use ranges::GenericRange;
    ///
    /// assert!(GenericRange::from(1..1).is_empty());
    /// assert!(!GenericRange::from(1..=1).is_empty());
    /// assert!(!GenericRange::from(1..2).is_empty());
    /// ```
    #[must_use]
    pub fn is_empty(&self) -> bool {
        #[allow(clippy::needless_borrowed_reference)]
        match (&self.start, &self.end) {
            (&Bound::Unbounded, _) | (_, &Bound::Unbounded)
            // this case could only be true for x > y, which is never the case
            | (&Bound::Included(_), &Bound::Included(_)) => false,

            // we can safely ignore x > y here because of the constructor assertion
            (&Bound::Included(ref x), &Bound::Excluded(ref y)) | (&Bound::Excluded(ref x), &Bound::Included(ref y)) => x == y,

            (&Bound::Excluded(ref x), &Bound::Excluded(ref y)) => {
                if <T as Domain>::DISCRETE {
                    x == y // we ignore x > y here because this constraint is given in the constructor
                    || x.is_next_to(y)
                } else {
                    // we can optimize this here because `is_next_to` always returns false for continuous types
                    x == y
                }
            }
        }
    }
}

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

    use crate::GenericRange;

    #[test]
    fn in_ex() {
        assert!(GenericRange::from(1..1).is_empty());
        assert!(!GenericRange::from(1..2).is_empty());
        assert!(!GenericRange::from(1..42).is_empty());
    }

    #[test]
    fn in_in() {
        assert!(!GenericRange::from(1..=1).is_empty());
        assert!(!GenericRange::from(1..=2).is_empty());
        assert!(!GenericRange::from(1..=42).is_empty());
    }

    #[test]
    fn unbound() {
        assert!(!GenericRange::from(1..).is_empty());
        assert!(!GenericRange::from(..2).is_empty());
        assert!(!GenericRange::from(..=2).is_empty());
        let generic: GenericRange<usize> = GenericRange::from(..);
        assert!(!generic.is_empty());
        assert!(!GenericRange::from((Bound::Excluded(1), Bound::Unbounded)).is_empty());
    }

    #[test]
    fn ex_ex() {
        assert!(GenericRange::from((Bound::Excluded(1), Bound::Excluded(1))).is_empty());
        assert!(GenericRange::from((Bound::Excluded(1), Bound::Excluded(2))).is_empty());
        assert!(!GenericRange::from((Bound::Excluded(1), Bound::Excluded(42))).is_empty());
    }

    #[test]
    fn ex_in() {
        assert!(GenericRange::from((Bound::Excluded(1), Bound::Included(1))).is_empty());
        assert!(!GenericRange::from((Bound::Excluded(1), Bound::Included(2))).is_empty());
        assert!(!GenericRange::from((Bound::Excluded(1), Bound::Included(42))).is_empty());
    }
}

#[allow(clippy::tests_outside_test_module)]
#[cfg(all(test, feature = "noisy_float"))]
mod tests_continuous {
    use core::ops::Bound;

    #[cfg(feature = "noisy_float")]
    use noisy_float::types::N64;

    use crate::GenericRange;

    #[test]
    fn in_ex() {
        let n64_1 = N64::new(1.);
        let n64_2 = N64::new(2.);
        assert!(GenericRange::from(n64_1..n64_1).is_empty());
        assert!(!GenericRange::from(n64_1..n64_2).is_empty());
    }

    #[test]
    fn in_in() {
        let n64_1 = N64::new(1.);
        let n64_2 = N64::new(2.);
        assert!(!GenericRange::from(n64_1..=n64_1).is_empty());
        assert!(!GenericRange::from(n64_1..=n64_2).is_empty());
    }

    #[test]
    fn unbound() {
        let n64_1 = N64::new(1.);
        let n64_2 = N64::new(2.);
        assert!(!GenericRange::from(n64_1..).is_empty());
        assert!(!GenericRange::from(..n64_2).is_empty());
        assert!(!GenericRange::from(..=n64_2).is_empty());
        let generic: GenericRange<N64> = GenericRange::from(..);
        assert!(!generic.is_empty());
        assert!(!GenericRange::from((Bound::Excluded(n64_1), Bound::Unbounded)).is_empty());
    }

    #[test]
    fn ex_ex() {
        let n64_1 = N64::new(1.);
        let n64_2 = N64::new(2.);
        assert!(GenericRange::from((Bound::Excluded(n64_1), Bound::Excluded(n64_1))).is_empty());
        assert!(!GenericRange::from((Bound::Excluded(n64_1), Bound::Excluded(n64_2))).is_empty());
    }

    #[test]
    fn ex_in() {
        let n64_1 = N64::new(1.);
        let n64_2 = N64::new(2.);
        assert!(GenericRange::from((Bound::Excluded(n64_1), Bound::Included(n64_1))).is_empty());
        assert!(!GenericRange::from((Bound::Excluded(n64_1), Bound::Included(n64_2))).is_empty());
    }
}