Skip to content

Commit

Permalink
Auto merge of rust-lang#77704 - AnthonyMikh:slice_index_with_ops_boun…
Browse files Browse the repository at this point in the history
…d_pair, r=m-ou-se

Implement indexing slices with pairs of core::ops::Bound<usize>

Closes rust-lang#49976.

I am not sure about code duplication between `check_range` and `into_maybe_range`. Should be former implemented in terms of the latter? Also this PR doesn't address code duplication between `impl SliceIndex for Range*`.
  • Loading branch information
bors committed Apr 22, 2021
2 parents 25c15cd + 6763a40 commit ccf1712
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 0 deletions.
112 changes: 112 additions & 0 deletions library/core/src/slice/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ mod private_slice_index {
impl Sealed for ops::RangeInclusive<usize> {}
#[stable(feature = "slice_get_slice", since = "1.28.0")]
impl Sealed for ops::RangeToInclusive<usize> {}
#[stable(feature = "slice_index_with_ops_bound_pair", since = "1.53.0")]
impl Sealed for (ops::Bound<usize>, ops::Bound<usize>) {}
}

/// A helper trait used for indexing operations.
Expand Down Expand Up @@ -546,3 +548,113 @@ where

ops::Range { start, end }
}

/// Convert pair of `ops::Bound`s into `ops::Range` without performing any bounds checking and (in debug) overflow checking
fn into_range_unchecked(
len: usize,
(start, end): (ops::Bound<usize>, ops::Bound<usize>),
) -> ops::Range<usize> {
use ops::Bound;
let start = match start {
Bound::Included(i) => i,
Bound::Excluded(i) => i + 1,
Bound::Unbounded => 0,
};
let end = match end {
Bound::Included(i) => i + 1,
Bound::Excluded(i) => i,
Bound::Unbounded => len,
};
start..end
}

/// Convert pair of `ops::Bound`s into `ops::Range`.
/// Returns `None` on overflowing indices.
fn into_range(
len: usize,
(start, end): (ops::Bound<usize>, ops::Bound<usize>),
) -> Option<ops::Range<usize>> {
use ops::Bound;
let start = match start {
Bound::Included(start) => start,
Bound::Excluded(start) => start.checked_add(1)?,
Bound::Unbounded => 0,
};

let end = match end {
Bound::Included(end) => end.checked_add(1)?,
Bound::Excluded(end) => end,
Bound::Unbounded => len,
};

// Don't bother with checking `start < end` and `end <= len`
// since these checks are handled by `Range` impls

Some(start..end)
}

/// Convert pair of `ops::Bound`s into `ops::Range`.
/// Panics on overflowing indices.
fn into_slice_range(
len: usize,
(start, end): (ops::Bound<usize>, ops::Bound<usize>),
) -> ops::Range<usize> {
use ops::Bound;
let start = match start {
Bound::Included(start) => start,
Bound::Excluded(start) => {
start.checked_add(1).unwrap_or_else(|| slice_start_index_overflow_fail())
}
Bound::Unbounded => 0,
};

let end = match end {
Bound::Included(end) => {
end.checked_add(1).unwrap_or_else(|| slice_end_index_overflow_fail())
}
Bound::Excluded(end) => end,
Bound::Unbounded => len,
};

// Don't bother with checking `start < end` and `end <= len`
// since these checks are handled by `Range` impls

start..end
}

#[stable(feature = "slice_index_with_ops_bound_pair", since = "1.53.0")]
unsafe impl<T> SliceIndex<[T]> for (ops::Bound<usize>, ops::Bound<usize>) {
type Output = [T];

#[inline]
fn get(self, slice: &[T]) -> Option<&Self::Output> {
into_range(slice.len(), self)?.get(slice)
}

#[inline]
fn get_mut(self, slice: &mut [T]) -> Option<&mut Self::Output> {
into_range(slice.len(), self)?.get_mut(slice)
}

#[inline]
unsafe fn get_unchecked(self, slice: *const [T]) -> *const Self::Output {
// SAFETY: the caller has to uphold the safety contract for `get_unchecked`.
unsafe { into_range_unchecked(slice.len(), self).get_unchecked(slice) }
}

#[inline]
unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut Self::Output {
// SAFETY: the caller has to uphold the safety contract for `get_unchecked_mut`.
unsafe { into_range_unchecked(slice.len(), self).get_unchecked_mut(slice) }
}

#[inline]
fn index(self, slice: &[T]) -> &Self::Output {
into_slice_range(slice.len(), self).index(slice)
}

#[inline]
fn index_mut(self, slice: &mut [T]) -> &mut Self::Output {
into_slice_range(slice.len(), self).index_mut(slice)
}
}
43 changes: 43 additions & 0 deletions library/core/tests/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,9 @@ mod slice_index {
}
)*) => {$(
mod $case_name {
#[allow(unused_imports)]
use core::ops::Bound;

#[test]
fn pass() {
let mut v = $data;
Expand Down Expand Up @@ -1376,6 +1379,24 @@ mod slice_index {
bad: data[7..=6];
message: "out of range";
}

in mod boundpair_len {
data: [0, 1, 2, 3, 4, 5];

good: data[(Bound::Included(6), Bound::Unbounded)] == [];
good: data[(Bound::Unbounded, Bound::Included(5))] == [0, 1, 2, 3, 4, 5];
good: data[(Bound::Unbounded, Bound::Excluded(6))] == [0, 1, 2, 3, 4, 5];
good: data[(Bound::Included(0), Bound::Included(5))] == [0, 1, 2, 3, 4, 5];
good: data[(Bound::Included(0), Bound::Excluded(6))] == [0, 1, 2, 3, 4, 5];
good: data[(Bound::Included(2), Bound::Excluded(4))] == [2, 3];
good: data[(Bound::Excluded(1), Bound::Included(4))] == [2, 3, 4];
good: data[(Bound::Excluded(5), Bound::Excluded(6))] == [];
good: data[(Bound::Included(6), Bound::Excluded(6))] == [];
good: data[(Bound::Excluded(5), Bound::Included(5))] == [];
good: data[(Bound::Included(6), Bound::Included(5))] == [];
bad: data[(Bound::Unbounded, Bound::Included(6))];
message: "out of range";
}
}

panic_cases! {
Expand Down Expand Up @@ -1416,6 +1437,14 @@ mod slice_index {
bad: data[4..=2];
message: "but ends at";
}

in mod boundpair_neg_width {
data: [0, 1, 2, 3, 4, 5];

good: data[(Bound::Included(4), Bound::Excluded(4))] == [];
bad: data[(Bound::Included(4), Bound::Excluded(3))];
message: "but ends at";
}
}

panic_cases! {
Expand All @@ -1434,6 +1463,20 @@ mod slice_index {
bad: data[..= usize::MAX];
message: "maximum usize";
}

in mod boundpair_overflow_end {
data: [0; 1];

bad: data[(Bound::Unbounded, Bound::Included(usize::MAX))];
message: "maximum usize";
}

in mod boundpair_overflow_start {
data: [0; 1];

bad: data[(Bound::Excluded(usize::MAX), Bound::Unbounded)];
message: "maximum usize";
}
} // panic_cases!
}

Expand Down

0 comments on commit ccf1712

Please sign in to comment.