Skip to content

Commit 5cd1dc9

Browse files
committed
feat: add a simplify for error messages
1 parent acfbe99 commit 5cd1dc9

File tree

1 file changed

+145
-12
lines changed

1 file changed

+145
-12
lines changed

src/range.rs

+145-12
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
//! If we do not see practical bugs, or we get a formal proof that the code cannot lead to error states, then we may remove this warning.
5252
5353
use crate::{internal::small_vec::SmallVec, version_set::VersionSet};
54+
use std::cmp::Ordering;
5455
use std::ops::RangeBounds;
5556
use std::{
5657
fmt::{Debug, Display, Formatter},
@@ -202,23 +203,46 @@ impl<V: Ord> Range<V> {
202203
/// Returns true if the this Range contains the specified value.
203204
pub fn contains(&self, v: &V) -> bool {
204205
for segment in self.segments.iter() {
205-
if match segment {
206-
(Unbounded, Unbounded) => true,
207-
(Unbounded, Included(end)) => v <= end,
208-
(Unbounded, Excluded(end)) => v < end,
209-
(Included(start), Unbounded) => v >= start,
210-
(Included(start), Included(end)) => v >= start && v <= end,
211-
(Included(start), Excluded(end)) => v >= start && v < end,
212-
(Excluded(start), Unbounded) => v > start,
213-
(Excluded(start), Included(end)) => v > start && v <= end,
214-
(Excluded(start), Excluded(end)) => v > start && v < end,
215-
} {
216-
return true;
206+
match within_bounds(v, segment) {
207+
Ordering::Less => return false,
208+
Ordering::Equal => return true,
209+
Ordering::Greater => (),
217210
}
218211
}
219212
false
220213
}
221214

215+
/// Returns true if the this Range contains the specified values.
216+
///
217+
/// The `versions` iterator must be sorted.
218+
/// Functionally equivalent to `versions.map(|v| self.contains(v))`.
219+
/// Except it runs in `O(size_of_range + len_of_versions)` not `O(size_of_range * len_of_versions)`
220+
pub fn contains_many<'s, I>(&'s self, versions: I) -> impl Iterator<Item = bool> + 's
221+
where
222+
I: Iterator<Item = &'s V> + 's,
223+
V: 's,
224+
{
225+
self.locate_versions(versions).map(|m| m.is_some())
226+
}
227+
228+
/// Return the segment index in the range for each version in the range, None otherwise
229+
fn locate_versions<'s, I>(&'s self, versions: I) -> impl Iterator<Item = Option<usize>> + 's
230+
where
231+
I: Iterator<Item = &'s V> + 's,
232+
V: 's,
233+
{
234+
versions.scan(0, |i, v| {
235+
while let Some(segment) = self.segments.get(*i) {
236+
match within_bounds(v, segment) {
237+
Ordering::Less => return Some(None),
238+
Ordering::Equal => return Some(Some(*i)),
239+
Ordering::Greater => *i += 1,
240+
}
241+
}
242+
Some(None)
243+
})
244+
}
245+
222246
/// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`.
223247
pub fn from_range_bounds<R, IV>(bounds: R) -> Self
224248
where
@@ -264,6 +288,26 @@ impl<V: Ord> Range<V> {
264288
}
265289
}
266290

291+
fn within_bounds<V: PartialOrd>(v: &V, segment: &Interval<V>) -> Ordering {
292+
let below_lower_bound = match segment {
293+
(Excluded(start), _) => v <= start,
294+
(Included(start), _) => v < start,
295+
(Unbounded, _) => false,
296+
};
297+
if below_lower_bound {
298+
return Ordering::Less;
299+
}
300+
let below_upper_bound = match segment {
301+
(_, Unbounded) => true,
302+
(_, Included(end)) => v <= end,
303+
(_, Excluded(end)) => v < end,
304+
};
305+
if below_upper_bound {
306+
return Ordering::Equal;
307+
}
308+
Ordering::Greater
309+
}
310+
267311
fn valid_segment<T: PartialOrd>(start: &Bound<T>, end: &Bound<T>) -> bool {
268312
match (start, end) {
269313
(Included(s), Included(e)) => s <= e,
@@ -274,6 +318,33 @@ fn valid_segment<T: PartialOrd>(start: &Bound<T>, end: &Bound<T>) -> bool {
274318
}
275319
}
276320

321+
/// group adjacent versions locations
322+
/// [None, 3, 6, 7, None] -> [(3, 7)]
323+
/// [3, 6, 7, None] -> [(None, 7)]
324+
/// [3, 6, 7] -> [(None, None)]
325+
/// [None, 1, 4, 7, None, None, None, 8, None, 9] -> [(1, 7), (8, 8), (9, None)]
326+
fn group_adjacent_locations(
327+
mut locations: impl Iterator<Item = Option<usize>>,
328+
) -> impl Iterator<Item = (Option<usize>, Option<usize>)> {
329+
// If the first version matched, then the lower bound of that segment is not needed
330+
let mut seg = locations.next().flatten().map(|ver| (None, Some(ver)));
331+
std::iter::from_fn(move || {
332+
for ver in locations.by_ref() {
333+
if let Some(ver) = ver {
334+
// As long as were still matching versions, we keep merging into the currently matching segment
335+
seg = Some((seg.map_or(Some(ver), |(s, _)| s), Some(ver)));
336+
} else {
337+
// If we have found a version that doesn't match, then right the merge segment and prepare for a new one.
338+
if seg.is_some() {
339+
return seg.take();
340+
}
341+
}
342+
}
343+
// If the last version matched, then write out the merged segment but the upper bound is not needed.
344+
seg.take().map(|(s, _)| (s, None))
345+
})
346+
}
347+
277348
impl<V: Ord + Clone> Range<V> {
278349
/// Computes the union of this `Range` and another.
279350
pub fn union(&self, other: &Self) -> Self {
@@ -321,6 +392,41 @@ impl<V: Ord + Clone> Range<V> {
321392

322393
Self { segments }.check_invariants()
323394
}
395+
396+
/// Returns a simpler Range that contains the same versions
397+
///
398+
/// For every one of the Versions provided in versions the existing range and
399+
/// the simplified range will agree on whether it is contained.
400+
/// The simplified version may include or exclude versions that are not in versions as the implementation wishes.
401+
/// For example:
402+
/// - If all the versions are contained in the original than the range will be simplified to `full`.
403+
/// - If none of the versions are contained in the original than the range will be simplified to `empty`.
404+
///
405+
/// If versions are not sorted the correctness of this function is not guaranteed.
406+
pub fn simplify<'s, I>(&'s self, versions: I) -> Self
407+
where
408+
I: Iterator<Item = &'s V> + 's,
409+
V: 's,
410+
{
411+
let version_locations = self.locate_versions(versions);
412+
let kept_segments = group_adjacent_locations(version_locations);
413+
self.keep_segments(kept_segments)
414+
}
415+
416+
/// simplify range with segments at given location bounds.
417+
fn keep_segments(
418+
&self,
419+
kept_segments: impl Iterator<Item = (Option<usize>, Option<usize>)>,
420+
) -> Range<V> {
421+
let mut segments = SmallVec::Empty;
422+
for (s, e) in kept_segments {
423+
segments.push((
424+
s.map_or(Unbounded, |s| self.segments[s].0.clone()),
425+
e.map_or(Unbounded, |e| self.segments[e].1.clone()),
426+
));
427+
}
428+
Self { segments }.check_invariants()
429+
}
324430
}
325431

326432
impl<T: Debug + Display + Clone + Eq + Ord> VersionSet for Range<T> {
@@ -600,5 +706,32 @@ pub mod tests {
600706
let rv2: Range<u32> = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty);
601707
assert_eq!(rv, rv2);
602708
}
709+
710+
#[test]
711+
fn contains(range in strategy(), versions in proptest::collection::vec(version_strat(), ..30)) {
712+
for v in versions {
713+
assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v)));
714+
}
715+
}
716+
717+
#[test]
718+
fn contains_many(range in strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) {
719+
versions.sort();
720+
assert_eq!(versions.len(), range.contains_many(versions.iter()).count());
721+
for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) {
722+
assert_eq!(range.contains(a), b);
723+
}
724+
}
725+
726+
#[test]
727+
fn simplify(range in strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) {
728+
versions.sort();
729+
let simp = range.simplify(versions.iter());
730+
731+
for v in versions {
732+
assert_eq!(range.contains(&v), simp.contains(&v));
733+
}
734+
assert!(simp.segments.len() <= range.segments.len())
735+
}
603736
}
604737
}

0 commit comments

Comments
 (0)