Skip to content

Commit 3a8e5bb

Browse files
committed
feat: add a simplify for error messages
1 parent acfbe99 commit 3a8e5bb

File tree

1 file changed

+151
-9
lines changed

1 file changed

+151
-9
lines changed

src/range.rs

Lines changed: 151 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,22 +203,55 @@ impl<V: Ord> Range<V> {
203203
pub fn contains(&self, v: &V) -> bool {
204204
for segment in self.segments.iter() {
205205
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,
206+
(Excluded(start), _) => v <= start,
207+
(Included(start), _) => v < start,
208+
(Unbounded, _) => false,
209+
} {
210+
return false;
211+
}
212+
if match segment {
213+
(_, Unbounded) => true,
214+
(_, Included(end)) => v <= end,
215+
(_, Excluded(end)) => v < end,
215216
} {
216217
return true;
217218
}
218219
}
219220
false
220221
}
221222

223+
/// Returns true if the this Range contains the specified values.
224+
///
225+
/// The `versions` iterator must be sorted.
226+
/// Functionally equivalent to `versions.map(|v| self.contains(v))`.
227+
/// Except it runs in `O(size_of_range + len_of_versions)` not `O(size_of_range * len_of_versions)`
228+
pub fn contains_many<'s, I>(&'s self, versions: I) -> impl Iterator<Item = bool> + 's
229+
where
230+
I: Iterator<Item = &'s V> + 's,
231+
V: 's,
232+
{
233+
versions.scan(0, |i, v| {
234+
while let Some(segment) = self.segments.get(*i) {
235+
if match segment {
236+
(Excluded(start), _) => v <= start,
237+
(Included(start), _) => v < start,
238+
(Unbounded, _) => false,
239+
} {
240+
return Some(false);
241+
}
242+
if match segment {
243+
(_, Unbounded) => true,
244+
(_, Included(end)) => v <= end,
245+
(_, Excluded(end)) => v < end,
246+
} {
247+
return Some(true);
248+
}
249+
*i += 1;
250+
}
251+
Some(false)
252+
})
253+
}
254+
222255
/// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`.
223256
pub fn from_range_bounds<R, IV>(bounds: R) -> Self
224257
where
@@ -321,6 +354,71 @@ impl<V: Ord + Clone> Range<V> {
321354

322355
Self { segments }.check_invariants()
323356
}
357+
358+
/// Returns a simpler Range that contains the same versions
359+
///
360+
/// For every one of the Versions provided in versions the existing range and
361+
/// the simplified range will agree on whether it is contained.
362+
/// The simplified version may include or exclude versions that are not in versions as the implementation wishes.
363+
/// For example:
364+
/// - If all the versions are contained in the original than the range will be simplified to `full`.
365+
/// - If none of the versions are contained in the original than the range will be simplified to `empty`.
366+
///
367+
/// If versions are not sorted the correctness of this function is not guaranteed.
368+
pub fn simplify<'a, I>(&self, versions: I) -> Self
369+
where
370+
I: Iterator<Item = &'a V>,
371+
V: 'a,
372+
{
373+
// First we determined for each version if there is a segment that makes it match.
374+
let mut matches = versions.scan(0, |i, v| {
375+
while let Some(segment) = self.segments.get(*i) {
376+
if match segment {
377+
(Excluded(start), _) => v <= start,
378+
(Included(start), _) => v < start,
379+
(Unbounded, _) => false,
380+
} {
381+
return Some(None);
382+
}
383+
if match segment {
384+
(_, Unbounded) => true,
385+
(_, Included(end)) => v <= end,
386+
(_, Excluded(end)) => v < end,
387+
} {
388+
return Some(Some(*i));
389+
}
390+
*i += 1;
391+
}
392+
Some(None)
393+
});
394+
let mut segments = SmallVec::Empty;
395+
// If the first version matched, then the lower bound of that segment is not needed
396+
let mut seg = if let Some(Some(ver)) = matches.next() {
397+
Some((&Unbounded, &self.segments[ver].1))
398+
} else {
399+
None
400+
};
401+
for ver in matches {
402+
if let Some(ver) = ver {
403+
// As long as were still matching versions, we keep merging into the currently matching segment
404+
seg = Some((
405+
seg.map(|(s, _)| s).unwrap_or(&self.segments[ver].0),
406+
&self.segments[ver].1,
407+
));
408+
} else {
409+
// If we have found a version that doesn't match, then right the merge segment and prepare for a new one.
410+
if let Some((s, e)) = seg {
411+
segments.push((s.clone(), e.clone()));
412+
}
413+
seg = None;
414+
}
415+
}
416+
// If the last version matched, then write out the merged segment but the upper bound is not needed.
417+
if let Some((s, _)) = seg {
418+
segments.push((s.clone(), Unbounded));
419+
}
420+
Self { segments }.check_invariants()
421+
}
324422
}
325423

326424
impl<T: Debug + Display + Clone + Eq + Ord> VersionSet for Range<T> {
@@ -600,5 +698,49 @@ pub mod tests {
600698
let rv2: Range<u32> = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty);
601699
assert_eq!(rv, rv2);
602700
}
701+
702+
#[test]
703+
fn contains(range in strategy(), versions in vec![version_strat()]) {
704+
fn direct_implementation_of_contains(s: &Range<u32>, v: &u32) -> bool {
705+
for segment in s.segments.iter() {
706+
if match segment {
707+
(Unbounded, Unbounded) => true,
708+
(Unbounded, Included(end)) => v <= end,
709+
(Unbounded, Excluded(end)) => v < end,
710+
(Included(start), Unbounded) => v >= start,
711+
(Included(start), Included(end)) => v >= start && v <= end,
712+
(Included(start), Excluded(end)) => v >= start && v < end,
713+
(Excluded(start), Unbounded) => v > start,
714+
(Excluded(start), Included(end)) => v > start && v <= end,
715+
(Excluded(start), Excluded(end)) => v > start && v < end,
716+
} {
717+
return true;
718+
}
719+
}
720+
false
721+
}
722+
723+
for v in versions {
724+
assert_eq!(range.contains(&v), direct_implementation_of_contains(&range, &v));
725+
}
726+
}
727+
728+
#[test]
729+
fn contains_many(range in strategy(), mut versions in vec![version_strat()]) {
730+
versions.sort();
731+
for (a, b) in versions.iter().map(|v| range.contains(v)).zip(range.contains_many(versions.iter())) {
732+
assert_eq!(a, b);
733+
}
734+
}
735+
736+
#[test]
737+
fn simplify(range in strategy(), mut versions in vec![version_strat()]) {
738+
versions.sort();
739+
let simp = range.simplify(versions.iter());
740+
for v in versions {
741+
assert_eq!(range.contains(&v), simp.contains(&v));
742+
}
743+
assert!(simp.segments.len() <= range.segments.len())
744+
}
603745
}
604746
}

0 commit comments

Comments
 (0)