Skip to content

Commit c843929

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

File tree

1 file changed

+126
-11
lines changed

1 file changed

+126
-11
lines changed

Diff for: src/range.rs

+126-11
Original file line numberDiff line numberDiff line change
@@ -202,23 +202,40 @@ impl<V: Ord> Range<V> {
202202
/// Returns true if the this Range contains the specified value.
203203
pub fn contains(&self, v: &V) -> bool {
204204
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-
} {
205+
if !within_lower_bound(segment, v) {
206+
return false;
207+
}
208+
if within_uppern_bound(segment, v) {
216209
return true;
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+
versions.scan(0, |i, v| {
226+
while let Some(segment) = self.segments.get(*i) {
227+
if !within_lower_bound(segment, v) {
228+
return Some(false);
229+
}
230+
if within_uppern_bound(segment, v) {
231+
return Some(true);
232+
}
233+
*i += 1;
234+
}
235+
Some(false)
236+
})
237+
}
238+
222239
/// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`.
223240
pub fn from_range_bounds<R, IV>(bounds: R) -> Self
224241
where
@@ -264,6 +281,22 @@ impl<V: Ord> Range<V> {
264281
}
265282
}
266283

284+
fn within_lower_bound<V: PartialOrd>(segment: &Interval<V>, v: &V) -> bool {
285+
match segment {
286+
(Excluded(start), _) => start < v,
287+
(Included(start), _) => start <= v,
288+
(Unbounded, _) => true,
289+
}
290+
}
291+
292+
fn within_uppern_bound<V: PartialOrd>(segment: &Interval<V>, v: &V) -> bool {
293+
match segment {
294+
(_, Unbounded) => true,
295+
(_, Included(end)) => v <= end,
296+
(_, Excluded(end)) => v < end,
297+
}
298+
}
299+
267300
fn valid_segment<T: PartialOrd>(start: &Bound<T>, end: &Bound<T>) -> bool {
268301
match (start, end) {
269302
(Included(s), Included(e)) => s <= e,
@@ -321,6 +354,63 @@ 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 !within_lower_bound(segment, v) {
377+
return Some(None);
378+
}
379+
if within_uppern_bound(segment, v) {
380+
return Some(Some(*i));
381+
}
382+
*i += 1;
383+
}
384+
Some(None)
385+
});
386+
let mut segments = SmallVec::Empty;
387+
// If the first version matched, then the lower bound of that segment is not needed
388+
let mut seg = if let Some(Some(ver)) = matches.next() {
389+
Some((&Unbounded, &self.segments[ver].1))
390+
} else {
391+
None
392+
};
393+
for ver in matches {
394+
if let Some(ver) = ver {
395+
// As long as were still matching versions, we keep merging into the currently matching segment
396+
seg = Some((
397+
seg.map(|(s, _)| s).unwrap_or(&self.segments[ver].0),
398+
&self.segments[ver].1,
399+
));
400+
} else {
401+
// If we have found a version that doesn't match, then right the merge segment and prepare for a new one.
402+
if let Some((s, e)) = seg {
403+
segments.push((s.clone(), e.clone()));
404+
}
405+
seg = None;
406+
}
407+
}
408+
// If the last version matched, then write out the merged segment but the upper bound is not needed.
409+
if let Some((s, _)) = seg {
410+
segments.push((s.clone(), Unbounded));
411+
}
412+
Self { segments }.check_invariants()
413+
}
324414
}
325415

326416
impl<T: Debug + Display + Clone + Eq + Ord> VersionSet for Range<T> {
@@ -600,5 +690,30 @@ pub mod tests {
600690
let rv2: Range<u32> = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty);
601691
assert_eq!(rv, rv2);
602692
}
693+
694+
#[test]
695+
fn contains(range in strategy(), versions in vec![version_strat()]) {
696+
for v in versions {
697+
assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v)));
698+
}
699+
}
700+
701+
#[test]
702+
fn contains_many(range in strategy(), mut versions in vec![version_strat()]) {
703+
versions.sort();
704+
for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) {
705+
assert_eq!(range.contains(a), b);
706+
}
707+
}
708+
709+
#[test]
710+
fn simplify(range in strategy(), mut versions in vec![version_strat()]) {
711+
versions.sort();
712+
let simp = range.simplify(versions.iter());
713+
for v in versions {
714+
assert_eq!(range.contains(&v), simp.contains(&v));
715+
}
716+
assert!(simp.segments.len() <= range.segments.len())
717+
}
603718
}
604719
}

0 commit comments

Comments
 (0)