1 // SPDX-License-Identifier: MPL-2.0
2
3 //! Ranges are constraints defining sets of versions.
4 //!
5 //! Concretely, those constraints correspond to any set of versions
6 //! representable as the concatenation, union, and complement
7 //! of the ranges building blocks.
8 //!
9 //! Those building blocks are:
10 //! - [none()](Range::none): the empty set
11 //! - [any()](Range::any): the set of all possible versions
12 //! - [exact(v)](Range::exact): the set containing only the version v
13 //! - [higher_than(v)](Range::higher_than): the set defined by `v <= versions`
14 //! - [strictly_lower_than(v)](Range::strictly_lower_than): the set defined by `versions < v`
15 //! - [between(v1, v2)](Range::between): the set defined by `v1 <= versions < v2`
16
17 use std::cmp::Ordering;
18 use std::fmt;
19
20 use crate::internal::small_vec::SmallVec;
21 use crate::version::Version;
22
23 /// A Range is a set of versions.
24 #[derive(Debug, Clone, Eq, PartialEq)]
25 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26 #[cfg_attr(feature = "serde", serde(transparent))]
27 pub struct Range<V: Version> {
28 segments: SmallVec<Interval<V>>,
29 }
30
31 type Interval<V> = (V, Option<V>);
32
33 // Range building blocks.
34 impl<V: Version> Range<V> {
35 /// Empty set of versions.
none() -> Self36 pub fn none() -> Self {
37 Self {
38 segments: SmallVec::empty(),
39 }
40 }
41
42 /// Set of all possible versions.
any() -> Self43 pub fn any() -> Self {
44 Self::higher_than(V::lowest())
45 }
46
47 /// Set containing exactly one version.
exact(v: impl Into<V>) -> Self48 pub fn exact(v: impl Into<V>) -> Self {
49 let v = v.into();
50 Self {
51 segments: SmallVec::one((v.clone(), Some(v.bump()))),
52 }
53 }
54
55 /// Set of all versions higher or equal to some version.
higher_than(v: impl Into<V>) -> Self56 pub fn higher_than(v: impl Into<V>) -> Self {
57 Self {
58 segments: SmallVec::one((v.into(), None)),
59 }
60 }
61
62 /// Set of all versions strictly lower than some version.
strictly_lower_than(v: impl Into<V>) -> Self63 pub fn strictly_lower_than(v: impl Into<V>) -> Self {
64 let v = v.into();
65 if v == V::lowest() {
66 Self::none()
67 } else {
68 Self {
69 segments: SmallVec::one((V::lowest(), Some(v))),
70 }
71 }
72 }
73
74 /// Set of all versions comprised between two given versions.
75 /// The lower bound is included and the higher bound excluded.
76 /// `v1 <= v < v2`.
between(v1: impl Into<V>, v2: impl Into<V>) -> Self77 pub fn between(v1: impl Into<V>, v2: impl Into<V>) -> Self {
78 let v1 = v1.into();
79 let v2 = v2.into();
80 if v1 < v2 {
81 Self {
82 segments: SmallVec::one((v1, Some(v2))),
83 }
84 } else {
85 Self::none()
86 }
87 }
88 }
89
90 // Set operations.
91 impl<V: Version> Range<V> {
92 // Negate ##################################################################
93
94 /// Compute the complement set of versions.
negate(&self) -> Self95 pub fn negate(&self) -> Self {
96 match self.segments.first() {
97 None => Self::any(), // Complement of ∅ is *
98
99 // First high bound is +∞
100 Some((v, None)) => {
101 // Complement of * is ∅
102 if v == &V::lowest() {
103 Self::none()
104 // Complement of "v <= _" is "_ < v"
105 } else {
106 Self::strictly_lower_than(v.clone())
107 }
108 }
109
110 // First high bound is not +∞
111 Some((v1, Some(v2))) => {
112 if v1 == &V::lowest() {
113 Self::negate_segments(v2.clone(), &self.segments[1..])
114 } else {
115 Self::negate_segments(V::lowest(), &self.segments)
116 }
117 }
118 }
119 }
120
121 /// Helper function performing the negation of intervals in segments.
122 /// For example:
123 /// [ (v1, None) ] => [ (start, Some(v1)) ]
124 /// [ (v1, Some(v2)) ] => [ (start, Some(v1)), (v2, None) ]
negate_segments(start: V, segments: &[Interval<V>]) -> Range<V>125 fn negate_segments(start: V, segments: &[Interval<V>]) -> Range<V> {
126 let mut complement_segments = SmallVec::empty();
127 let mut start = Some(start);
128 for (v1, maybe_v2) in segments {
129 // start.unwrap() is fine because `segments` is not exposed,
130 // and our usage guaranties that only the last segment may contain a None.
131 complement_segments.push((start.unwrap(), Some(v1.to_owned())));
132 start = maybe_v2.to_owned();
133 }
134 if let Some(last) = start {
135 complement_segments.push((last, None));
136 }
137
138 Self {
139 segments: complement_segments,
140 }
141 }
142
143 // Union and intersection ##################################################
144
145 /// Compute the union of two sets of versions.
union(&self, other: &Self) -> Self146 pub fn union(&self, other: &Self) -> Self {
147 self.negate().intersection(&other.negate()).negate()
148 }
149
150 /// Compute the intersection of two sets of versions.
intersection(&self, other: &Self) -> Self151 pub fn intersection(&self, other: &Self) -> Self {
152 let mut segments = SmallVec::empty();
153 let mut left_iter = self.segments.iter();
154 let mut right_iter = other.segments.iter();
155 let mut left = left_iter.next();
156 let mut right = right_iter.next();
157 loop {
158 match (left, right) {
159 // Both left and right still contain a finite interval:
160 (Some((l1, Some(l2))), Some((r1, Some(r2)))) => {
161 if l2 <= r1 {
162 // Intervals are disjoint, progress on the left.
163 left = left_iter.next();
164 } else if r2 <= l1 {
165 // Intervals are disjoint, progress on the right.
166 right = right_iter.next();
167 } else {
168 // Intervals are not disjoint.
169 let start = l1.max(r1).to_owned();
170 if l2 < r2 {
171 segments.push((start, Some(l2.to_owned())));
172 left = left_iter.next();
173 } else {
174 segments.push((start, Some(r2.to_owned())));
175 right = right_iter.next();
176 }
177 }
178 }
179
180 // Right contains an infinite interval:
181 (Some((l1, Some(l2))), Some((r1, None))) => match l2.cmp(r1) {
182 Ordering::Less => {
183 left = left_iter.next();
184 }
185 Ordering::Equal => {
186 for l in left_iter.cloned() {
187 segments.push(l)
188 }
189 break;
190 }
191 Ordering::Greater => {
192 let start = l1.max(r1).to_owned();
193 segments.push((start, Some(l2.to_owned())));
194 for l in left_iter.cloned() {
195 segments.push(l)
196 }
197 break;
198 }
199 },
200
201 // Left contains an infinite interval:
202 (Some((l1, None)), Some((r1, Some(r2)))) => match r2.cmp(l1) {
203 Ordering::Less => {
204 right = right_iter.next();
205 }
206 Ordering::Equal => {
207 for r in right_iter.cloned() {
208 segments.push(r)
209 }
210 break;
211 }
212 Ordering::Greater => {
213 let start = l1.max(r1).to_owned();
214 segments.push((start, Some(r2.to_owned())));
215 for r in right_iter.cloned() {
216 segments.push(r)
217 }
218 break;
219 }
220 },
221
222 // Both sides contain an infinite interval:
223 (Some((l1, None)), Some((r1, None))) => {
224 let start = l1.max(r1).to_owned();
225 segments.push((start, None));
226 break;
227 }
228
229 // Left or right has ended.
230 _ => {
231 break;
232 }
233 }
234 }
235
236 Self { segments }
237 }
238 }
239
240 // Other useful functions.
241 impl<V: Version> Range<V> {
242 /// Check if a range contains a given version.
contains(&self, version: &V) -> bool243 pub fn contains(&self, version: &V) -> bool {
244 for (v1, maybe_v2) in &self.segments {
245 match maybe_v2 {
246 None => return v1 <= version,
247 Some(v2) => {
248 if version < v1 {
249 return false;
250 } else if version < v2 {
251 return true;
252 }
253 }
254 }
255 }
256 false
257 }
258
259 /// Return the lowest version in the range (if there is one).
lowest_version(&self) -> Option<V>260 pub fn lowest_version(&self) -> Option<V> {
261 self.segments.first().map(|(start, _)| start).cloned()
262 }
263 }
264
265 // REPORT ######################################################################
266
267 impl<V: Version> fmt::Display for Range<V> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result268 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269 match self.segments.as_slice() {
270 [] => write!(f, "∅"),
271 [(start, None)] if start == &V::lowest() => write!(f, "∗"),
272 [(start, None)] => write!(f, "{} <= v", start),
273 [(start, Some(end))] if end == &start.bump() => write!(f, "{}", start),
274 [(start, Some(end))] if start == &V::lowest() => write!(f, "v < {}", end),
275 [(start, Some(end))] => write!(f, "{} <= v < {}", start, end),
276 more_than_one_interval => {
277 let string_intervals: Vec<_> = more_than_one_interval
278 .iter()
279 .map(interval_to_string)
280 .collect();
281 write!(f, "{}", string_intervals.join(" "))
282 }
283 }
284 }
285 }
286
interval_to_string<V: Version>((start, maybe_end): &Interval<V>) -> String287 fn interval_to_string<V: Version>((start, maybe_end): &Interval<V>) -> String {
288 match maybe_end {
289 Some(end) => format!("[ {}, {} [", start, end),
290 None => format!("[ {}, ∞ [", start),
291 }
292 }
293
294 // TESTS #######################################################################
295
296 #[cfg(test)]
297 pub mod tests {
298 use proptest::prelude::*;
299
300 use crate::version::NumberVersion;
301
302 use super::*;
303
strategy() -> impl Strategy<Value = Range<NumberVersion>>304 pub fn strategy() -> impl Strategy<Value = Range<NumberVersion>> {
305 prop::collection::vec(any::<u32>(), 0..10).prop_map(|mut vec| {
306 vec.sort_unstable();
307 vec.dedup();
308 let mut pair_iter = vec.chunks_exact(2);
309 let mut segments = SmallVec::empty();
310 while let Some([v1, v2]) = pair_iter.next() {
311 segments.push((NumberVersion(*v1), Some(NumberVersion(*v2))));
312 }
313 if let [v] = pair_iter.remainder() {
314 segments.push((NumberVersion(*v), None));
315 }
316 Range { segments }
317 })
318 }
319
version_strat() -> impl Strategy<Value = NumberVersion>320 fn version_strat() -> impl Strategy<Value = NumberVersion> {
321 any::<u32>().prop_map(NumberVersion)
322 }
323
324 proptest! {
325
326 // Testing negate ----------------------------------
327
328 #[test]
329 fn negate_is_different(range in strategy()) {
330 assert_ne!(range.negate(), range);
331 }
332
333 #[test]
334 fn double_negate_is_identity(range in strategy()) {
335 assert_eq!(range.negate().negate(), range);
336 }
337
338 #[test]
339 fn negate_contains_opposite(range in strategy(), version in version_strat()) {
340 assert_ne!(range.contains(&version), range.negate().contains(&version));
341 }
342
343 // Testing intersection ----------------------------
344
345 #[test]
346 fn intersection_is_symmetric(r1 in strategy(), r2 in strategy()) {
347 assert_eq!(r1.intersection(&r2), r2.intersection(&r1));
348 }
349
350 #[test]
351 fn intersection_with_any_is_identity(range in strategy()) {
352 assert_eq!(Range::any().intersection(&range), range);
353 }
354
355 #[test]
356 fn intersection_with_none_is_none(range in strategy()) {
357 assert_eq!(Range::none().intersection(&range), Range::none());
358 }
359
360 #[test]
361 fn intersection_is_idempotent(r1 in strategy(), r2 in strategy()) {
362 assert_eq!(r1.intersection(&r2).intersection(&r2), r1.intersection(&r2));
363 }
364
365 #[test]
366 fn intersection_is_associative(r1 in strategy(), r2 in strategy(), r3 in strategy()) {
367 assert_eq!(r1.intersection(&r2).intersection(&r3), r1.intersection(&r2.intersection(&r3)));
368 }
369
370 #[test]
371 fn intesection_of_complements_is_none(range in strategy()) {
372 assert_eq!(range.negate().intersection(&range), Range::none());
373 }
374
375 #[test]
376 fn intesection_contains_both(r1 in strategy(), r2 in strategy(), version in version_strat()) {
377 assert_eq!(r1.intersection(&r2).contains(&version), r1.contains(&version) && r2.contains(&version));
378 }
379
380 // Testing union -----------------------------------
381
382 #[test]
383 fn union_of_complements_is_any(range in strategy()) {
384 assert_eq!(range.negate().union(&range), Range::any());
385 }
386
387 #[test]
388 fn union_contains_either(r1 in strategy(), r2 in strategy(), version in version_strat()) {
389 assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version));
390 }
391
392 // Testing contains --------------------------------
393
394 #[test]
395 fn always_contains_exact(version in version_strat()) {
396 assert!(Range::exact(version).contains(&version));
397 }
398
399 #[test]
400 fn contains_negation(range in strategy(), version in version_strat()) {
401 assert_ne!(range.contains(&version), range.negate().contains(&version));
402 }
403
404 #[test]
405 fn contains_intersection(range in strategy(), version in version_strat()) {
406 assert_eq!(range.contains(&version), range.intersection(&Range::exact(version)) != Range::none());
407 }
408 }
409 }
410