1
//! This module exposes helpers for working with types that implement
2
//! [`RangeBounds`].
3

            
4
use std::cmp::{self, Ord};
5
use std::ops::{Bound, RangeBounds};
6

            
7
/// An extension trait for [`RangeBounds`].
8
pub trait RangeBoundsExt<T>: RangeBounds<T> {
9
    /// Compute the intersection of two `RangeBound`s.
10
    ///
11
    /// In essence, this computes the intersection of the intervals described by bounds of the
12
    /// two objects.
13
    ///
14
    /// Returns `None` if the intersection of the two ranges is the empty set.
15
    fn intersect<'a, U: RangeBounds<T>>(
16
        &'a self,
17
        other: &'a U,
18
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)>;
19
}
20

            
21
impl<T, R> RangeBoundsExt<T> for R
22
where
23
    R: RangeBounds<T>,
24
    T: Ord,
25
{
26
40508
    fn intersect<'a, U: RangeBounds<T>>(
27
40508
        &'a self,
28
40508
        other: &'a U,
29
40508
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)> {
30
        use Bound::*;
31

            
32
40508
        let this_start = self.start_bound();
33
40508
        let other_start = other.start_bound();
34
40508
        let this_end = self.end_bound();
35
40508
        let other_end = other.end_bound();
36

            
37
40508
        let start = bounds_max(this_start, other_start);
38
40508
        let end = bounds_min(this_end, other_end);
39

            
40
40508
        match (start, end) {
41
10068
            (Excluded(start), Excluded(end)) | (Included(start), Excluded(end)) if start == end => {
42
                // The interval (n, n) = [n, n) = {} (empty set).
43
1528
                None
44
            }
45
7184
            (Included(start), Included(end))
46
7944
            | (Included(start), Excluded(end))
47
7892
            | (Excluded(start), Included(end))
48
8852
            | (Excluded(start), Excluded(end))
49
8768
                if start > end =>
50
            {
51
                // For any a > b, the intervals [a, b], [a, b), (a, b], (a, b) are empty.
52
31872
                None
53
            }
54
7108
            _ => Some((start, end)),
55
        }
56
40508
    }
57
}
58

            
59
/// Return the largest of `b1` and `b2`.
60
///
61
/// If one of the bounds is [Unbounded](Bound::Unbounded), the other will be returned.
62
40508
fn bounds_max<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
63
    use Bound::*;
64

            
65
40508
    match (b1, b2) {
66
9888
        (Included(b1), Included(b2)) => Included(cmp::max(b1, b2)),
67
10188
        (Excluded(b1), Excluded(b2)) => Excluded(cmp::max(b1, b2)),
68

            
69
9976
        (Excluded(b1), Included(b2)) if b1 >= b2 => Excluded(b1),
70
4464
        (Excluded(_), Included(b2)) => Included(b2),
71

            
72
9976
        (Included(b1), Excluded(b2)) if b2 >= b1 => Excluded(b2),
73
4464
        (Included(b1), Excluded(_)) => Included(b1),
74

            
75
480
        (b, Unbounded) | (Unbounded, b) => b,
76
    }
77
40508
}
78

            
79
/// Return the smallest of `b1` and `b2`.
80
///
81
/// If one of the bounds is [Unbounded](Bound::Unbounded), the other will be returned.
82
40508
fn bounds_min<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
83
    use Bound::*;
84

            
85
40508
    match (b1, b2) {
86
10176
        (Included(b1), Included(b2)) => Included(cmp::min(b1, b2)),
87
9972
        (Excluded(b1), Excluded(b2)) => Excluded(cmp::min(b1, b2)),
88

            
89
10158
        (Excluded(b1), Included(b2)) if b1 <= b2 => Excluded(b1),
90
4448
        (Excluded(_), Included(b2)) => Included(b2),
91

            
92
10158
        (Included(b1), Excluded(b2)) if b2 <= b1 => Excluded(b2),
93
4448
        (Included(b1), Excluded(_)) => Included(b1),
94

            
95
44
        (b, Unbounded) | (Unbounded, b) => b,
96
    }
97
40508
}
98

            
99
#[cfg(test)]
100
mod test {
101
    // @@ begin test lint list maintained by maint/add_warning @@
102
    #![allow(clippy::bool_assert_comparison)]
103
    #![allow(clippy::clone_on_copy)]
104
    #![allow(clippy::dbg_macro)]
105
    #![allow(clippy::mixed_attributes_style)]
106
    #![allow(clippy::print_stderr)]
107
    #![allow(clippy::print_stdout)]
108
    #![allow(clippy::single_char_pattern)]
109
    #![allow(clippy::unwrap_used)]
110
    #![allow(clippy::unchecked_time_subtraction)]
111
    #![allow(clippy::useless_vec)]
112
    #![allow(clippy::needless_pass_by_value)]
113
    #![allow(clippy::string_slice)] // See arti#2571
114
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
115
    use super::*;
116
    use Bound::{Excluded as Excl, Included as Incl, Unbounded};
117
    use std::fmt::Debug;
118
    use web_time_compat::{Duration, SystemTime, SystemTimeExt};
119

            
120
    /// A helper that computes the intersection of `range1` and `range2`.
121
    ///
122
    /// This function also asserts that the intersection operation is commutative.
123
    fn intersect<'a, T, R: RangeBounds<T>>(
124
        range1: &'a R,
125
        range2: &'a R,
126
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)>
127
    where
128
        T: PartialEq + Ord + Debug,
129
    {
130
        let intersection1 = range1.intersect(range2);
131
        let intersection2 = range2.intersect(range1);
132

            
133
        assert_eq!(intersection1, intersection2);
134

            
135
        intersection1
136
    }
137

            
138
    /// A helper for randomly generating either an inclusive or an exclusive bound with a
139
    /// particular value.
140
    fn random_bound<T>(value: T) -> Bound<T> {
141
        if rand::random() {
142
            Bound::Included(value)
143
        } else {
144
            Bound::Excluded(value)
145
        }
146
    }
147

            
148
    #[test]
149
    fn no_overlap() {
150
        #[allow(clippy::type_complexity)]
151
        const NON_OVERLAPPING_RANGES: &[(
152
            (Bound<usize>, Bound<usize>),
153
            (Bound<usize>, Bound<usize>),
154
        )] = &[
155
            // (1, 2) and (3, 4)
156
            ((Excl(1), Excl(2)), (Excl(3), Excl(4))),
157
            // (1, 2) and (2, 3)
158
            ((Excl(1), Excl(2)), (Excl(2), Excl(3))),
159
            // (1, 2) and [2, 3)
160
            ((Excl(1), Excl(2)), (Incl(2), Excl(3))),
161
            // (1, 2) and [2, 3]
162
            ((Excl(1), Excl(2)), (Incl(3), Incl(4))),
163
            // (-inf, 2) and [2, 3]
164
            ((Unbounded, Excl(2)), (Incl(2), Incl(3))),
165
            // (-inf, 2) and (2, inf)
166
            ((Unbounded, Excl(2)), (Excl(2), Unbounded)),
167
            // (-inf, 2) and [2, inf)
168
            ((Unbounded, Excl(2)), (Incl(2), Unbounded)),
169
        ];
170

            
171
        for (range1, range2) in NON_OVERLAPPING_RANGES {
172
            let intersection = intersect(range1, range2);
173
            assert!(
174
                intersection.is_none(),
175
                "{:?} and {:?} => {:?}",
176
                range1,
177
                range2,
178
                intersection
179
            );
180
        }
181
    }
182

            
183
    #[test]
184
    fn intersect_unbounded_start() {
185
        // (-inf, 3)
186
        let range1 = (Unbounded, Excl(3));
187
        // [2, 5]
188
        let range2 = (Incl(2), Incl(5));
189

            
190
        let intersection = intersect(&range1, &range2).unwrap();
191

            
192
        // intersection = [2 3]
193
        assert_eq!(intersection.start_bound(), Bound::Included(&2));
194
        assert_eq!(intersection.end_bound(), Bound::Excluded(&3));
195
    }
196

            
197
    #[test]
198
    fn intersect_unbounded_end() {
199
        // (8, inf)
200
        let range1 = (Excl(8), Unbounded);
201
        // [8, 20]
202
        let range2 = (Incl(8), Incl(20));
203

            
204
        let intersection = intersect(&range1, &range2).unwrap();
205

            
206
        // intersection = (8, 20]
207
        assert_eq!(intersection.start_bound(), Bound::Excluded(&8));
208
        assert_eq!(intersection.end_bound(), Bound::Included(&20));
209
    }
210

            
211
    #[test]
212
    fn intersect_unbounded_range() {
213
        #[allow(clippy::type_complexity)]
214
        const RANGES: &[(Bound<usize>, Bound<usize>)] = &[
215
            // (1, 2)
216
            (Excl(1), Excl(2)),
217
            // (1, 2]
218
            (Excl(1), Incl(2)),
219
            // [1, 2]
220
            (Incl(1), Incl(2)),
221
            // [1, 2)
222
            (Incl(1), Excl(2)),
223
            // (1, inf)
224
            (Excl(1), Unbounded),
225
            // [1, inf)
226
            (Incl(1), Unbounded),
227
            // (-inf, 2)
228
            (Unbounded, Excl(2)),
229
            // (-inf, 2]
230
            (Unbounded, Incl(2)),
231
        ];
232

            
233
        // The intersection of any interval I with (Unbounded, Unbounded) will be I.
234
        let range1 = (Unbounded, Unbounded);
235

            
236
        for range2 in RANGES {
237
            let range2 = (range2.0.as_ref(), range2.1.as_ref());
238
            assert_eq!(intersect(&range1, &range2).unwrap(), range2);
239
        }
240
    }
241

            
242
    #[test]
243
    fn intersect_time_bounds() {
244
        const MIN: Duration = Duration::from_secs(60);
245

            
246
        // time (relative to now):  0   1   2   3
247
        //                          |   |   |   |
248
        // [t1, t2]:                [.......]
249
        // [t3, t4]:                    [.......]
250
        // intersection:                [...]
251
        let now = SystemTime::get();
252
        let t1 = now;
253
        let t2 = now + 2 * MIN;
254

            
255
        let t3 = now + 1 * MIN;
256
        let t4 = now + 3 * MIN;
257

            
258
        let b1 = (Bound::Included(t1), Bound::Included(t2));
259
        let b2 = (Bound::Included(t3), Bound::Included(t4));
260
        let expected = (Bound::Included(&t3), Bound::Included(&t2));
261
        assert_eq!(intersect(&b1, &b2).unwrap(), expected);
262

            
263
        //  t1  -  -  t2  -  -
264
        //                   t3  -  -  t4
265
        //
266
        // time (relative to now):  0   1   2   3   4   5   6   7
267
        //                          |   |   |   |   |   |   |   |
268
        // [t1, t2]:                [.......]
269
        // [t3, t4]:                                [............]
270
        let t3 = now + 4 * MIN;
271
        let t4 = now + 7 * MIN;
272
        let b2 = (Bound::Included(t3), Bound::Included(t4));
273
        assert!(intersect(&b1, &b2).is_none());
274
    }
275

            
276
    #[test]
277
    fn combinatorial() {
278
        for i in 0..10 {
279
            for j in 0..10 {
280
                for k in 0..10 {
281
                    for l in 0..10 {
282
                        let range1 = (random_bound(i), random_bound(j));
283
                        let range2 = (random_bound(k), random_bound(l));
284

            
285
                        let intersection = intersect(&range1, &range2);
286

            
287
                        for witness in 0..10 {
288
                            let c1 = range1.contains(&witness);
289
                            let c2 = range2.contains(&witness);
290
                            let both_contain_witness = c1 && c2;
291

            
292
                            if both_contain_witness {
293
                                // If both ranges contain `witness` they definitely intersect.
294
                                assert!(intersection.unwrap().contains(&witness));
295
                            } else if let Some(intersection) = intersection {
296
                                // If one of them doesn't contain `witness`, `witness` is
297
                                // definitely not part of the intersection.
298
                                assert!(!intersection.contains(&witness));
299
                            }
300
                        }
301
                    }
302
                }
303
            }
304
        }
305
    }
306
}