#![cfg_attr(not(test), no_std)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::ops::{Bound, RangeBounds};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RangeOrdering {
Below,
Inside,
Above,
Empty,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum BoundOrdering {
Within,
Outside,
Incomparable,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RangePosition {
pub lower: BoundOrdering,
pub upper: BoundOrdering,
}
impl RangePosition {
pub fn is_inside(&self) -> bool {
self.lower == BoundOrdering::Within && self.upper == BoundOrdering::Within
}
pub fn ordering(&self) -> Option<RangeOrdering> {
match (self.lower, self.upper) {
(BoundOrdering::Within, BoundOrdering::Within) => Some(RangeOrdering::Inside),
(BoundOrdering::Outside, BoundOrdering::Within) => Some(RangeOrdering::Below),
(BoundOrdering::Within, BoundOrdering::Outside) => Some(RangeOrdering::Above),
(BoundOrdering::Outside, BoundOrdering::Outside) => Some(RangeOrdering::Empty),
_ => None,
}
}
}
fn lower_ordering<T: PartialOrd>(value: &T, bound: Bound<&T>) -> BoundOrdering {
match bound {
Bound::Unbounded => BoundOrdering::Within,
Bound::Included(key) => match value.partial_cmp(key) {
Some(Ordering::Less) => BoundOrdering::Outside,
Some(Ordering::Equal) | Some(Ordering::Greater) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
Bound::Excluded(key) => match value.partial_cmp(key) {
Some(Ordering::Less) | Some(Ordering::Equal) => BoundOrdering::Outside,
Some(Ordering::Greater) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
}
}
fn upper_ordering<T: PartialOrd>(value: &T, bound: Bound<&T>) -> BoundOrdering {
match bound {
Bound::Unbounded => BoundOrdering::Within,
Bound::Included(key) => match value.partial_cmp(key) {
Some(Ordering::Greater) => BoundOrdering::Outside,
Some(Ordering::Equal) | Some(Ordering::Less) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
Bound::Excluded(key) => match value.partial_cmp(key) {
Some(Ordering::Greater) | Some(Ordering::Equal) => BoundOrdering::Outside,
Some(Ordering::Less) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
}
}
fn position_in<T: PartialOrd, R: RangeBounds<T>>(value: &T, range: &R) -> RangePosition {
RangePosition {
lower: lower_ordering(value, range.start_bound()),
upper: upper_ordering(value, range.end_bound()),
}
}
fn range_is_empty<T: Ord, R: RangeBounds<T>>(range: &R) -> bool {
match (range.start_bound(), range.end_bound()) {
(Bound::Included(start), Bound::Included(end)) => start > end,
(Bound::Included(start), Bound::Excluded(end))
| (Bound::Excluded(start), Bound::Included(end))
| (Bound::Excluded(start), Bound::Excluded(end)) => start >= end,
_ => false,
}
}
pub trait BorrowRange<T: ?Sized, R>: Borrow<R> {}
impl<T, R: RangeBounds<T>> BorrowRange<T, R> for R {}
impl<T, R: RangeBounds<T>> BorrowRange<T, R> for &R {}
pub trait RangeOrd {
fn rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(&self, range: B) -> RangeOrdering;
}
impl<T: Ord> RangeOrd for T {
fn rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(&self, range: B) -> RangeOrdering {
let range = range.borrow();
if range_is_empty(range) {
return RangeOrdering::Empty;
}
position_in(self, range)
.ordering()
.expect("a total order over a non-empty range always yields a verdict")
}
}
pub trait PartialRangeOrd {
fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
&self,
range: B,
) -> RangePosition;
}
impl<T: PartialOrd> PartialRangeOrd for T {
fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
&self,
range: B,
) -> RangePosition {
position_in(self, range.borrow())
}
}
#[cfg(test)]
mod rcmp_tests {
use super::*;
#[test]
fn range_full() {
assert_eq!(1.rcmp(..), RangeOrdering::Inside);
}
#[test]
fn range_from() {
assert_eq!(1.rcmp(1..), RangeOrdering::Inside);
assert_eq!(1.rcmp(&1..), RangeOrdering::Inside);
assert_eq!(1.rcmp(2..), RangeOrdering::Below);
assert_eq!(1.rcmp(&2..), RangeOrdering::Below);
}
#[test]
fn range_to() {
assert_eq!(1.rcmp(..1), RangeOrdering::Above);
assert_eq!(1.rcmp(..&1), RangeOrdering::Above);
assert_eq!(1.rcmp(..2), RangeOrdering::Inside);
assert_eq!(1.rcmp(..&2), RangeOrdering::Inside);
}
#[test]
fn range() {
assert_eq!(1.rcmp(0..1), RangeOrdering::Above);
assert_eq!(1.rcmp(&0..&1), RangeOrdering::Above);
assert_eq!(1.rcmp(1..2), RangeOrdering::Inside);
assert_eq!(1.rcmp(&1..&2), RangeOrdering::Inside);
assert_eq!(1.rcmp(2..3), RangeOrdering::Below);
assert_eq!(1.rcmp(&2..&3), RangeOrdering::Below);
}
#[test]
fn range_inclusive() {
assert_eq!(1.rcmp(0..=0), RangeOrdering::Above);
assert_eq!(1.rcmp(&0..=&0), RangeOrdering::Above);
assert_eq!(1.rcmp(1..=1), RangeOrdering::Inside);
assert_eq!(1.rcmp(&1..=&1), RangeOrdering::Inside);
assert_eq!(1.rcmp(2..=2), RangeOrdering::Below);
assert_eq!(1.rcmp(&2..=&2), RangeOrdering::Below);
}
#[test]
fn range_to_inclusive() {
assert_eq!(1.rcmp(..=0), RangeOrdering::Above);
assert_eq!(1.rcmp(..=&0), RangeOrdering::Above);
assert_eq!(1.rcmp(..=1), RangeOrdering::Inside);
assert_eq!(1.rcmp(..=&1), RangeOrdering::Inside);
}
#[test]
fn bounds_full() {
let bounds: (Bound<i32>, Bound<i32>) = (Bound::Unbounded, Bound::Unbounded);
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
}
#[test]
fn bounds_from() {
let bounds = (Bound::Included(1), Bound::Unbounded);
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Included(&1), Bound::Unbounded);
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Included(2), Bound::Unbounded);
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
let bounds = (Bound::Included(&2), Bound::Unbounded);
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
}
#[test]
fn bounds_to() {
let bounds = (Bound::Unbounded, Bound::Excluded(1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Unbounded, Bound::Excluded(&1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Unbounded, Bound::Excluded(2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Unbounded, Bound::Excluded(&2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
}
#[test]
fn bounds() {
let bounds = (Bound::Included(0), Bound::Excluded(1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Included(&0), Bound::Excluded(&1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Included(1), Bound::Excluded(2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Included(&1), Bound::Excluded(&2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Included(2), Bound::Excluded(3));
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
let bounds = (Bound::Included(&2), Bound::Excluded(&3));
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
}
#[test]
fn bounds_inclusive() {
let bounds = (Bound::Included(0), Bound::Included(0));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Included(&0), Bound::Included(&0));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Included(1), Bound::Included(1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Included(&1), Bound::Included(&1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Included(2), Bound::Included(2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
let bounds = (Bound::Included(&2), Bound::Included(&2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
}
#[test]
fn bounds_to_inclusive() {
let bounds = (Bound::Unbounded, Bound::Included(0));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Unbounded, Bound::Included(&0));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds = (Bound::Unbounded, Bound::Included(1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds = (Bound::Unbounded, Bound::Included(&1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
}
#[test]
fn bounds_exclusive_inclusive() {
let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(-1), Bound::Included(0));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&-1), Bound::Included(&0));
assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(0), Bound::Included(1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&0), Bound::Included(&1));
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(1), Bound::Included(2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&1), Bound::Included(&2));
assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
}
#[test]
fn bounds_as_reference() {
let bounds = 0..2;
assert_eq!(1.rcmp(&bounds), RangeOrdering::Inside);
assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
}
#[test]
#[allow(clippy::reversed_empty_ranges)] fn empty_ranges() {
assert_eq!(0.rcmp(0..0), RangeOrdering::Empty);
assert_eq!(0.rcmp(&0..&0), RangeOrdering::Empty);
assert_eq!(0.rcmp(..0u32), RangeOrdering::Above);
assert_eq!(0.rcmp(..&0u32), RangeOrdering::Above);
assert_eq!(30.rcmp(45..35), RangeOrdering::Empty);
assert_eq!(30.rcmp(&45..&35), RangeOrdering::Empty);
assert_eq!(30.rcmp(25..15), RangeOrdering::Empty);
assert_eq!(30.rcmp(&25..&15), RangeOrdering::Empty);
assert_eq!(0.rcmp(0..=0), RangeOrdering::Inside);
assert_eq!(1.rcmp(0..=0), RangeOrdering::Above);
}
}
#[cfg(test)]
mod partial_rcmp_tests {
use super::*;
#[derive(Clone, Copy, Debug, PartialEq)]
struct Div(i32);
impl PartialOrd for Div {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let a_s = self.0.abs();
let a_o = other.0.abs();
match a_s.cmp(&a_o) {
Ordering::Less if a_o % a_s == 0 => Some(Ordering::Less),
Ordering::Greater if a_s % a_o == 0 => Some(Ordering::Greater),
Ordering::Equal => Some(Ordering::Equal),
_ => None,
}
}
}
const W: BoundOrdering = BoundOrdering::Within;
const O: BoundOrdering = BoundOrdering::Outside;
const I: BoundOrdering = BoundOrdering::Incomparable;
fn pos(lower: BoundOrdering, upper: BoundOrdering) -> RangePosition {
RangePosition { lower, upper }
}
#[test]
fn range_full() {
assert_eq!(Div(1).partial_rcmp(..), pos(W, W));
assert_eq!(
Div(1).partial_rcmp(..).ordering(),
Some(RangeOrdering::Inside)
);
}
#[test]
fn range_from() {
assert_eq!(Div(1).partial_rcmp(Div(1)..), pos(W, W));
assert_eq!(Div(1).partial_rcmp(&Div(1)..), pos(W, W));
assert_eq!(Div(1).partial_rcmp(Div(2)..), pos(O, W));
assert_eq!(Div(1).partial_rcmp(&Div(2)..), pos(O, W));
assert_eq!(Div(2).partial_rcmp(Div(3)..), pos(I, W));
assert_eq!(Div(2).partial_rcmp(&Div(3)..), pos(I, W));
assert_eq!(Div(2).partial_rcmp(Div(3)..).ordering(), None);
}
#[test]
fn range_to() {
assert_eq!(Div(4).partial_rcmp(..Div(2)), pos(W, O));
assert_eq!(Div(4).partial_rcmp(..&Div(2)), pos(W, O));
assert_eq!(Div(1).partial_rcmp(..Div(2)), pos(W, W));
assert_eq!(Div(1).partial_rcmp(..&Div(2)), pos(W, W));
assert_eq!(Div(3).partial_rcmp(..Div(10)), pos(W, I));
assert_eq!(Div(3).partial_rcmp(..&Div(10)), pos(W, I));
assert_eq!(Div(3).partial_rcmp(..Div(10)).ordering(), None);
}
#[test]
fn range() {
assert_eq!(Div(3).partial_rcmp(Div(1)..Div(3)), pos(W, O));
assert_eq!(Div(3).partial_rcmp(&Div(1)..&Div(3)), pos(W, O));
assert_eq!(Div(6).partial_rcmp(Div(2)..Div(12)), pos(W, W));
assert_eq!(Div(6).partial_rcmp(&Div(2)..&Div(12)), pos(W, W));
assert_eq!(Div(2).partial_rcmp(Div(4)..Div(8)), pos(O, W));
assert_eq!(Div(2).partial_rcmp(&Div(4)..&Div(8)), pos(O, W));
assert_eq!(Div(3).partial_rcmp(Div(4)..Div(12)), pos(I, W));
assert_eq!(Div(3).partial_rcmp(&Div(4)..&Div(12)), pos(I, W));
assert_eq!(Div(3).partial_rcmp(Div(4)..Div(12)).ordering(), None);
}
#[test]
fn range_inclusive() {
assert_eq!(Div(6).partial_rcmp(Div(1)..=Div(3)), pos(W, O));
assert_eq!(Div(6).partial_rcmp(&Div(1)..=&Div(3)), pos(W, O));
assert_eq!(Div(6).partial_rcmp(Div(6)..=Div(6)), pos(W, W));
assert_eq!(Div(6).partial_rcmp(&Div(6)..=&Div(6)), pos(W, W));
assert_eq!(Div(2).partial_rcmp(Div(4)..=Div(8)), pos(O, W));
assert_eq!(Div(2).partial_rcmp(&Div(4)..=&Div(8)), pos(O, W));
assert_eq!(Div(3).partial_rcmp(Div(4)..=Div(12)), pos(I, W));
assert_eq!(Div(3).partial_rcmp(&Div(4)..=&Div(12)), pos(I, W));
}
#[test]
fn range_to_inclusive() {
assert_eq!(Div(4).partial_rcmp(..=Div(2)), pos(W, O));
assert_eq!(Div(4).partial_rcmp(..=&Div(2)), pos(W, O));
assert_eq!(Div(1).partial_rcmp(..=Div(2)), pos(W, W));
assert_eq!(Div(1).partial_rcmp(..=&Div(2)), pos(W, W));
assert_eq!(Div(3).partial_rcmp(..=Div(10)), pos(W, I));
assert_eq!(Div(3).partial_rcmp(..=&Div(10)), pos(W, I));
}
#[test]
fn bounds_full() {
let bounds: (Bound<Div>, Bound<Div>) = (Bound::Unbounded, Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
}
#[test]
fn bounds_from() {
let bounds = (Bound::Included(Div(1)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
let bounds = (Bound::Included(&Div(1)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
let bounds = (Bound::Included(Div(2)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), pos(O, W));
let bounds = (Bound::Included(&Div(2)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), pos(O, W));
let bounds = (Bound::Included(Div(3)), Bound::Unbounded);
assert_eq!(Div(2).partial_rcmp(bounds), pos(I, W));
let bounds = (Bound::Included(&Div(3)), Bound::Unbounded);
assert_eq!(Div(2).partial_rcmp(bounds), pos(I, W));
}
#[test]
fn bounds_to() {
let bounds = (Bound::Unbounded, Bound::Excluded(Div(2)));
assert_eq!(Div(4).partial_rcmp(bounds), pos(W, O));
let bounds = (Bound::Unbounded, Bound::Excluded(Div(2)));
assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
let bounds = (Bound::Unbounded, Bound::Excluded(&Div(10)));
assert_eq!(Div(3).partial_rcmp(bounds), pos(W, I));
}
#[test]
fn bounds() {
let bounds = (Bound::Included(Div(1)), Bound::Excluded(Div(3)));
assert_eq!(Div(3).partial_rcmp(bounds), pos(W, O));
let bounds = (Bound::Included(&Div(2)), Bound::Excluded(&Div(12)));
assert_eq!(Div(6).partial_rcmp(bounds), pos(W, W));
let bounds = (Bound::Included(Div(4)), Bound::Excluded(Div(8)));
assert_eq!(Div(2).partial_rcmp(bounds), pos(O, W));
}
#[test]
fn bounds_inclusive() {
let bounds = (Bound::Included(Div(1)), Bound::Included(Div(3)));
assert_eq!(Div(6).partial_rcmp(bounds), pos(W, O));
let bounds = (Bound::Included(&Div(6)), Bound::Included(&Div(6)));
assert_eq!(Div(6).partial_rcmp(bounds), pos(W, W));
let bounds = (Bound::Included(Div(4)), Bound::Included(Div(8)));
assert_eq!(Div(2).partial_rcmp(bounds), pos(O, W));
}
#[test]
fn bounds_to_inclusive() {
let bounds = (Bound::Unbounded, Bound::Included(Div(2)));
assert_eq!(Div(4).partial_rcmp(bounds), pos(W, O));
let bounds = (Bound::Unbounded, Bound::Included(&Div(2)));
assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
}
#[test]
fn bounds_exclusive_inclusive() {
let bounds = (Bound::Excluded(Div(1)), Bound::Included(Div(3)));
assert_eq!(Div(6).partial_rcmp(bounds), pos(W, O));
let bounds = (Bound::Excluded(Div(1)), Bound::Included(Div(2)));
assert_eq!(Div(1).partial_rcmp(bounds), pos(O, W));
}
#[test]
fn bounds_as_reference() {
let bounds = Div(2)..Div(12);
assert_eq!(Div(6).partial_rcmp(&bounds), pos(W, W));
assert_eq!(Div(6).partial_rcmp(bounds), pos(W, W));
}
#[test]
fn comparable_to_one_bound_only() {
assert_eq!(Div(4).partial_rcmp(Div(2)..Div(9)), pos(W, I));
assert_eq!(Div(4).partial_rcmp(Div(2)..Div(9)).ordering(), None);
assert_eq!(Div(2).partial_rcmp(Div(4)..Div(9)), pos(O, I));
assert_eq!(Div(2).partial_rcmp(Div(4)..Div(9)).ordering(), None);
assert_eq!(Div(4).partial_rcmp(Div(3)..Div(12)), pos(I, W));
assert_eq!(Div(4).partial_rcmp(Div(3)..Div(12)).ordering(), None);
}
#[test]
fn empty_ranges() {
assert_eq!(Div(4).partial_rcmp(Div(8)..Div(2)), pos(O, O));
assert_eq!(
Div(4).partial_rcmp(Div(8)..Div(2)).ordering(),
Some(RangeOrdering::Empty)
);
}
}