Skip to main content

range_cmp/
lib.rs

1// Copyright 2023 Developers of the range_cmp project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! This crate provides the [`RangeOrd`] trait on all types that implement [`Ord`].
10//! This trait exposes a [`rcmp`](RangeOrd::rcmp) associated method that allows
11//! comparing a value with a range of values:
12//!
13//! ```
14//! use range_cmp::{RangeOrd, RangeOrdering};
15//! assert_eq!(15.rcmp(20..30), RangeOrdering::Below);
16//! assert_eq!(25.rcmp(20..30), RangeOrdering::Inside);
17//! assert_eq!(35.rcmp(20..30), RangeOrdering::Above);
18//! ```
19//!
20//! # Empty ranges
21//!
22//! Unlike previous versions, the crate now handles empty ranges explicitly, instead
23//! of returning an arbitrary, representation-dependent answer. An empty range (such as
24//! `30..20` or `0..0`) is reported as [`RangeOrdering::Empty`]:
25//!
26//! ```
27//! use range_cmp::{RangeOrd, RangeOrdering};
28//! assert_eq!(25.rcmp(30..20), RangeOrdering::Empty);
29//! assert_eq!(0.rcmp(0..0), RangeOrdering::Empty);
30//! ```
31//!
32//! Emptiness is judged from the *bounds*, not from the population of the type: `..0u32`
33//! is treated as a regular (non-empty) range even though no `u32` is below `0`.
34//!
35//! # Partial orders
36//!
37//! The crate also provides the [`PartialRangeOrd`] trait on all types that implement
38//! [`PartialOrd`]. Because a partial order is not a line but a poset, a value cannot
39//! always be collapsed into a single `Below`/`Inside`/`Above` verdict: it may be
40//! incomparable with one or both bounds. [`partial_rcmp`](PartialRangeOrd::partial_rcmp)
41//! therefore returns a [`RangePosition`], the *pair* of the value's relationships to the
42//! lower and upper bounds, which never loses information:
43//!
44//! ```
45//! use range_cmp::{PartialRangeOrd, RangeOrdering};
46//! // `f64` is `PartialOrd` but not `Ord`.
47//! assert_eq!(1.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Below));
48//! assert_eq!(2.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Inside));
49//! assert_eq!(3.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Above));
50//! // `NaN` is incomparable with the bounds, so there is no single verdict:
51//! assert_eq!(core::f64::NAN.partial_rcmp(2.0..3.0).ordering(), None);
52//! ```
53//!
54//! # `no_std`
55//!
56//! The crate is `#![no_std]`: it has no dependencies and only relies on `core`, so it
57//! can be used in embedded and other environments without the standard library.
58#![cfg_attr(not(test), no_std)]
59#![forbid(unsafe_code)]
60#![warn(missing_docs)]
61
62use core::borrow::Borrow;
63use core::cmp::Ordering;
64use core::ops::{Bound, RangeBounds};
65
66/// Simplified result for [`RangeOrd::rcmp`], obtained for totally ordered types or by
67/// collapsing a [`RangePosition`] through [`RangePosition::ordering`].
68#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
69pub enum RangeOrdering {
70    /// The value is below (all) the range. For instance, `-1` is below the range `0..42`.
71    Below,
72    /// The value is contained inside the range. For instance, `34` is inside the range `0..42`.
73    Inside,
74    /// The value is above (all) the range. For instance, `314` is above the range `0..42`.
75    Above,
76    /// The range is empty, so the value cannot be meaningfully positioned. For instance,
77    /// `42..0` is empty.
78    Empty,
79}
80
81/// Position of a value relative to a single bound of a range.
82///
83/// This is the building block of [`RangePosition`]. For a lower bound, `Within` means the
84/// value satisfies the bound (it is greater than, or equal to, the bound depending on
85/// inclusiveness) and `Outside` means it is below it. For an upper bound, the meaning is
86/// mirrored.
87#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
88pub enum BoundOrdering {
89    /// The value is on the inner side of the bound (it satisfies the bound).
90    Within,
91    /// The value is on the outer side of the bound (it violates the bound).
92    Outside,
93    /// The value is incomparable with the bound. This can only happen for types that are
94    /// [`PartialOrd`] but not [`Ord`].
95    Incomparable,
96}
97
98/// Full position of a value relative to a range, expressed as the pair of its
99/// relationships to the lower and the upper bound.
100///
101/// Keeping both relationships separate is what allows [`PartialRangeOrd`] to stay honest
102/// over partial orders: a value can be, say, comparable with the lower bound and
103/// incomparable with the upper one, and the information is preserved instead of being
104/// flattened into a single ambiguous verdict.
105#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
106pub struct RangePosition {
107    /// Relationship of the value to the lower bound. `Within` means the value satisfies
108    /// the lower bound, `Outside` means it is below it.
109    pub lower: BoundOrdering,
110    /// Relationship of the value to the upper bound. `Within` means the value satisfies
111    /// the upper bound, `Outside` means it is above it.
112    pub upper: BoundOrdering,
113}
114
115impl RangePosition {
116    /// Returns whether the value lies inside the range, i.e. it satisfies both bounds.
117    ///
118    /// ```
119    /// use range_cmp::PartialRangeOrd;
120    /// assert!(2.5_f64.partial_rcmp(2.0..3.0).is_inside());
121    /// assert!(!3.5_f64.partial_rcmp(2.0..3.0).is_inside());
122    /// ```
123    pub fn is_inside(&self) -> bool {
124        self.lower == BoundOrdering::Within && self.upper == BoundOrdering::Within
125    }
126
127    /// Collapses the pair into a simple [`RangeOrdering`] when possible.
128    ///
129    /// Returns `None` when the value is incomparable with at least one bound, in which
130    /// case no single `Below`/`Inside`/`Above`/`Empty` verdict captures the position;
131    /// inspect the [`lower`](RangePosition::lower) and [`upper`](RangePosition::upper)
132    /// fields directly in that case.
133    ///
134    /// The `(Outside, Outside)` case — being simultaneously below the lower bound and
135    /// above the upper bound — can only occur for an empty (inverted) range, and is
136    /// reported as [`RangeOrdering::Empty`].
137    pub fn ordering(&self) -> Option<RangeOrdering> {
138        match (self.lower, self.upper) {
139            (BoundOrdering::Within, BoundOrdering::Within) => Some(RangeOrdering::Inside),
140            (BoundOrdering::Outside, BoundOrdering::Within) => Some(RangeOrdering::Below),
141            (BoundOrdering::Within, BoundOrdering::Outside) => Some(RangeOrdering::Above),
142            (BoundOrdering::Outside, BoundOrdering::Outside) => Some(RangeOrdering::Empty),
143            _ => None,
144        }
145    }
146}
147
148/// Computes the relationship of `value` to a lower bound.
149fn lower_ordering<T: PartialOrd>(value: &T, bound: Bound<&T>) -> BoundOrdering {
150    match bound {
151        Bound::Unbounded => BoundOrdering::Within,
152        Bound::Included(key) => match value.partial_cmp(key) {
153            Some(Ordering::Less) => BoundOrdering::Outside,
154            Some(Ordering::Equal) | Some(Ordering::Greater) => BoundOrdering::Within,
155            None => BoundOrdering::Incomparable,
156        },
157        Bound::Excluded(key) => match value.partial_cmp(key) {
158            Some(Ordering::Less) | Some(Ordering::Equal) => BoundOrdering::Outside,
159            Some(Ordering::Greater) => BoundOrdering::Within,
160            None => BoundOrdering::Incomparable,
161        },
162    }
163}
164
165/// Computes the relationship of `value` to an upper bound.
166fn upper_ordering<T: PartialOrd>(value: &T, bound: Bound<&T>) -> BoundOrdering {
167    match bound {
168        Bound::Unbounded => BoundOrdering::Within,
169        Bound::Included(key) => match value.partial_cmp(key) {
170            Some(Ordering::Greater) => BoundOrdering::Outside,
171            Some(Ordering::Equal) | Some(Ordering::Less) => BoundOrdering::Within,
172            None => BoundOrdering::Incomparable,
173        },
174        Bound::Excluded(key) => match value.partial_cmp(key) {
175            Some(Ordering::Greater) | Some(Ordering::Equal) => BoundOrdering::Outside,
176            Some(Ordering::Less) => BoundOrdering::Within,
177            None => BoundOrdering::Incomparable,
178        },
179    }
180}
181
182/// Builds the [`RangePosition`] of `value` relative to `range`.
183fn position_in<T: PartialOrd, R: RangeBounds<T>>(value: &T, range: &R) -> RangePosition {
184    RangePosition {
185        lower: lower_ordering(value, range.start_bound()),
186        upper: upper_ordering(value, range.end_bound()),
187    }
188}
189
190/// Returns whether a range is empty, judged solely from its bounds (and thus from the
191/// total order over `T`). A range with at least one unbounded side is never empty.
192fn range_is_empty<T: Ord, R: RangeBounds<T>>(range: &R) -> bool {
193    match (range.start_bound(), range.end_bound()) {
194        (Bound::Included(start), Bound::Included(end)) => start > end,
195        (Bound::Included(start), Bound::Excluded(end))
196        | (Bound::Excluded(start), Bound::Included(end))
197        | (Bound::Excluded(start), Bound::Excluded(end)) => start >= end,
198        _ => false,
199    }
200}
201
202// suggestion from @benschulz https://internals.rust-lang.org/t/implement-rangebounds-for-range/19704/3
203/// Helper trait to allow passing a range as either a owned value or a reference.
204///
205/// For instance:
206///
207/// ```
208/// use std::ops::RangeBounds;
209/// use range_cmp::BorrowRange;
210/// fn f<T, R: RangeBounds<T>, B: BorrowRange<T, R>>(range: B) {
211///     let range = range.borrow();
212///     // ...
213/// }
214/// ```
215///
216/// With concrete type such as [`i32`], this would be achieved by taking a generic type `T` with
217/// the bound `T: Borrow<i32>`. So we might be tempted to do the same with the [`RangeBounds`]
218/// trait:
219///
220/// ```
221/// use std::borrow::Borrow;
222/// use std::ops::RangeBounds;
223/// fn f<R: RangeBounds<i32>, B: Borrow<R>>(range: B) {
224///     let range = range.borrow();
225///     // ...
226/// }
227/// f(0..42)
228/// ```
229///
230/// However, this fails to compile when passing a reference:
231///
232/// ```compile_fail,E0282
233/// # use std::borrow::Borrow;
234/// # use std::ops::RangeBounds;
235/// # fn f<R: RangeBounds<i32>, B: Borrow<R>>(range: B) {
236/// #     let range = range.borrow();
237/// #     // ...
238/// # }
239/// f(&(0..42))
240/// ```
241///
242/// The compilation output is:
243///
244/// ```shell
245///   | f(&(0..42))
246///   | ^ cannot infer type of the type parameter `R` declared on the function `f`
247/// ```
248///
249/// Indeed, although we understand we want to pass a [`Range`](core::ops::Range)`<`[`i32`]`>` by
250/// reference, the compiler need to assume that other types could yield a
251/// `&`[`Range`](core::ops::Range)`<`[`i32`]`>` when borrowed.
252pub trait BorrowRange<T: ?Sized, R>: Borrow<R> {}
253impl<T, R: RangeBounds<T>> BorrowRange<T, R> for R {}
254impl<T, R: RangeBounds<T>> BorrowRange<T, R> for &R {}
255
256/// Trait to provide the [`rcmp`](RangeOrd::rcmp) method, which allows comparing
257/// the type to a range. A blanket implementation is provided for all types that implement the
258/// [`Ord`] trait.
259pub trait RangeOrd {
260    /// Compare the value to a range of values. Returns whether the value is below, inside,
261    /// above, or whether the range is empty.
262    ///
263    /// ```
264    /// use range_cmp::{RangeOrd, RangeOrdering};
265    /// assert_eq!(15.rcmp(20..30), RangeOrdering::Below);
266    /// assert_eq!(25.rcmp(20..30), RangeOrdering::Inside);
267    /// assert_eq!(35.rcmp(20..30), RangeOrdering::Above);
268    /// assert_eq!(25.rcmp(30..20), RangeOrdering::Empty);
269    /// ```
270    fn rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(&self, range: B) -> RangeOrdering;
271}
272
273impl<T: Ord> RangeOrd for T {
274    fn rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(&self, range: B) -> RangeOrdering {
275        let range = range.borrow();
276        if range_is_empty(range) {
277            return RangeOrdering::Empty;
278        }
279        // `Self` is totally ordered, so no bound can be incomparable, and a non-empty
280        // range always yields one of `Below`, `Inside` or `Above`.
281        position_in(self, range)
282            .ordering()
283            .expect("a total order over a non-empty range always yields a verdict")
284    }
285}
286
287/// Trait to provide the [`partial_rcmp`](PartialRangeOrd::partial_rcmp) method, which allows
288/// comparing the type to a range. A blanket implementation is provided for all types that
289/// implement the [`PartialOrd`] trait.
290pub trait PartialRangeOrd {
291    /// Compare the value to a range of values, returning its full [`RangePosition`]: the
292    /// pair of its relationships to the lower and the upper bound.
293    ///
294    /// Use [`RangePosition::ordering`] to collapse it into a simple [`RangeOrdering`] when
295    /// the value is comparable with both bounds.
296    ///
297    /// ```
298    /// use range_cmp::{PartialRangeOrd, RangeOrdering};
299    /// assert_eq!(1.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Below));
300    /// assert_eq!(2.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Inside));
301    /// assert_eq!(3.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Above));
302    /// // `NaN` is incomparable with the bounds:
303    /// assert_eq!(core::f64::NAN.partial_rcmp(2.0..3.0).ordering(), None);
304    /// ```
305    fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
306        &self,
307        range: B,
308    ) -> RangePosition;
309}
310
311impl<T: PartialOrd> PartialRangeOrd for T {
312    fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
313        &self,
314        range: B,
315    ) -> RangePosition {
316        position_in(self, range.borrow())
317    }
318}
319
320#[cfg(test)]
321mod rcmp_tests {
322    use super::*;
323
324    #[test]
325    fn range_full() {
326        // 1 is inside ]-inf, inf[
327        assert_eq!(1.rcmp(..), RangeOrdering::Inside);
328    }
329
330    #[test]
331    fn range_from() {
332        // 1 is inside [1, +inf[
333        assert_eq!(1.rcmp(1..), RangeOrdering::Inside);
334        assert_eq!(1.rcmp(&1..), RangeOrdering::Inside);
335
336        // 1 is below [2, +inf[
337        assert_eq!(1.rcmp(2..), RangeOrdering::Below);
338        assert_eq!(1.rcmp(&2..), RangeOrdering::Below);
339    }
340
341    #[test]
342    fn range_to() {
343        // 1 is above ]-inf, 1[
344        assert_eq!(1.rcmp(..1), RangeOrdering::Above);
345        assert_eq!(1.rcmp(..&1), RangeOrdering::Above);
346
347        // 1 is inside ]-inf, 2[
348        assert_eq!(1.rcmp(..2), RangeOrdering::Inside);
349        assert_eq!(1.rcmp(..&2), RangeOrdering::Inside);
350    }
351
352    #[test]
353    fn range() {
354        // 1 is above [0, 1[
355        assert_eq!(1.rcmp(0..1), RangeOrdering::Above);
356        assert_eq!(1.rcmp(&0..&1), RangeOrdering::Above);
357
358        // 1 is inside [1, 2[
359        assert_eq!(1.rcmp(1..2), RangeOrdering::Inside);
360        assert_eq!(1.rcmp(&1..&2), RangeOrdering::Inside);
361
362        // 1 is below [2, 3[
363        assert_eq!(1.rcmp(2..3), RangeOrdering::Below);
364        assert_eq!(1.rcmp(&2..&3), RangeOrdering::Below);
365    }
366
367    #[test]
368    fn range_inclusive() {
369        // 1 is above [0, 0]
370        assert_eq!(1.rcmp(0..=0), RangeOrdering::Above);
371        assert_eq!(1.rcmp(&0..=&0), RangeOrdering::Above);
372
373        // 1 is inside [1, 1]
374        assert_eq!(1.rcmp(1..=1), RangeOrdering::Inside);
375        assert_eq!(1.rcmp(&1..=&1), RangeOrdering::Inside);
376
377        // 1 is below [2, 2]
378        assert_eq!(1.rcmp(2..=2), RangeOrdering::Below);
379        assert_eq!(1.rcmp(&2..=&2), RangeOrdering::Below);
380    }
381
382    #[test]
383    fn range_to_inclusive() {
384        // 1 is above ]-inf, 0]
385        assert_eq!(1.rcmp(..=0), RangeOrdering::Above);
386        assert_eq!(1.rcmp(..=&0), RangeOrdering::Above);
387
388        // 1 is inside ]-inf, 1
389        assert_eq!(1.rcmp(..=1), RangeOrdering::Inside);
390        assert_eq!(1.rcmp(..=&1), RangeOrdering::Inside);
391    }
392
393    #[test]
394    fn bounds_full() {
395        // 1 is inside ]-inf, inf[
396        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Unbounded, Bound::Unbounded);
397        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
398    }
399
400    #[test]
401    fn bounds_from() {
402        // 1 is inside [1, +inf[
403        let bounds = (Bound::Included(1), Bound::Unbounded);
404        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
405
406        let bounds = (Bound::Included(&1), Bound::Unbounded);
407        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
408
409        // 1 is below [2, +inf[
410        let bounds = (Bound::Included(2), Bound::Unbounded);
411        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
412
413        let bounds = (Bound::Included(&2), Bound::Unbounded);
414        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
415    }
416
417    #[test]
418    fn bounds_to() {
419        // 1 is above ]-inf, 1[
420        let bounds = (Bound::Unbounded, Bound::Excluded(1));
421        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
422
423        let bounds = (Bound::Unbounded, Bound::Excluded(&1));
424        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
425
426        // 1 is inside ]-inf, 2[
427        let bounds = (Bound::Unbounded, Bound::Excluded(2));
428        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
429
430        let bounds = (Bound::Unbounded, Bound::Excluded(&2));
431        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
432    }
433
434    #[test]
435    fn bounds() {
436        // 1 is above [0, 1[
437        let bounds = (Bound::Included(0), Bound::Excluded(1));
438        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
439
440        let bounds = (Bound::Included(&0), Bound::Excluded(&1));
441        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
442
443        // 1 is inside [1, 2[
444        let bounds = (Bound::Included(1), Bound::Excluded(2));
445        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
446
447        let bounds = (Bound::Included(&1), Bound::Excluded(&2));
448        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
449
450        // 1 is below [2, 3[
451        let bounds = (Bound::Included(2), Bound::Excluded(3));
452        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
453
454        let bounds = (Bound::Included(&2), Bound::Excluded(&3));
455        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
456    }
457
458    #[test]
459    fn bounds_inclusive() {
460        // 1 is above [0, 0]
461        let bounds = (Bound::Included(0), Bound::Included(0));
462        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
463
464        let bounds = (Bound::Included(&0), Bound::Included(&0));
465        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
466
467        // 1 is inside [1, 1]
468        let bounds = (Bound::Included(1), Bound::Included(1));
469        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
470
471        let bounds = (Bound::Included(&1), Bound::Included(&1));
472        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
473
474        // 1 is below [2, 2]
475        let bounds = (Bound::Included(2), Bound::Included(2));
476        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
477
478        let bounds = (Bound::Included(&2), Bound::Included(&2));
479        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
480    }
481
482    #[test]
483    fn bounds_to_inclusive() {
484        // 1 is above ]-inf, 0]
485        let bounds = (Bound::Unbounded, Bound::Included(0));
486        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
487
488        let bounds = (Bound::Unbounded, Bound::Included(&0));
489        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
490
491        // 1 is inside ]-inf, 1]
492        let bounds = (Bound::Unbounded, Bound::Included(1));
493        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
494
495        let bounds = (Bound::Unbounded, Bound::Included(&1));
496        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
497    }
498
499    #[test]
500    fn bounds_exclusive_inclusive() {
501        // 1 is above ]-1, 0]
502        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(-1), Bound::Included(0));
503        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
504
505        let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&-1), Bound::Included(&0));
506        assert_eq!(1.rcmp(bounds), RangeOrdering::Above);
507
508        // 1 is inside ]0, 1]
509        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(0), Bound::Included(1));
510        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
511
512        let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&0), Bound::Included(&1));
513        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
514
515        // 1 is below ]1, 2]
516        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(1), Bound::Included(2));
517        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
518
519        let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&1), Bound::Included(&2));
520        assert_eq!(1.rcmp(bounds), RangeOrdering::Below);
521    }
522
523    #[test]
524    fn bounds_as_reference() {
525        let bounds = 0..2;
526        assert_eq!(1.rcmp(&bounds), RangeOrdering::Inside);
527        assert_eq!(1.rcmp(bounds), RangeOrdering::Inside);
528    }
529
530    #[test]
531    #[allow(clippy::reversed_empty_ranges)] // intentionally testing empty/inverted ranges
532    fn empty_ranges() {
533        // [0, 0[ is empty
534        assert_eq!(0.rcmp(0..0), RangeOrdering::Empty);
535        assert_eq!(0.rcmp(&0..&0), RangeOrdering::Empty);
536
537        // ]-inf, 0u32[ is a regular range (emptiness is judged from the bounds, not the
538        // population of the type), and 0u32 is above it
539        assert_eq!(0.rcmp(..0u32), RangeOrdering::Above);
540        assert_eq!(0.rcmp(..&0u32), RangeOrdering::Above);
541
542        // [45, 35[ is empty (inverted)
543        assert_eq!(30.rcmp(45..35), RangeOrdering::Empty);
544        assert_eq!(30.rcmp(&45..&35), RangeOrdering::Empty);
545
546        // [25, 15[ is empty (inverted)
547        assert_eq!(30.rcmp(25..15), RangeOrdering::Empty);
548        assert_eq!(30.rcmp(&25..&15), RangeOrdering::Empty);
549
550        // [0, 0] is *not* empty: it contains exactly 0
551        assert_eq!(0.rcmp(0..=0), RangeOrdering::Inside);
552        assert_eq!(1.rcmp(0..=0), RangeOrdering::Above);
553    }
554}
555
556#[cfg(test)]
557mod partial_rcmp_tests {
558    use super::*;
559
560    /// A deliberately partial order: `Div(a)` compares to `Div(b)` through divisibility of
561    /// their absolute values. `Div(a) < Div(b)` iff `|a|` strictly divides `|b|`; values
562    /// that do not divide one another (e.g. `2` and `3`) are incomparable.
563    #[derive(Clone, Copy, Debug, PartialEq)]
564    struct Div(i32);
565
566    impl PartialOrd for Div {
567        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
568            let a_s = self.0.abs();
569            let a_o = other.0.abs();
570
571            match a_s.cmp(&a_o) {
572                Ordering::Less if a_o % a_s == 0 => Some(Ordering::Less),
573                Ordering::Greater if a_s % a_o == 0 => Some(Ordering::Greater),
574                Ordering::Equal => Some(Ordering::Equal),
575                _ => None,
576            }
577        }
578    }
579
580    // Shorthands for terser assertions.
581    const W: BoundOrdering = BoundOrdering::Within;
582    const O: BoundOrdering = BoundOrdering::Outside;
583    const I: BoundOrdering = BoundOrdering::Incomparable;
584
585    fn pos(lower: BoundOrdering, upper: BoundOrdering) -> RangePosition {
586        RangePosition { lower, upper }
587    }
588
589    #[test]
590    fn range_full() {
591        // 1 is an integer, comparable to everything in ]-inf, inf[
592        assert_eq!(Div(1).partial_rcmp(..), pos(W, W));
593        assert_eq!(
594            Div(1).partial_rcmp(..).ordering(),
595            Some(RangeOrdering::Inside)
596        );
597    }
598
599    #[test]
600    fn range_from() {
601        // 1 is a multiple of 1
602        assert_eq!(Div(1).partial_rcmp(Div(1)..), pos(W, W));
603        assert_eq!(Div(1).partial_rcmp(&Div(1)..), pos(W, W));
604
605        // 1 is below the multiples of 2
606        assert_eq!(Div(1).partial_rcmp(Div(2)..), pos(O, W));
607        assert_eq!(Div(1).partial_rcmp(&Div(2)..), pos(O, W));
608
609        // 2 is incomparable with the multiples of 3
610        assert_eq!(Div(2).partial_rcmp(Div(3)..), pos(I, W));
611        assert_eq!(Div(2).partial_rcmp(&Div(3)..), pos(I, W));
612        assert_eq!(Div(2).partial_rcmp(Div(3)..).ordering(), None);
613    }
614
615    #[test]
616    fn range_to() {
617        // 4 is a multiple of all divisors of 2, hence above ]-inf, 2[
618        assert_eq!(Div(4).partial_rcmp(..Div(2)), pos(W, O));
619        assert_eq!(Div(4).partial_rcmp(..&Div(2)), pos(W, O));
620
621        // 1 is a divisor of 2
622        assert_eq!(Div(1).partial_rcmp(..Div(2)), pos(W, W));
623        assert_eq!(Div(1).partial_rcmp(..&Div(2)), pos(W, W));
624
625        // 3 is incomparable with the divisors of 10
626        assert_eq!(Div(3).partial_rcmp(..Div(10)), pos(W, I));
627        assert_eq!(Div(3).partial_rcmp(..&Div(10)), pos(W, I));
628        assert_eq!(Div(3).partial_rcmp(..Div(10)).ordering(), None);
629    }
630
631    #[test]
632    fn range() {
633        // 3 is a multiple of all divisors of 3, hence above [1, 3[
634        assert_eq!(Div(3).partial_rcmp(Div(1)..Div(3)), pos(W, O));
635        assert_eq!(Div(3).partial_rcmp(&Div(1)..&Div(3)), pos(W, O));
636
637        // 6 is a multiple of 2 and a divisor of 12
638        assert_eq!(Div(6).partial_rcmp(Div(2)..Div(12)), pos(W, W));
639        assert_eq!(Div(6).partial_rcmp(&Div(2)..&Div(12)), pos(W, W));
640
641        // 2 divides all multiples of 4 that divide 8, hence below [4, 8[
642        assert_eq!(Div(2).partial_rcmp(Div(4)..Div(8)), pos(O, W));
643        assert_eq!(Div(2).partial_rcmp(&Div(4)..&Div(8)), pos(O, W));
644
645        // 3 is incomparable with 4 (lower bound) but divides 12 (within the upper bound)
646        assert_eq!(Div(3).partial_rcmp(Div(4)..Div(12)), pos(I, W));
647        assert_eq!(Div(3).partial_rcmp(&Div(4)..&Div(12)), pos(I, W));
648        assert_eq!(Div(3).partial_rcmp(Div(4)..Div(12)).ordering(), None);
649    }
650
651    #[test]
652    fn range_inclusive() {
653        // 6 is a multiple of all divisors of 3, hence above [1, 3]
654        assert_eq!(Div(6).partial_rcmp(Div(1)..=Div(3)), pos(W, O));
655        assert_eq!(Div(6).partial_rcmp(&Div(1)..=&Div(3)), pos(W, O));
656
657        // 6 is a multiple of 6 and a divisor of 6
658        assert_eq!(Div(6).partial_rcmp(Div(6)..=Div(6)), pos(W, W));
659        assert_eq!(Div(6).partial_rcmp(&Div(6)..=&Div(6)), pos(W, W));
660
661        // 2 divides all multiples of 4 that divide 8, hence below [4, 8]
662        assert_eq!(Div(2).partial_rcmp(Div(4)..=Div(8)), pos(O, W));
663        assert_eq!(Div(2).partial_rcmp(&Div(4)..=&Div(8)), pos(O, W));
664
665        // 3 is incomparable with 4 (lower bound) but divides 12 (within the upper bound)
666        assert_eq!(Div(3).partial_rcmp(Div(4)..=Div(12)), pos(I, W));
667        assert_eq!(Div(3).partial_rcmp(&Div(4)..=&Div(12)), pos(I, W));
668    }
669
670    #[test]
671    fn range_to_inclusive() {
672        // 4 is a multiple of all divisors of 2, hence above ]-inf, 2]
673        assert_eq!(Div(4).partial_rcmp(..=Div(2)), pos(W, O));
674        assert_eq!(Div(4).partial_rcmp(..=&Div(2)), pos(W, O));
675
676        // 1 is a divisor of 2
677        assert_eq!(Div(1).partial_rcmp(..=Div(2)), pos(W, W));
678        assert_eq!(Div(1).partial_rcmp(..=&Div(2)), pos(W, W));
679
680        // 3 is incomparable with the divisors of 10
681        assert_eq!(Div(3).partial_rcmp(..=Div(10)), pos(W, I));
682        assert_eq!(Div(3).partial_rcmp(..=&Div(10)), pos(W, I));
683    }
684
685    #[test]
686    fn bounds_full() {
687        let bounds: (Bound<Div>, Bound<Div>) = (Bound::Unbounded, Bound::Unbounded);
688        assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
689    }
690
691    #[test]
692    fn bounds_from() {
693        // 1 is a multiple of 1
694        let bounds = (Bound::Included(Div(1)), Bound::Unbounded);
695        assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
696
697        let bounds = (Bound::Included(&Div(1)), Bound::Unbounded);
698        assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
699
700        // 1 is below all multiples of 2
701        let bounds = (Bound::Included(Div(2)), Bound::Unbounded);
702        assert_eq!(Div(1).partial_rcmp(bounds), pos(O, W));
703
704        let bounds = (Bound::Included(&Div(2)), Bound::Unbounded);
705        assert_eq!(Div(1).partial_rcmp(bounds), pos(O, W));
706
707        // 2 is incomparable with the multiples of 3
708        let bounds = (Bound::Included(Div(3)), Bound::Unbounded);
709        assert_eq!(Div(2).partial_rcmp(bounds), pos(I, W));
710
711        let bounds = (Bound::Included(&Div(3)), Bound::Unbounded);
712        assert_eq!(Div(2).partial_rcmp(bounds), pos(I, W));
713    }
714
715    #[test]
716    fn bounds_to() {
717        // 4 is above ]-inf, 2[
718        let bounds = (Bound::Unbounded, Bound::Excluded(Div(2)));
719        assert_eq!(Div(4).partial_rcmp(bounds), pos(W, O));
720
721        // 1 is inside ]-inf, 2[
722        let bounds = (Bound::Unbounded, Bound::Excluded(Div(2)));
723        assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
724
725        // 3 is incomparable with the divisors of 10
726        let bounds = (Bound::Unbounded, Bound::Excluded(&Div(10)));
727        assert_eq!(Div(3).partial_rcmp(bounds), pos(W, I));
728    }
729
730    #[test]
731    fn bounds() {
732        // 3 is above [1, 3[
733        let bounds = (Bound::Included(Div(1)), Bound::Excluded(Div(3)));
734        assert_eq!(Div(3).partial_rcmp(bounds), pos(W, O));
735
736        // 6 is inside [2, 12[
737        let bounds = (Bound::Included(&Div(2)), Bound::Excluded(&Div(12)));
738        assert_eq!(Div(6).partial_rcmp(bounds), pos(W, W));
739
740        // 2 is below [4, 8[
741        let bounds = (Bound::Included(Div(4)), Bound::Excluded(Div(8)));
742        assert_eq!(Div(2).partial_rcmp(bounds), pos(O, W));
743    }
744
745    #[test]
746    fn bounds_inclusive() {
747        // 6 is above [1, 3]
748        let bounds = (Bound::Included(Div(1)), Bound::Included(Div(3)));
749        assert_eq!(Div(6).partial_rcmp(bounds), pos(W, O));
750
751        // 6 is inside [6, 6]
752        let bounds = (Bound::Included(&Div(6)), Bound::Included(&Div(6)));
753        assert_eq!(Div(6).partial_rcmp(bounds), pos(W, W));
754
755        // 2 is below [4, 8]
756        let bounds = (Bound::Included(Div(4)), Bound::Included(Div(8)));
757        assert_eq!(Div(2).partial_rcmp(bounds), pos(O, W));
758    }
759
760    #[test]
761    fn bounds_to_inclusive() {
762        // 4 is above ]-inf, 2]
763        let bounds = (Bound::Unbounded, Bound::Included(Div(2)));
764        assert_eq!(Div(4).partial_rcmp(bounds), pos(W, O));
765
766        // 1 is inside ]-inf, 2]
767        let bounds = (Bound::Unbounded, Bound::Included(&Div(2)));
768        assert_eq!(Div(1).partial_rcmp(bounds), pos(W, W));
769    }
770
771    #[test]
772    fn bounds_exclusive_inclusive() {
773        // 6 is above ]1, 3]
774        let bounds = (Bound::Excluded(Div(1)), Bound::Included(Div(3)));
775        assert_eq!(Div(6).partial_rcmp(bounds), pos(W, O));
776
777        // 1 is below ]1, 2]: 1 == 1 violates the excluded lower bound
778        let bounds = (Bound::Excluded(Div(1)), Bound::Included(Div(2)));
779        assert_eq!(Div(1).partial_rcmp(bounds), pos(O, W));
780    }
781
782    #[test]
783    fn bounds_as_reference() {
784        let bounds = Div(2)..Div(12);
785        assert_eq!(Div(6).partial_rcmp(&bounds), pos(W, W));
786        assert_eq!(Div(6).partial_rcmp(bounds), pos(W, W));
787    }
788
789    /// The key cases the old `Option<RangeOrdering>` design lost: a value comparable with
790    /// one bound and incomparable with the other. The per-bound information is preserved.
791    #[test]
792    fn comparable_to_one_bound_only() {
793        // 4 is a multiple of 2 (within the lower bound) but incomparable with 9
794        assert_eq!(Div(4).partial_rcmp(Div(2)..Div(9)), pos(W, I));
795        assert_eq!(Div(4).partial_rcmp(Div(2)..Div(9)).ordering(), None);
796
797        // 2 is below 4 (outside the lower bound) but incomparable with 9
798        assert_eq!(Div(2).partial_rcmp(Div(4)..Div(9)), pos(O, I));
799        assert_eq!(Div(2).partial_rcmp(Div(4)..Div(9)).ordering(), None);
800
801        // 4 is incomparable with 3 but a divisor of 12 (within the upper bound)
802        assert_eq!(Div(4).partial_rcmp(Div(3)..Div(12)), pos(I, W));
803        assert_eq!(Div(4).partial_rcmp(Div(3)..Div(12)).ordering(), None);
804    }
805
806    #[test]
807    fn empty_ranges() {
808        // [8, 2[ is inverted; 4 is a divisor of 8 (below the lower bound) and a multiple
809        // of 2 (above the upper bound), witnessing the emptiness.
810        assert_eq!(Div(4).partial_rcmp(Div(8)..Div(2)), pos(O, O));
811        assert_eq!(
812            Div(4).partial_rcmp(Div(8)..Div(2)).ordering(),
813            Some(RangeOrdering::Empty)
814        );
815    }
816}