version_ranges/lib.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! This crate contains a performance-optimized type for generic version ranges and operations on
4//! them.
5//!
6//! [`Ranges`] can represent version selectors such as `(>=1, <2) OR (==3) OR (>4)`. Internally,
7//! it is an ordered list of contiguous intervals (segments) with inclusive, exclusive or open-ended
8//! ends, similar to a `Vec<(Bound<T>, Bound<T>)>`.
9//!
10//! You can construct a basic range from one of the following build blocks. All other ranges are
11//! concatenation, union, and complement of these basic ranges.
12//! - [empty()](Ranges::empty): No version
13//! - [full()](Ranges::full): All versions
14//! - [singleton(v)](Ranges::singleton): Only the version v exactly
15//! - [higher_than(v)](Ranges::higher_than): All versions `v <= versions`
16//! - [strictly_higher_than(v)](Ranges::strictly_higher_than): All versions `v < versions`
17//! - [lower_than(v)](Ranges::lower_than): All versions `versions <= v`
18//! - [strictly_lower_than(v)](Ranges::strictly_lower_than): All versions `versions < v`
19//! - [between(v1, v2)](Ranges::between): All versions `v1 <= versions < v2`
20//!
21//! [`Ranges`] is generic over any type that implements [`Ord`] + [`Clone`] and can represent all
22//! kinds of slices with ordered coordinates, not just version ranges. While built as a
23//! performance-critical piece of [pubgrub](https://github.com/pubgrub-rs/pubgrub), it can be
24//! adopted for other domains, too.
25//!
26//! Note that there are limitations to the equality implementation: Given a `Ranges<u32>`,
27//! the segments `(Unbounded, Included(42u32))` and `(Included(0), Included(42u32))` as well as
28//! `(Included(1), Included(5))` and `(Included(1), Included(3)) + (Included(4), Included(5))`
29//! are reported as unequal, even though the match the same versions: We can't tell that there isn't
30//! a version between `0` and `-inf` or `3` and `4` respectively.
31//!
32//! ## Optional features
33//!
34//! * `serde`: serialization and deserialization for the version range, given that the version type
35//! also supports it.
36//! * `proptest`: Exports are proptest strategy for [`Ranges<u32>`].
37
38use std::borrow::Borrow;
39use std::cmp::Ordering;
40use std::fmt::{Debug, Display, Formatter};
41use std::ops::Bound::{self, Excluded, Included, Unbounded};
42use std::ops::RangeBounds;
43
44#[cfg(any(feature = "proptest", test))]
45use proptest::prelude::*;
46use smallvec::{smallvec, SmallVec};
47
48/// Ranges represents multiple intervals of a continuous range of monotone increasing values.
49///
50/// Internally, [`Ranges`] are an ordered list of segments, where segment is a bounds pair.
51///
52/// Invariants:
53/// 1. The segments are sorted, from lowest to highest (through `Ord`).
54/// 2. Each segment contains at least one version (start < end).
55/// 3. There is at least one version between two segments.
56///
57/// These ensure that equivalent instances have an identical representation, which is important
58/// for `Eq` and `Hash`. Note that this representation cannot strictly guaranty equality of
59/// [`Ranges`] with equality of its representation without also knowing the nature of the underlying
60/// versions. In particular, if the version space is discrete, different representations, using
61/// different types of bounds (exclusive/inclusive) may correspond to the same set of existing
62/// versions. It is a tradeoff we acknowledge, but which makes representations of continuous version
63/// sets more accessible, to better handle features like pre-releases and other types of version
64/// modifiers. For example, `[(Included(3u32), Excluded(7u32))]` and
65/// `[(Included(3u32), Included(6u32))]` refer to the same version set, since there is no version
66/// between 6 and 7, which this crate doesn't know about.
67
68#[derive(Debug, Clone, Eq, PartialEq, Hash)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70#[cfg_attr(feature = "serde", serde(transparent))]
71pub struct Ranges<V> {
72 /// Profiling in <https://github.com/pubgrub-rs/pubgrub/pull/262#discussion_r1804276278> showed
73 /// that a single stack entry is the most efficient. This is most likely due to `Interval<V>`
74 /// being large.
75 segments: SmallVec<[Interval<V>; 1]>,
76}
77
78// TODO: Replace the tuple type with a custom enum inlining the bounds to reduce the type's size.
79type Interval<V> = (Bound<V>, Bound<V>);
80
81impl<V> Ranges<V> {
82 /// Empty set of versions.
83 pub fn empty() -> Self {
84 Self {
85 segments: SmallVec::new(),
86 }
87 }
88
89 /// Set of all possible versions
90 pub fn full() -> Self {
91 Self {
92 segments: smallvec![(Unbounded, Unbounded)],
93 }
94 }
95
96 /// Set of all versions higher or equal to some version
97 pub fn higher_than(v: impl Into<V>) -> Self {
98 Self {
99 segments: smallvec![(Included(v.into()), Unbounded)],
100 }
101 }
102
103 /// Set of all versions higher to some version
104 pub fn strictly_higher_than(v: impl Into<V>) -> Self {
105 Self {
106 segments: smallvec![(Excluded(v.into()), Unbounded)],
107 }
108 }
109
110 /// Set of all versions lower to some version
111 pub fn strictly_lower_than(v: impl Into<V>) -> Self {
112 Self {
113 segments: smallvec![(Unbounded, Excluded(v.into()))],
114 }
115 }
116
117 /// Set of all versions lower or equal to some version
118 pub fn lower_than(v: impl Into<V>) -> Self {
119 Self {
120 segments: smallvec![(Unbounded, Included(v.into()))],
121 }
122 }
123
124 /// Set of versions greater or equal to `v1` but less than `v2`.
125 pub fn between(v1: impl Into<V>, v2: impl Into<V>) -> Self {
126 Self {
127 segments: smallvec![(Included(v1.into()), Excluded(v2.into()))],
128 }
129 }
130
131 /// Whether the set is empty, i.e. it has not ranges
132 pub fn is_empty(&self) -> bool {
133 self.segments.is_empty()
134 }
135}
136
137impl<V: Clone> Ranges<V> {
138 /// Set containing exactly one version
139 pub fn singleton(v: impl Into<V>) -> Self {
140 let v = v.into();
141 Self {
142 segments: smallvec![(Included(v.clone()), Included(v))],
143 }
144 }
145
146 /// Returns the complement, which contains everything not included in `self`.
147 pub fn complement(&self) -> Self {
148 match self.segments.first() {
149 // Complement of ∅ is ∞
150 None => Self::full(),
151
152 // Complement of ∞ is ∅
153 Some((Unbounded, Unbounded)) => Self::empty(),
154
155 // First high bound is +∞
156 Some((Included(v), Unbounded)) => Self::strictly_lower_than(v.clone()),
157 Some((Excluded(v), Unbounded)) => Self::lower_than(v.clone()),
158
159 Some((Unbounded, Included(v))) => {
160 Self::negate_segments(Excluded(v.clone()), &self.segments[1..])
161 }
162 Some((Unbounded, Excluded(v))) => {
163 Self::negate_segments(Included(v.clone()), &self.segments[1..])
164 }
165 Some((Included(_), Included(_)))
166 | Some((Included(_), Excluded(_)))
167 | Some((Excluded(_), Included(_)))
168 | Some((Excluded(_), Excluded(_))) => Self::negate_segments(Unbounded, &self.segments),
169 }
170 }
171
172 /// Helper function performing the negation of intervals in segments.
173 fn negate_segments(start: Bound<V>, segments: &[Interval<V>]) -> Self {
174 let mut complement_segments = SmallVec::new();
175 let mut start = start;
176 for (v1, v2) in segments {
177 complement_segments.push((
178 start,
179 match v1 {
180 Included(v) => Excluded(v.clone()),
181 Excluded(v) => Included(v.clone()),
182 Unbounded => unreachable!(),
183 },
184 ));
185 start = match v2 {
186 Included(v) => Excluded(v.clone()),
187 Excluded(v) => Included(v.clone()),
188 Unbounded => Unbounded,
189 }
190 }
191 if !matches!(start, Unbounded) {
192 complement_segments.push((start, Unbounded));
193 }
194
195 Self {
196 segments: complement_segments,
197 }
198 }
199}
200
201impl<V: Ord> Ranges<V> {
202 /// If self contains exactly a single version, return it, otherwise, return [None].
203 pub fn as_singleton(&self) -> Option<&V> {
204 match self.segments.as_slice() {
205 [(Included(v1), Included(v2))] => {
206 if v1 == v2 {
207 Some(v1)
208 } else {
209 None
210 }
211 }
212 _ => None,
213 }
214 }
215
216 /// Convert to something that can be used with
217 /// [BTreeMap::range](std::collections::BTreeMap::range).
218 /// All versions contained in self, will be in the output,
219 /// but there may be versions in the output that are not contained in self.
220 /// Returns None if the range is empty.
221 pub fn bounding_range(&self) -> Option<(Bound<&V>, Bound<&V>)> {
222 self.segments.first().map(|(start, _)| {
223 let end = self
224 .segments
225 .last()
226 .expect("if there is a first element, there must be a last element");
227 (start.as_ref(), end.1.as_ref())
228 })
229 }
230
231 /// Returns true if self contains the specified value.
232 pub fn contains(&self, version: &V) -> bool {
233 self.segments
234 .binary_search_by(|segment| {
235 // We have to reverse because we need the segment wrt to the version, while
236 // within bounds tells us the version wrt to the segment.
237 within_bounds(version, segment).reverse()
238 })
239 // An equal interval is one that contains the version
240 .is_ok()
241 }
242
243 /// Returns true if self contains the specified values.
244 ///
245 /// The `versions` iterator must be sorted.
246 /// Functionally equivalent to `versions.map(|v| self.contains(v))`.
247 /// Except it runs in `O(size_of_range + len_of_versions)` not `O(size_of_range * len_of_versions)`
248 pub fn contains_many<'s, I, BV>(&'s self, versions: I) -> impl Iterator<Item = bool> + 's
249 where
250 I: Iterator<Item = BV> + 's,
251 BV: Borrow<V> + 's,
252 {
253 #[cfg(debug_assertions)]
254 let mut last: Option<BV> = None;
255 versions.scan(0, move |i, v| {
256 #[cfg(debug_assertions)]
257 {
258 if let Some(l) = last.as_ref() {
259 assert!(
260 l.borrow() <= v.borrow(),
261 "`contains_many` `versions` argument incorrectly sorted"
262 );
263 }
264 }
265 while let Some(segment) = self.segments.get(*i) {
266 match within_bounds(v.borrow(), segment) {
267 Ordering::Less => return Some(false),
268 Ordering::Equal => return Some(true),
269 Ordering::Greater => *i += 1,
270 }
271 }
272 #[cfg(debug_assertions)]
273 {
274 last = Some(v);
275 }
276 Some(false)
277 })
278 }
279
280 /// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`.
281 pub fn from_range_bounds<R, IV>(bounds: R) -> Self
282 where
283 R: RangeBounds<IV>,
284 IV: Clone + Into<V>,
285 {
286 let start = match bounds.start_bound() {
287 Included(v) => Included(v.clone().into()),
288 Excluded(v) => Excluded(v.clone().into()),
289 Unbounded => Unbounded,
290 };
291 let end = match bounds.end_bound() {
292 Included(v) => Included(v.clone().into()),
293 Excluded(v) => Excluded(v.clone().into()),
294 Unbounded => Unbounded,
295 };
296 if valid_segment(&start, &end) {
297 Self {
298 segments: smallvec![(start, end)],
299 }
300 } else {
301 Self::empty()
302 }
303 }
304
305 /// See [`Ranges`] for the invariants checked.
306 fn check_invariants(self) -> Self {
307 if cfg!(debug_assertions) {
308 for p in self.segments.as_slice().windows(2) {
309 assert!(end_before_start_with_gap(&p[0].1, &p[1].0));
310 }
311 for (s, e) in self.segments.iter() {
312 assert!(valid_segment(s, e));
313 }
314 }
315 self
316 }
317}
318
319/// Implementing `PartialOrd` for start `Bound` of an interval.
320///
321/// Legend: `∞` is unbounded, `[1,2]` is `>=1,<=2`, `]1,2[` is `>1,<2`.
322///
323/// ```text
324/// left: ∞-------]
325/// right: [-----]
326/// left is smaller, since it starts earlier.
327///
328/// left: [-----]
329/// right: ]-----]
330/// left is smaller, since it starts earlier.
331/// ```
332fn cmp_bounds_start<V: PartialOrd>(left: Bound<&V>, right: Bound<&V>) -> Option<Ordering> {
333 Some(match (left, right) {
334 // left: ∞-----
335 // right: ∞-----
336 (Unbounded, Unbounded) => Ordering::Equal,
337 // left: [---
338 // right: ∞-----
339 (Included(_left), Unbounded) => Ordering::Greater,
340 // left: ]---
341 // right: ∞-----
342 (Excluded(_left), Unbounded) => Ordering::Greater,
343 // left: ∞-----
344 // right: [---
345 (Unbounded, Included(_right)) => Ordering::Less,
346 // left: [----- OR [----- OR [-----
347 // right: [--- OR [----- OR [---
348 (Included(left), Included(right)) => left.partial_cmp(right)?,
349 (Excluded(left), Included(right)) => match left.partial_cmp(right)? {
350 // left: ]-----
351 // right: [---
352 Ordering::Less => Ordering::Less,
353 // left: ]-----
354 // right: [---
355 Ordering::Equal => Ordering::Greater,
356 // left: ]---
357 // right: [-----
358 Ordering::Greater => Ordering::Greater,
359 },
360 // left: ∞-----
361 // right: ]---
362 (Unbounded, Excluded(_right)) => Ordering::Less,
363 (Included(left), Excluded(right)) => match left.partial_cmp(right)? {
364 // left: [-----
365 // right: ]---
366 Ordering::Less => Ordering::Less,
367 // left: [-----
368 // right: ]---
369 Ordering::Equal => Ordering::Less,
370 // left: [---
371 // right: ]-----
372 Ordering::Greater => Ordering::Greater,
373 },
374 // left: ]----- OR ]----- OR ]---
375 // right: ]--- OR ]----- OR ]-----
376 (Excluded(left), Excluded(right)) => left.partial_cmp(right)?,
377 })
378}
379
380/// Implementing `PartialOrd` for end `Bound` of an interval.
381///
382/// We flip the unbounded ranges from `-∞` to `∞`, while `V`-valued bounds checks remain the same.
383///
384/// Legend: `∞` is unbounded, `[1,2]` is `>=1,<=2`, `]1,2[` is `>1,<2`.
385///
386/// ```text
387/// left: [--------∞
388/// right: [-----]
389/// left is greater, since it starts earlier.
390///
391/// left: [-----[
392/// right: [-----]
393/// left is smaller, since it ends earlier.
394/// ```
395fn cmp_bounds_end<V: PartialOrd>(left: Bound<&V>, right: Bound<&V>) -> Option<Ordering> {
396 Some(match (left, right) {
397 // left: -----∞
398 // right: -----∞
399 (Unbounded, Unbounded) => Ordering::Equal,
400 // left: ---]
401 // right: -----∞
402 (Included(_left), Unbounded) => Ordering::Less,
403 // left: ---[
404 // right: -----∞
405 (Excluded(_left), Unbounded) => Ordering::Less,
406 // left: -----∞
407 // right: ---]
408 (Unbounded, Included(_right)) => Ordering::Greater,
409 // left: -----] OR -----] OR ---]
410 // right: ---] OR -----] OR -----]
411 (Included(left), Included(right)) => left.partial_cmp(right)?,
412 (Excluded(left), Included(right)) => match left.partial_cmp(right)? {
413 // left: ---[
414 // right: -----]
415 Ordering::Less => Ordering::Less,
416 // left: -----[
417 // right: -----]
418 Ordering::Equal => Ordering::Less,
419 // left: -----[
420 // right: ---]
421 Ordering::Greater => Ordering::Greater,
422 },
423 (Unbounded, Excluded(_right)) => Ordering::Greater,
424 (Included(left), Excluded(right)) => match left.partial_cmp(right)? {
425 // left: ---]
426 // right: -----[
427 Ordering::Less => Ordering::Less,
428 // left: -----]
429 // right: -----[
430 Ordering::Equal => Ordering::Greater,
431 // left: -----]
432 // right: ---[
433 Ordering::Greater => Ordering::Greater,
434 },
435 // left: -----[ OR -----[ OR ---[
436 // right: ---[ OR -----[ OR -----[
437 (Excluded(left), Excluded(right)) => left.partial_cmp(right)?,
438 })
439}
440
441impl<V: PartialOrd> PartialOrd for Ranges<V> {
442 /// A simple ordering scheme where we zip the segments and compare all bounds in order. If all
443 /// bounds are equal, the longer range is considered greater. (And if all zipped bounds are
444 /// equal and we have the same number of segments, the ranges are equal).
445 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
446 for (left, right) in self.segments.iter().zip(other.segments.iter()) {
447 let start_cmp = cmp_bounds_start(left.start_bound(), right.start_bound())?;
448 if start_cmp != Ordering::Equal {
449 return Some(start_cmp);
450 }
451 let end_cmp = cmp_bounds_end(left.end_bound(), right.end_bound())?;
452 if end_cmp != Ordering::Equal {
453 return Some(end_cmp);
454 }
455 }
456 Some(self.segments.len().cmp(&other.segments.len()))
457 }
458}
459
460impl<V: Ord> Ord for Ranges<V> {
461 fn cmp(&self, other: &Self) -> Ordering {
462 self.partial_cmp(other)
463 .expect("PartialOrd must be `Some(Ordering)` for types that implement `Ord`")
464 }
465}
466
467/// The ordering of the version wrt to the interval.
468/// ```text
469/// |-------|
470/// ^ ^ ^
471/// less equal greater
472/// ```
473fn within_bounds<V: PartialOrd>(version: &V, segment: &Interval<V>) -> Ordering {
474 let below_lower_bound = match segment {
475 (Excluded(start), _) => version <= start,
476 (Included(start), _) => version < start,
477 (Unbounded, _) => false,
478 };
479 if below_lower_bound {
480 return Ordering::Less;
481 }
482 let below_upper_bound = match segment {
483 (_, Unbounded) => true,
484 (_, Included(end)) => version <= end,
485 (_, Excluded(end)) => version < end,
486 };
487 if below_upper_bound {
488 return Ordering::Equal;
489 }
490 Ordering::Greater
491}
492
493/// A valid segment is one where at least one version fits between start and end
494fn valid_segment<T: PartialOrd>(start: &Bound<T>, end: &Bound<T>) -> bool {
495 match (start, end) {
496 // Singleton interval are allowed
497 (Included(s), Included(e)) => s <= e,
498 (Included(s), Excluded(e)) => s < e,
499 (Excluded(s), Included(e)) => s < e,
500 (Excluded(s), Excluded(e)) => s < e,
501 (Unbounded, _) | (_, Unbounded) => true,
502 }
503}
504
505/// The end of one interval is before the start of the next one, so they can't be concatenated
506/// into a single interval. The `union` method calling with both intervals and then the intervals
507/// switched. If either is true, the intervals are separate in the union and if both are false, they
508/// are merged.
509/// ```text
510/// True for these two:
511/// |----|
512/// |-----|
513/// ^ end ^ start
514/// False for these two:
515/// |----|
516/// |-----|
517/// Here it depends: If they both exclude the position they share, there is a version in between
518/// them that blocks concatenation
519/// |----|
520/// |-----|
521/// ```
522fn end_before_start_with_gap<V: PartialOrd>(end: &Bound<V>, start: &Bound<V>) -> bool {
523 match (end, start) {
524 (_, Unbounded) => false,
525 (Unbounded, _) => false,
526 (Included(left), Included(right)) => left < right,
527 (Included(left), Excluded(right)) => left < right,
528 (Excluded(left), Included(right)) => left < right,
529 (Excluded(left), Excluded(right)) => left <= right,
530 }
531}
532
533fn left_start_is_smaller<V: PartialOrd>(left: Bound<V>, right: Bound<V>) -> bool {
534 match (left, right) {
535 (Unbounded, _) => true,
536 (_, Unbounded) => false,
537 (Included(l), Included(r)) => l <= r,
538 (Excluded(l), Excluded(r)) => l <= r,
539 (Included(l), Excluded(r)) => l <= r,
540 (Excluded(l), Included(r)) => l < r,
541 }
542}
543
544fn left_end_is_smaller<V: PartialOrd>(left: Bound<V>, right: Bound<V>) -> bool {
545 match (left, right) {
546 (_, Unbounded) => true,
547 (Unbounded, _) => false,
548 (Included(l), Included(r)) => l <= r,
549 (Excluded(l), Excluded(r)) => l <= r,
550 (Excluded(l), Included(r)) => l <= r,
551 (Included(l), Excluded(r)) => l < r,
552 }
553}
554
555/// Group adjacent versions locations.
556///
557/// ```text
558/// [None, 3, 6, 7, None] -> [(3, 7)]
559/// [3, 6, 7, None] -> [(None, 7)]
560/// [3, 6, 7] -> [(None, None)]
561/// [None, 1, 4, 7, None, None, None, 8, None, 9] -> [(1, 7), (8, 8), (9, None)]
562/// ```
563fn group_adjacent_locations(
564 mut locations: impl Iterator<Item = Option<usize>>,
565) -> impl Iterator<Item = (Option<usize>, Option<usize>)> {
566 // If the first version matched, then the lower bound of that segment is not needed
567 let mut seg = locations.next().flatten().map(|ver| (None, Some(ver)));
568 std::iter::from_fn(move || {
569 for ver in locations.by_ref() {
570 if let Some(ver) = ver {
571 // As long as were still matching versions, we keep merging into the currently matching segment
572 seg = Some((seg.map_or(Some(ver), |(s, _)| s), Some(ver)));
573 } else {
574 // If we have found a version that doesn't match, then right the merge segment and prepare for a new one.
575 if seg.is_some() {
576 return seg.take();
577 }
578 }
579 }
580 // If the last version matched, then write out the merged segment but the upper bound is not needed.
581 seg.take().map(|(s, _)| (s, None))
582 })
583}
584
585impl<V: Ord + Clone> Ranges<V> {
586 /// Computes the union of this `Ranges` and another.
587 pub fn union(&self, other: &Self) -> Self {
588 let mut output = SmallVec::new();
589 let mut accumulator: Option<(&Bound<_>, &Bound<_>)> = None;
590 let mut left_iter = self.segments.iter().peekable();
591 let mut right_iter = other.segments.iter().peekable();
592 loop {
593 let smaller_interval = match (left_iter.peek(), right_iter.peek()) {
594 (Some((left_start, left_end)), Some((right_start, right_end))) => {
595 if left_start_is_smaller(left_start.as_ref(), right_start.as_ref()) {
596 left_iter.next();
597 (left_start, left_end)
598 } else {
599 right_iter.next();
600 (right_start, right_end)
601 }
602 }
603 (Some((left_start, left_end)), None) => {
604 left_iter.next();
605 (left_start, left_end)
606 }
607 (None, Some((right_start, right_end))) => {
608 right_iter.next();
609 (right_start, right_end)
610 }
611 (None, None) => break,
612 };
613
614 if let Some(accumulator_) = accumulator {
615 if end_before_start_with_gap(accumulator_.1, smaller_interval.0) {
616 output.push((accumulator_.0.clone(), accumulator_.1.clone()));
617 accumulator = Some(smaller_interval);
618 } else {
619 let accumulator_end = match (accumulator_.1, smaller_interval.1) {
620 (_, Unbounded) | (Unbounded, _) => &Unbounded,
621 (Included(l), Excluded(r) | Included(r)) if l == r => accumulator_.1,
622 (Included(l) | Excluded(l), Included(r) | Excluded(r)) => {
623 if l > r {
624 accumulator_.1
625 } else {
626 smaller_interval.1
627 }
628 }
629 };
630 accumulator = Some((accumulator_.0, accumulator_end));
631 }
632 } else {
633 accumulator = Some(smaller_interval)
634 }
635 }
636
637 if let Some(accumulator) = accumulator {
638 output.push((accumulator.0.clone(), accumulator.1.clone()));
639 }
640
641 Self { segments: output }.check_invariants()
642 }
643
644 /// Computes the intersection of two sets of versions.
645 pub fn intersection(&self, other: &Self) -> Self {
646 let mut output = SmallVec::new();
647 let mut left_iter = self.segments.iter().peekable();
648 let mut right_iter = other.segments.iter().peekable();
649 // By the definition of intersection any point that is matched by the output
650 // must have a segment in each of the inputs that it matches.
651 // Therefore, every segment in the output must be the intersection of a segment from each of the inputs.
652 // It would be correct to do the "O(n^2)" thing, by computing the intersection of every segment from one input
653 // with every segment of the other input, and sorting the result.
654 // We can avoid the sorting by generating our candidate segments with an increasing `end` value.
655 while let Some(((left_start, left_end), (right_start, right_end))) =
656 left_iter.peek().zip(right_iter.peek())
657 {
658 // The next smallest `end` value is going to come from one of the inputs.
659 let left_end_is_smaller = left_end_is_smaller(left_end.as_ref(), right_end.as_ref());
660 // Now that we are processing `end` we will never have to process any segment smaller than that.
661 // We can ensure that the input that `end` came from is larger than `end` by advancing it one step.
662 // `end` is the smaller available input, so we know the other input is already larger than `end`.
663 // Note: We can call `other_iter.next_if( == end)`, but the ends lining up is rare enough that
664 // it does not end up being faster in practice.
665 let (other_start, end) = if left_end_is_smaller {
666 left_iter.next();
667 (right_start, left_end)
668 } else {
669 right_iter.next();
670 (left_start, right_end)
671 };
672 // `start` will either come from the input `end` came from or the other input, whichever one is larger.
673 // The intersection is invalid if `start` > `end`.
674 // But, we already know that the segments in our input are valid.
675 // So we do not need to check if the `start` from the input `end` came from is smaller than `end`.
676 // If the `other_start` is larger than end, then the intersection will be invalid.
677 if !valid_segment(other_start, end) {
678 // Note: We can call `this_iter.next_if(!valid_segment(other_start, this_end))` in a loop.
679 // But the checks make it slower for the benchmarked inputs.
680 continue;
681 }
682 let start = match (left_start, right_start) {
683 (Included(l), Included(r)) => Included(std::cmp::max(l, r)),
684 (Excluded(l), Excluded(r)) => Excluded(std::cmp::max(l, r)),
685
686 (Included(i), Excluded(e)) | (Excluded(e), Included(i)) => {
687 if i <= e {
688 Excluded(e)
689 } else {
690 Included(i)
691 }
692 }
693 (s, Unbounded) | (Unbounded, s) => s.as_ref(),
694 };
695 // Now we clone and push a new segment.
696 // By dealing with references until now we ensure that NO cloning happens when we reject the segment.
697 output.push((start.cloned(), end.clone()))
698 }
699
700 Self { segments: output }.check_invariants()
701 }
702
703 /// Return true if there can be no `V` so that `V` is contained in both `self` and `other`.
704 ///
705 /// Note that we don't know that set of all existing `V`s here, so we only check if the segments
706 /// are disjoint, not if no version is contained in both.
707 pub fn is_disjoint(&self, other: &Self) -> bool {
708 // The operation is symmetric
709 let mut left_iter = self.segments.iter().peekable();
710 let mut right_iter = other.segments.iter().peekable();
711
712 while let Some((left, right)) = left_iter.peek().zip(right_iter.peek()) {
713 if !valid_segment(&right.start_bound(), &left.end_bound()) {
714 left_iter.next();
715 } else if !valid_segment(&left.start_bound(), &right.end_bound()) {
716 right_iter.next();
717 } else {
718 return false;
719 }
720 }
721
722 // The remaining element(s) can't intersect anymore
723 true
724 }
725
726 /// Return true if any `V` that is contained in `self` is also contained in `other`.
727 ///
728 /// Note that we don't know that set of all existing `V`s here, so we only check if all
729 /// segments `self` are contained in a segment of `other`.
730 pub fn subset_of(&self, other: &Self) -> bool {
731 let mut containing_iter = other.segments.iter();
732 let mut subset_iter = self.segments.iter();
733 let Some(mut containing_elem) = containing_iter.next() else {
734 // As long as we have subset elements, we need containing elements
735 return subset_iter.next().is_none();
736 };
737
738 for subset_elem in subset_iter {
739 // Check if the current containing element ends before the subset element.
740 // There needs to be another containing element for our subset element in this case.
741 while !valid_segment(&subset_elem.start_bound(), &containing_elem.end_bound()) {
742 if let Some(containing_elem_) = containing_iter.next() {
743 containing_elem = containing_elem_;
744 } else {
745 return false;
746 };
747 }
748
749 let start_contained =
750 left_start_is_smaller(containing_elem.start_bound(), subset_elem.start_bound());
751
752 if !start_contained {
753 // The start element is not contained
754 return false;
755 }
756
757 let end_contained =
758 left_end_is_smaller(subset_elem.end_bound(), containing_elem.end_bound());
759
760 if !end_contained {
761 // The end element is not contained
762 return false;
763 }
764 }
765
766 true
767 }
768
769 /// Returns a simpler representation that contains the same versions.
770 ///
771 /// For every one of the Versions provided in versions the existing range and the simplified range will agree on whether it is contained.
772 /// The simplified version may include or exclude versions that are not in versions as the implementation wishes.
773 ///
774 /// If none of the versions are contained in the original than the range will be returned unmodified.
775 /// If the range includes a single version, it will be returned unmodified.
776 /// If all the versions are contained in the original than the range will be simplified to `full`.
777 ///
778 /// If the given versions are not sorted the correctness of this function is not guaranteed.
779 pub fn simplify<'s, I, BV>(&self, versions: I) -> Self
780 where
781 I: Iterator<Item = BV> + 's,
782 BV: Borrow<V> + 's,
783 {
784 // Do not simplify singletons
785 if self.as_singleton().is_some() {
786 return self.clone();
787 }
788
789 #[cfg(debug_assertions)]
790 let mut last: Option<BV> = None;
791 // Return the segment index in the range for each version in the range, None otherwise
792 let version_locations = versions.scan(0, move |i, v| {
793 #[cfg(debug_assertions)]
794 {
795 if let Some(l) = last.as_ref() {
796 assert!(
797 l.borrow() <= v.borrow(),
798 "`simplify` `versions` argument incorrectly sorted"
799 );
800 }
801 }
802 while let Some(segment) = self.segments.get(*i) {
803 match within_bounds(v.borrow(), segment) {
804 Ordering::Less => return Some(None),
805 Ordering::Equal => return Some(Some(*i)),
806 Ordering::Greater => *i += 1,
807 }
808 }
809 #[cfg(debug_assertions)]
810 {
811 last = Some(v);
812 }
813 Some(None)
814 });
815 let mut kept_segments = group_adjacent_locations(version_locations).peekable();
816
817 // Do not return null sets
818 if kept_segments.peek().is_none() {
819 return self.clone();
820 }
821
822 self.keep_segments(kept_segments)
823 }
824
825 /// Create a new range with a subset of segments at given location bounds.
826 ///
827 /// Each new segment is constructed from a pair of segments, taking the
828 /// start of the first and the end of the second.
829 fn keep_segments(
830 &self,
831 kept_segments: impl Iterator<Item = (Option<usize>, Option<usize>)>,
832 ) -> Ranges<V> {
833 let mut segments = SmallVec::new();
834 for (s, e) in kept_segments {
835 segments.push((
836 s.map_or(Unbounded, |s| self.segments[s].0.clone()),
837 e.map_or(Unbounded, |e| self.segments[e].1.clone()),
838 ));
839 }
840 Self { segments }.check_invariants()
841 }
842
843 /// Iterate over the parts of the range.
844 pub fn iter(&self) -> impl Iterator<Item = (&Bound<V>, &Bound<V>)> {
845 self.segments.iter().map(|(start, end)| (start, end))
846 }
847}
848
849// Newtype to avoid leaking our internal representation.
850pub struct RangesIter<V>(smallvec::IntoIter<[Interval<V>; 1]>);
851
852impl<V> Iterator for RangesIter<V> {
853 type Item = Interval<V>;
854
855 fn next(&mut self) -> Option<Self::Item> {
856 self.0.next()
857 }
858
859 fn size_hint(&self) -> (usize, Option<usize>) {
860 (self.0.len(), Some(self.0.len()))
861 }
862}
863
864impl<V> ExactSizeIterator for RangesIter<V> {}
865
866impl<V> DoubleEndedIterator for RangesIter<V> {
867 fn next_back(&mut self) -> Option<Self::Item> {
868 self.0.next_back()
869 }
870}
871
872impl<V> IntoIterator for Ranges<V> {
873 type Item = (Bound<V>, Bound<V>);
874 // Newtype to avoid leaking our internal representation.
875 type IntoIter = RangesIter<V>;
876
877 fn into_iter(self) -> Self::IntoIter {
878 RangesIter(self.segments.into_iter())
879 }
880}
881
882impl<V: Ord> FromIterator<(Bound<V>, Bound<V>)> for Ranges<V> {
883 /// Constructor from arbitrary, unsorted and potentially overlapping ranges.
884 ///
885 /// This is equivalent, but faster, to computing the [`Ranges::union`] of the
886 /// [`Ranges::from_range_bounds`] of each segment.
887 fn from_iter<T: IntoIterator<Item = (Bound<V>, Bound<V>)>>(iter: T) -> Self {
888 // We have three constraints we need to fulfil:
889 // 1. The segments are sorted, from lowest to highest (through `Ord`): By sorting.
890 // 2. Each segment contains at least one version (start < end): By skipping invalid
891 // segments.
892 // 3. There is at least one version between two segments: By merging overlapping elements.
893 //
894 // Technically, the implementation has a O(n²) worst case complexity since we're inserting
895 // and removing. This has two motivations: One is that we don't have any performance
896 // critical usages of this method as of this writing, so we have no real world benchmark.
897 // The other is that we get the elements from an iterator, so to avoid moving elements
898 // around we would first need to build a different, sorted collection with extra
899 // allocation(s), before we could build our real segments. --Konsti
900
901 // For this implementation, we choose to only build a single smallvec and insert or remove
902 // in it, instead of e.g. collecting the segments into a sorted datastructure first and then
903 // construction the second smallvec without shifting.
904 let mut segments: SmallVec<[Interval<V>; 1]> = SmallVec::new();
905
906 for segment in iter {
907 if !valid_segment(&segment.start_bound(), &segment.end_bound()) {
908 continue;
909 }
910 // Find where to insert the new segment
911 let insertion_point = segments.partition_point(|elem: &Interval<V>| {
912 cmp_bounds_start(elem.start_bound(), segment.start_bound())
913 .unwrap()
914 .is_lt()
915 });
916 // Is it overlapping with the previous segment?
917 let previous_overlapping = insertion_point > 0
918 && !end_before_start_with_gap(
919 &segments[insertion_point - 1].end_bound(),
920 &segment.start_bound(),
921 );
922
923 // Is it overlapping with the following segment? We'll check if there's more than one
924 // overlap later.
925 let next_overlapping = insertion_point < segments.len()
926 && !end_before_start_with_gap(
927 &segment.end_bound(),
928 &segments[insertion_point].start_bound(),
929 );
930
931 match (previous_overlapping, next_overlapping) {
932 (true, true) => {
933 // previous: |------|
934 // segment: |------|
935 // following: |------|
936 // final: |---------------|
937 //
938 // OR
939 //
940 // previous: |------|
941 // segment: |-----------|
942 // following: |----|
943 // final: |---------------|
944 //
945 // OR
946 //
947 // previous: |------|
948 // segment: |----------------|
949 // following: |----| |------|
950 // final: |------------------------|
951 // We merge all three segments into one, which is effectively removing one of
952 // two previously inserted and changing the bounds on the other.
953
954 // Remove all elements covered by the final element
955 let mut following = segments.remove(insertion_point);
956 while insertion_point < segments.len()
957 && !end_before_start_with_gap(
958 &segment.end_bound(),
959 &segments[insertion_point].start_bound(),
960 )
961 {
962 following = segments.remove(insertion_point);
963 }
964
965 // Set end to max(segment.end, <last overlapping segment>.end)
966 if cmp_bounds_end(segment.end_bound(), following.end_bound())
967 .unwrap()
968 .is_lt()
969 {
970 segments[insertion_point - 1].1 = following.1;
971 } else {
972 segments[insertion_point - 1].1 = segment.1;
973 }
974 }
975 (true, false) => {
976 // previous: |------|
977 // segment: |------|
978 // following: |------|
979 //
980 // OR
981 //
982 // previous: |----------|
983 // segment: |---|
984 // following: |------|
985 //
986 // final: |----------| |------|
987 // We can reuse the existing element by extending it.
988
989 // Set end to max(segment.end, <previous>.end)
990 if cmp_bounds_end(
991 segments[insertion_point - 1].end_bound(),
992 segment.end_bound(),
993 )
994 .unwrap()
995 .is_lt()
996 {
997 segments[insertion_point - 1].1 = segment.1;
998 }
999 }
1000 (false, true) => {
1001 // previous: |------|
1002 // segment: |------|
1003 // following: |------|
1004 // final: |------| |----------|
1005 //
1006 // OR
1007 //
1008 // previous: |------|
1009 // segment: |----------|
1010 // following: |---|
1011 // final: |------| |----------|
1012 //
1013 // OR
1014 //
1015 // previous: |------|
1016 // segment: |------------|
1017 // following: |---| |------|
1018 //
1019 // final: |------| |-----------------|
1020 // We can reuse the existing element by extending it.
1021
1022 // Remove all fully covered segments so the next element is the last one that
1023 // overlaps.
1024 while insertion_point + 1 < segments.len()
1025 && !end_before_start_with_gap(
1026 &segment.end_bound(),
1027 &segments[insertion_point + 1].start_bound(),
1028 )
1029 {
1030 // We know that the one after also overlaps, so we can drop the current
1031 // following.
1032 segments.remove(insertion_point);
1033 }
1034
1035 // Set end to max(segment.end, <last overlapping segment>.end)
1036 if cmp_bounds_end(segments[insertion_point].end_bound(), segment.end_bound())
1037 .unwrap()
1038 .is_lt()
1039 {
1040 segments[insertion_point].1 = segment.1;
1041 }
1042 segments[insertion_point].0 = segment.0;
1043 }
1044 (false, false) => {
1045 // previous: |------|
1046 // segment: |------|
1047 // following: |------|
1048 //
1049 // final: |------| |------| |------|
1050
1051 // This line is O(n), which makes the algorithm O(n²), but it should be good
1052 // enough for now.
1053 segments.insert(insertion_point, segment);
1054 }
1055 }
1056 }
1057
1058 Self { segments }.check_invariants()
1059 }
1060}
1061
1062// REPORT ######################################################################
1063
1064impl<V: Display + Eq> Display for Ranges<V> {
1065 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1066 if self.segments.is_empty() {
1067 write!(f, "∅")?;
1068 } else {
1069 for (idx, segment) in self.segments.iter().enumerate() {
1070 if idx > 0 {
1071 write!(f, " | ")?;
1072 }
1073 match segment {
1074 (Unbounded, Unbounded) => write!(f, "*")?,
1075 (Unbounded, Included(v)) => write!(f, "<={v}")?,
1076 (Unbounded, Excluded(v)) => write!(f, "<{v}")?,
1077 (Included(v), Unbounded) => write!(f, ">={v}")?,
1078 (Included(v), Included(b)) => {
1079 if v == b {
1080 write!(f, "{v}")?
1081 } else {
1082 write!(f, ">={v}, <={b}")?
1083 }
1084 }
1085 (Included(v), Excluded(b)) => write!(f, ">={v}, <{b}")?,
1086 (Excluded(v), Unbounded) => write!(f, ">{v}")?,
1087 (Excluded(v), Included(b)) => write!(f, ">{v}, <={b}")?,
1088 (Excluded(v), Excluded(b)) => write!(f, ">{v}, <{b}")?,
1089 };
1090 }
1091 }
1092 Ok(())
1093 }
1094}
1095
1096// SERIALIZATION ###############################################################
1097
1098#[cfg(feature = "serde")]
1099impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Ranges<V> {
1100 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1101 // This enables conversion from the "old" discrete implementation of `Ranges` to the new
1102 // bounded one.
1103 //
1104 // Serialization is always performed in the new format.
1105 #[derive(serde::Deserialize)]
1106 #[serde(untagged)]
1107 enum EitherInterval<V> {
1108 B(Bound<V>, Bound<V>),
1109 D(V, Option<V>),
1110 }
1111
1112 let bounds: SmallVec<[EitherInterval<V>; 2]> =
1113 serde::Deserialize::deserialize(deserializer)?;
1114
1115 let mut segments = SmallVec::new();
1116 for i in bounds {
1117 match i {
1118 EitherInterval::B(l, r) => segments.push((l, r)),
1119 EitherInterval::D(l, Some(r)) => segments.push((Included(l), Excluded(r))),
1120 EitherInterval::D(l, None) => segments.push((Included(l), Unbounded)),
1121 }
1122 }
1123
1124 Ok(Ranges { segments })
1125 }
1126}
1127
1128/// Generate version sets from a random vector of deltas between randomly inclusive or exclusive
1129/// bounds.
1130#[cfg(any(feature = "proptest", test))]
1131pub fn proptest_strategy() -> impl Strategy<Value = Ranges<u32>> {
1132 (
1133 any::<bool>(),
1134 prop::collection::vec(any::<(u32, bool)>(), 0..10),
1135 )
1136 .prop_map(|(start_unbounded, deltas)| {
1137 let mut start = if start_unbounded {
1138 Some(Unbounded)
1139 } else {
1140 None
1141 };
1142 let mut largest: u32 = 0;
1143 let mut last_bound_was_inclusive = false;
1144 let mut segments = SmallVec::new();
1145 for (delta, inclusive) in deltas {
1146 // Add the offset to the current bound
1147 largest = match largest.checked_add(delta) {
1148 Some(s) => s,
1149 None => {
1150 // Skip this offset, if it would result in a too large bound.
1151 continue;
1152 }
1153 };
1154
1155 let current_bound = if inclusive {
1156 Included(largest)
1157 } else {
1158 Excluded(largest)
1159 };
1160
1161 // If we already have a start bound, the next offset defines the complete range.
1162 // If we don't have a start bound, we have to generate one.
1163 if let Some(start_bound) = start.take() {
1164 // If the delta from the start bound is 0, the only authorized configuration is
1165 // Included(x), Included(x)
1166 if delta == 0 && !(matches!(start_bound, Included(_)) && inclusive) {
1167 start = Some(start_bound);
1168 continue;
1169 }
1170 last_bound_was_inclusive = inclusive;
1171 segments.push((start_bound, current_bound));
1172 } else {
1173 // If the delta from the end bound of the last range is 0 and
1174 // any of the last ending or current starting bound is inclusive,
1175 // we skip the delta because they basically overlap.
1176 if delta == 0 && (last_bound_was_inclusive || inclusive) {
1177 continue;
1178 }
1179 start = Some(current_bound);
1180 }
1181 }
1182
1183 // If we still have a start bound, but didn't have enough deltas to complete another
1184 // segment, we add an unbounded upperbound.
1185 if let Some(start_bound) = start {
1186 segments.push((start_bound, Unbounded));
1187 }
1188
1189 Ranges { segments }.check_invariants()
1190 })
1191}
1192
1193#[cfg(test)]
1194pub mod tests {
1195 use proptest::prelude::*;
1196
1197 use super::*;
1198
1199 fn version_strat() -> impl Strategy<Value = u32> {
1200 any::<u32>()
1201 }
1202
1203 proptest! {
1204
1205 // Testing serde ----------------------------------
1206
1207 #[cfg(feature = "serde")]
1208 #[test]
1209 fn serde_round_trip(range in proptest_strategy()) {
1210 let s = ron::ser::to_string(&range).unwrap();
1211 let r = ron::de::from_str(&s).unwrap();
1212 assert_eq!(range, r);
1213 }
1214
1215 // Testing negate ----------------------------------
1216
1217 #[test]
1218 fn negate_is_different(range in proptest_strategy()) {
1219 assert_ne!(range.complement(), range);
1220 }
1221
1222 #[test]
1223 fn double_negate_is_identity(range in proptest_strategy()) {
1224 assert_eq!(range.complement().complement(), range);
1225 }
1226
1227 #[test]
1228 fn negate_contains_opposite(range in proptest_strategy(), version in version_strat()) {
1229 assert_ne!(range.contains(&version), range.complement().contains(&version));
1230 }
1231
1232 // Testing intersection ----------------------------
1233
1234 #[test]
1235 fn intersection_is_symmetric(r1 in proptest_strategy(), r2 in proptest_strategy()) {
1236 assert_eq!(r1.intersection(&r2), r2.intersection(&r1));
1237 }
1238
1239 #[test]
1240 fn intersection_with_any_is_identity(range in proptest_strategy()) {
1241 assert_eq!(Ranges::full().intersection(&range), range);
1242 }
1243
1244 #[test]
1245 fn intersection_with_none_is_none(range in proptest_strategy()) {
1246 assert_eq!(Ranges::empty().intersection(&range), Ranges::empty());
1247 }
1248
1249 #[test]
1250 fn intersection_is_idempotent(r1 in proptest_strategy(), r2 in proptest_strategy()) {
1251 assert_eq!(r1.intersection(&r2).intersection(&r2), r1.intersection(&r2));
1252 }
1253
1254 #[test]
1255 fn intersection_is_associative(r1 in proptest_strategy(), r2 in proptest_strategy(), r3 in proptest_strategy()) {
1256 assert_eq!(r1.intersection(&r2).intersection(&r3), r1.intersection(&r2.intersection(&r3)));
1257 }
1258
1259 #[test]
1260 fn intesection_of_complements_is_none(range in proptest_strategy()) {
1261 assert_eq!(range.complement().intersection(&range), Ranges::empty());
1262 }
1263
1264 #[test]
1265 fn intesection_contains_both(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) {
1266 assert_eq!(r1.intersection(&r2).contains(&version), r1.contains(&version) && r2.contains(&version));
1267 }
1268
1269 // Testing union -----------------------------------
1270
1271 #[test]
1272 fn union_of_complements_is_any(range in proptest_strategy()) {
1273 assert_eq!(range.complement().union(&range), Ranges::full());
1274 }
1275
1276 #[test]
1277 fn union_contains_either(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) {
1278 assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version));
1279 }
1280
1281 #[test]
1282 fn is_disjoint_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) {
1283 let disjoint_def = r1.intersection(&r2) == Ranges::empty();
1284 assert_eq!(r1.is_disjoint(&r2), disjoint_def);
1285 }
1286
1287 #[test]
1288 fn subset_of_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) {
1289 let disjoint_def = r1.intersection(&r2) == r1;
1290 assert_eq!(r1.subset_of(&r2), disjoint_def);
1291 }
1292
1293 #[test]
1294 fn union_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) {
1295 let union_def = r1
1296 .complement()
1297 .intersection(&r2.complement())
1298 .complement()
1299 .check_invariants();
1300 assert_eq!(r1.union(&r2), union_def);
1301 }
1302
1303 // Testing contains --------------------------------
1304
1305 #[test]
1306 fn always_contains_exact(version in version_strat()) {
1307 assert!(Ranges::singleton(version).contains(&version));
1308 }
1309
1310 #[test]
1311 fn contains_negation(range in proptest_strategy(), version in version_strat()) {
1312 assert_ne!(range.contains(&version), range.complement().contains(&version));
1313 }
1314
1315 #[test]
1316 fn contains_intersection(range in proptest_strategy(), version in version_strat()) {
1317 assert_eq!(range.contains(&version), range.intersection(&Ranges::singleton(version)) != Ranges::empty());
1318 }
1319
1320 #[test]
1321 fn contains_bounding_range(range in proptest_strategy(), version in version_strat()) {
1322 if range.contains(&version) {
1323 assert!(range.bounding_range().map(|b| b.contains(&version)).unwrap_or(false));
1324 }
1325 }
1326
1327 #[test]
1328 fn from_range_bounds(range in any::<(Bound<u32>, Bound<u32>)>(), version in version_strat()) {
1329 let rv: Ranges<_> = Ranges::from_range_bounds(range);
1330 assert_eq!(range.contains(&version), rv.contains(&version));
1331 }
1332
1333 #[test]
1334 fn from_range_bounds_round_trip(range in any::<(Bound<u32>, Bound<u32>)>()) {
1335 let rv: Ranges<u32> = Ranges::from_range_bounds(range);
1336 let rv2: Ranges<u32> = rv.bounding_range().map(Ranges::from_range_bounds::<_, u32>).unwrap_or_else(Ranges::empty);
1337 assert_eq!(rv, rv2);
1338 }
1339
1340 #[test]
1341 fn contains(range in proptest_strategy(), versions in proptest::collection::vec(version_strat(), ..30)) {
1342 for v in versions {
1343 assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v)));
1344 }
1345 }
1346
1347 #[test]
1348 fn contains_many(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) {
1349 versions.sort();
1350 assert_eq!(versions.len(), range.contains_many(versions.iter()).count());
1351 for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) {
1352 assert_eq!(range.contains(a), b);
1353 }
1354 }
1355
1356 #[test]
1357 fn simplify(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) {
1358 versions.sort();
1359 let simp = range.simplify(versions.iter());
1360
1361 for v in versions {
1362 assert_eq!(range.contains(&v), simp.contains(&v));
1363 }
1364 assert!(simp.segments.len() <= range.segments.len())
1365 }
1366
1367 #[test]
1368 fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound<u32>, Bound<u32>)>(), ..30)) {
1369 let mut expected = Ranges::empty();
1370 for segment in &segments {
1371 expected = expected.union(&Ranges::from_range_bounds(*segment));
1372 }
1373 let actual = Ranges::from_iter(segments.clone());
1374 assert_eq!(expected, actual, "{segments:?}");
1375 }
1376 }
1377
1378 #[test]
1379 fn contains_many_can_take_owned() {
1380 let range: Ranges<u8> = Ranges::singleton(1);
1381 let versions = vec![1, 2, 3];
1382 // Check that iter can be a Cow
1383 assert_eq!(
1384 range.contains_many(versions.iter()).count(),
1385 range
1386 .contains_many(versions.iter().map(std::borrow::Cow::Borrowed))
1387 .count()
1388 );
1389 // Check that iter can be a V
1390 assert_eq!(
1391 range.contains_many(versions.iter()).count(),
1392 range.contains_many(versions.into_iter()).count()
1393 );
1394 }
1395
1396 #[test]
1397 fn simplify_can_take_owned() {
1398 let range: Ranges<u8> = Ranges::singleton(1);
1399 let versions = vec![1, 2, 3];
1400 // Check that iter can be a Cow
1401 assert_eq!(
1402 range.simplify(versions.iter()),
1403 range.simplify(versions.iter().map(std::borrow::Cow::Borrowed))
1404 );
1405 // Check that iter can be a V
1406 assert_eq!(
1407 range.simplify(versions.iter()),
1408 range.simplify(versions.into_iter())
1409 );
1410 }
1411
1412 #[test]
1413 fn version_ord() {
1414 let versions: &[Ranges<u32>] = &[
1415 Ranges::strictly_lower_than(1u32),
1416 Ranges::lower_than(1u32),
1417 Ranges::singleton(1u32),
1418 Ranges::between(1u32, 3u32),
1419 Ranges::higher_than(1u32),
1420 Ranges::strictly_higher_than(1u32),
1421 Ranges::singleton(2u32),
1422 Ranges::singleton(2u32).union(&Ranges::singleton(3u32)),
1423 Ranges::singleton(2u32)
1424 .union(&Ranges::singleton(3u32))
1425 .union(&Ranges::singleton(4u32)),
1426 Ranges::singleton(2u32).union(&Ranges::singleton(4u32)),
1427 Ranges::singleton(3u32),
1428 ];
1429
1430 let mut versions_sorted = versions.to_vec();
1431 versions_sorted.sort();
1432 assert_eq!(versions_sorted, versions);
1433
1434 // Check that the sorting isn't just stable because we're returning equal.
1435 let mut version_reverse_sorted = versions.to_vec();
1436 version_reverse_sorted.reverse();
1437 version_reverse_sorted.sort();
1438 assert_eq!(version_reverse_sorted, versions);
1439 }
1440}