diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index 07d5a08304e8..6ba91cd23566 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -65,6 +65,10 @@ jobs: if: matrix.host != 'x86_64-unknown-linux-gnu' run: cargo test --features internal -- --skip dogfood + - name: Test clippy_data_structures + run: cargo test + working-directory: clippy_data_structures + - name: Test clippy_lints run: cargo test working-directory: clippy_lints diff --git a/.github/workflows/clippy_pr.yml b/.github/workflows/clippy_pr.yml index 880ebd6e5d5c..88aca34df4fe 100644 --- a/.github/workflows/clippy_pr.yml +++ b/.github/workflows/clippy_pr.yml @@ -41,6 +41,10 @@ jobs: - name: Test run: cargo test --features internal + - name: Test clippy_data_structures + run: cargo test + working-directory: clippy_data_structures + - name: Test clippy_lints run: cargo test working-directory: clippy_lints diff --git a/clippy_data_structures/Cargo.toml b/clippy_data_structures/Cargo.toml new file mode 100644 index 000000000000..1826e9ce31c7 --- /dev/null +++ b/clippy_data_structures/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "clippy_data_structures" +version = "0.0.1" +edition = "2021" + +[dependencies] +arrayvec = { version = "0.7", default-features = false} +smallvec = { version = "1.8.1", features = ["union", "may_dangle", "const_new"] } + +[package.metadata.rust-analyzer] +# This package uses #[feature(rustc_private)] +rustc_private = true diff --git a/clippy_data_structures/src/bit_set_2d.rs b/clippy_data_structures/src/bit_set_2d.rs new file mode 100644 index 000000000000..8cc8bbd0cbfa --- /dev/null +++ b/clippy_data_structures/src/bit_set_2d.rs @@ -0,0 +1,579 @@ +use crate::bit_slice::{BitSlice, Word, final_mask_for_size, word_count_from_bits}; +use crate::range::{self, Len as _, LimitExplicitBounds, SplitAt as _, SubtractFromEdge, WithStride}; +use core::iter; +use core::marker::PhantomData; +use rustc_arena::DroplessArena; +use rustc_index::{Idx, IntoSliceIdx}; + +/// A reference to a two-dimensional bit set. +/// +/// This is represented as a dense array of words stored in row major order with each row aligned to +/// the start of a word. +pub struct BitSlice2d<'a, R, C> { + words: &'a mut [Word], + rows: u32, + columns: u32, + row_stride: u32, + phantom: PhantomData<(R, C)>, +} +impl<'a, R, C> BitSlice2d<'a, R, C> { + /// Interprets `words` as a two-dimensional bit set of the given size. + /// + /// The length of the given slice must match the number of words required to store a bit set + /// with the given dimensions. + #[inline] + #[must_use] + #[expect(clippy::cast_possible_truncation)] + pub fn from_mut_words(words: &'a mut [Word], rows: u32, columns: u32) -> Self { + let row_stride = word_count_from_bits(columns as usize); + debug_assert_eq!(Some(words.len()), row_stride.checked_mul(rows as usize)); + Self { + words, + rows, + columns, + row_stride: row_stride as u32, + phantom: PhantomData, + } + } + + /// Allocates a new empty two-dimensional bit set of the given size. + /// + /// # Panics + /// Panics if `rows * columns` overflows a usize. + #[inline] + #[must_use] + #[expect(clippy::cast_possible_truncation)] + pub fn empty_arena(arena: &'a DroplessArena, rows: u32, columns: u32) -> Self { + let row_stride = word_count_from_bits(columns as usize); + Self { + words: arena.alloc_from_iter(iter::repeat_n(0, row_stride.checked_mul(rows as usize).unwrap())), + rows, + columns, + row_stride: row_stride as u32, + phantom: PhantomData, + } + } + + /// Gets the number of rows. + #[inline] + #[must_use] + pub const fn row_len(&self) -> u32 { + self.rows + } + + /// Gets the number of columns. + #[inline] + #[must_use] + pub const fn column_len(&self) -> u32 { + self.columns + } + + /// Get the backing slice of words. + #[inline] + #[must_use] + pub const fn words(&self) -> &[Word] { + self.words + } + + /// Get the backing slice of words. + #[inline] + #[must_use] + pub fn words_mut(&mut self) -> &mut [Word] { + self.words + } + + /// Creates an iterator over the given rows. + /// + /// # Panics + /// Panics if the range exceeds the number of rows. + #[inline] + #[must_use] + #[track_caller] + pub fn iter_rows( + &self, + range: impl IntoSliceIdx, + ) -> impl ExactSizeIterator> + Clone { + self.words[range.into_slice_idx().with_stride(self.row_stride)] + .chunks_exact(self.row_stride as usize) + .map(|words| BitSlice::from_words(words)) + } + + /// Creates an iterator over the given rows. + /// + /// # Panics + /// Panics if the range exceeds the number of rows. + #[inline] + #[must_use] + #[track_caller] + pub fn iter_mut_rows( + &mut self, + range: impl IntoSliceIdx, + ) -> impl ExactSizeIterator> { + self.words[range.into_slice_idx().with_stride(self.row_stride)] + .chunks_exact_mut(self.row_stride as usize) + .map(|words| BitSlice::from_words_mut(words)) + } + + /// Checks if the set is empty. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.words.iter().all(|&x| x == 0) + } + + /// Counts the number of elements in the set. + #[inline] + #[must_use] + pub fn count(&self) -> usize { + self.words.iter().map(|&x| x.count_ones() as usize).sum() + } + + /// Remove all elements from the set. + #[inline] + pub fn clear(&mut self) { + self.words.fill(0); + } + + /// Inserts all elements into the set. + #[inline] + pub fn insert_all(&mut self) { + self.words.fill(!0); + let mask = final_mask_for_size(self.columns as usize); + for row in self.iter_mut_rows(..) { + row.mask_final_word(mask); + } + } + + /// Performs a union of two sets storing the result in `self`. Returns `true` if `self` has + /// changed. + /// + /// # Panics + /// Panics if the sets contain a different number of either rows or columns. + pub fn union(&mut self, other: &BitSlice2d<'_, R, C>) -> bool { + assert_eq!(self.rows, other.rows); + assert_eq!(self.columns, other.columns); + self.words.iter_mut().zip(&*other.words).fold(false, |res, (dst, src)| { + let prev = *dst; + *dst |= *src; + res || prev != *dst + }) + } + + /// Performs a subtraction of other from `self` storing the result in `self`. Returns `true` if + /// `self` has changed. + /// + /// # Panics + /// Panics if the sets contain a different number of either rows or columns. + pub fn subtract(&mut self, other: &BitSlice2d<'_, R, C>) -> bool { + assert_eq!(self.rows, other.rows); + assert_eq!(self.columns, other.columns); + self.words.iter_mut().zip(&*other.words).fold(false, |res, (dst, src)| { + let prev = *dst; + *dst &= !*src; + res || prev != *dst + }) + } + + /// Performs an intersection of two sets storing the result in `self`. Returns `true` if `self` + /// has changed. + /// + /// # Panics + /// Panics if the sets contain a different number of either rows or columns. + pub fn intersect(&mut self, other: &BitSlice2d<'_, R, C>) -> bool { + assert_eq!(self.rows, other.rows); + assert_eq!(self.columns, other.columns); + self.words.iter_mut().zip(&*other.words).fold(false, |res, (dst, src)| { + let prev = *dst; + *dst &= *src; + res || prev != *dst + }) + } +} +impl BitSlice2d<'_, R, C> { + /// Creates an iterator which enumerates all rows. + #[inline] + #[must_use] + pub fn enumerate_rows(&self) -> impl ExactSizeIterator)> + Clone { + self.words + .chunks_exact(self.row_stride as usize) + .map(|words| BitSlice::from_words(words)) + .enumerate() + .map(|(i, row)| (R::new(i), row)) + } + + /// Creates an iterator which enumerates all rows. + #[inline] + pub fn enumerate_rows_mut(&mut self) -> impl ExactSizeIterator)> { + self.words + .chunks_exact_mut(self.row_stride as usize) + .map(|words| BitSlice::from_words_mut(words)) + .enumerate() + .map(|(i, row)| (R::new(i), row)) + } + + /// Gets a reference to the given row. + /// + /// # Panics + /// Panics if the row greater than the number of rows. + #[inline] + #[track_caller] + pub fn row(&self, row: R) -> &BitSlice { + assert!(row.index() < self.rows as usize); + let start = self.row_stride as usize * row.index(); + BitSlice::from_words(&self.words[start..start + self.row_stride as usize]) + } + + /// Gets a reference to the given row. + /// + /// # Panics + /// Panics if the row greater than the number of rows. + #[inline] + #[track_caller] + pub fn row_mut(&mut self, row: R) -> &mut BitSlice { + assert!(row.index() < self.rows as usize); + let start = self.row_stride as usize * row.index(); + BitSlice::from_words_mut(&mut self.words[start..start + self.row_stride as usize]) + } + + /// Copies a range of rows to another part of the bitset. + /// + /// # Panics + /// Panics if either the source or destination range exceeds the number of rows. + #[inline] + #[track_caller] + pub fn copy_rows(&mut self, src: impl IntoSliceIdx, dst: R) { + let src = src.into_slice_idx().with_stride(self.row_stride); + self.words.copy_within(src, dst.index() * self.row_stride as usize); + } + + /// Moves a range of rows to another part of the bitset leaving empty rows behind. + /// + /// # Panics + /// Panics if either the source or destination range exceeds the number of rows. + #[inline] + #[track_caller] + pub fn move_rows( + &mut self, + src: impl IntoSliceIdx>, + dst: R, + ) { + let src = src.into_slice_idx().with_stride(self.row_stride); + let dst_start = dst.index() * self.row_stride as usize; + self.words.copy_within(src.clone(), dst_start); + let src_len = src.len(); + self.words[src.subtract_from_edge(dst_start..dst_start + src_len)].fill(0); + } + + /// Clears all elements from a range of rows. + /// + /// # Panics + /// Panics if the range exceeds the number of rows. + #[inline] + #[track_caller] + pub fn clear_rows(&mut self, rows: impl IntoSliceIdx) { + let words = &mut self.words[rows.into_slice_idx().with_stride(self.row_stride)]; + words.fill(0); + } +} + +impl PartialEq for BitSlice2d<'_, R, C> { + fn eq(&self, other: &Self) -> bool { + self.columns == other.columns && self.rows == other.rows && self.words == other.words + } +} +impl Eq for BitSlice2d<'_, R, C> {} + +/// A two-dimensional bit set with a fixed number of columns and a dynamic number of rows. +/// +/// This is represented as a dense array of words stored in row major order with each row aligned to +/// the start of a word. Any row not physically stored will be treated as though it contains no +/// items and storage for the row (and all previous rows) will be allocated as needed to store +/// values. In effect this will behave as though it had the maximum number of rows representable by +/// `R`. +pub struct GrowableBitSet2d { + words: Vec, + rows: u32, + columns: u32, + row_stride: u32, + phantom: PhantomData<(R, C)>, +} +impl GrowableBitSet2d { + /// Creates a new bit set with the given number of columns without allocating any storage. + #[inline] + #[must_use] + #[expect(clippy::cast_possible_truncation)] + pub const fn new(columns: u32) -> Self { + Self { + words: Vec::new(), + rows: 0, + columns, + row_stride: word_count_from_bits(columns as usize) as u32, + phantom: PhantomData, + } + } + + /// Gets the number of rows for which values are currently stored. + #[inline] + #[must_use] + pub const fn row_len(&self) -> u32 { + self.rows + } + + /// Gets the number of columns. + #[inline] + #[must_use] + pub const fn column_len(&self) -> u32 { + self.columns + } + + /// Get the backing slice of currently stored words. + #[inline] + #[must_use] + pub fn words(&self) -> &[Word] { + self.words.as_slice() + } + + /// Get the backing slice of currently stored words. + #[inline] + #[must_use] + pub fn words_mut(&mut self) -> &mut [Word] { + self.words.as_mut_slice() + } + + /// Checks if the set is empty. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.words.iter().all(|&x| x == 0) + } + + /// Creates an iterator over a range of stored rows. Any unstored rows within the range will be + /// silently ignored. + #[inline] + #[must_use] + pub fn iter_rows( + &self, + range: impl IntoSliceIdx, + ) -> impl ExactSizeIterator> + Clone { + self.words[range + .into_slice_idx() + .limit_explicit_bounds(self.rows as usize) + .with_stride(self.row_stride)] + .chunks_exact(self.row_stride as usize) + .map(|words| BitSlice::from_words(words)) + } + + /// Creates an iterator over a range of stored rows. Any unstored rows within the range will be + /// silently ignored. + #[inline] + #[must_use] + pub fn iter_mut_rows( + &mut self, + range: impl IntoSliceIdx, + ) -> impl ExactSizeIterator> { + self.words[range + .into_slice_idx() + .limit_explicit_bounds(self.rows as usize) + .with_stride(self.row_stride)] + .chunks_exact_mut(self.row_stride as usize) + .map(|words| BitSlice::from_words_mut(words)) + } + + /// Counts the number of elements in the set. + #[inline] + #[must_use] + pub fn count(&self) -> usize { + self.words.iter().map(|&x| x.count_ones() as usize).sum() + } + + /// Removes all items in the set and resets the number of stored rows to zero. + /// + /// This will not deallocate any currently allocated storage. + #[inline] + pub fn clear(&mut self) { + self.words.clear(); + self.rows = 0; + } + + /// Performs a union of two sets storing the result in `self`. Returns `true` if `self` has + /// changed. + /// + /// The number of rows stored in `self` will be extended if needed. + /// + /// # Panics + /// Panics if the sets contain a different number of columns. + pub fn union(&mut self, other: &Self) -> bool { + assert_eq!(self.columns, other.columns); + if self.rows < other.rows { + self.words.resize(other.row_stride as usize * other.rows as usize, 0); + self.rows = other.rows; + } + self.words.iter_mut().zip(&*other.words).fold(false, |res, (dst, src)| { + let prev = *dst; + *dst |= *src; + res || prev != *dst + }) + } +} +impl GrowableBitSet2d { + /// Creates an iterator which enumerates all stored rows. + #[inline] + #[must_use] + pub fn enumerate_rows(&self) -> impl ExactSizeIterator)> + Clone { + self.words + .chunks_exact(self.row_stride as usize) + .map(|words| BitSlice::from_words(words)) + .enumerate() + .map(|(i, row)| (R::new(i), row)) + } + + /// Creates an iterator which enumerates all stored rows. + #[inline] + #[must_use] + pub fn enumerate_mut_rows(&mut self) -> impl ExactSizeIterator)> { + self.words + .chunks_exact_mut(self.row_stride as usize) + .map(|words| BitSlice::from_words_mut(words)) + .enumerate() + .map(|(i, row)| (R::new(i), row)) + } + + /// Gets a reference to a row if the row is stored, or `None` if it is not. + #[inline] + pub fn opt_row(&self, row: R) -> Option<&BitSlice> { + let start = self.row_stride as usize * row.index(); + self.words + .get(start..start + self.row_stride as usize) + .map(BitSlice::from_words) + } + + /// Gets a reference to a row, allocating storage for it if needed. + /// + /// This will also allocate storage for all previous rows. + #[inline] + #[expect(clippy::cast_possible_truncation)] + pub fn ensure_row(&mut self, row: R) -> &mut BitSlice { + let start = self.row_stride as usize * row.index(); + let end = start + self.row_stride as usize; + BitSlice::from_words_mut(if self.words.get_mut(start..end).is_some() { + // Can't use the borrow from before due to borrow checking errors. + &mut self.words[start..end] + } else { + self.words.resize(end, 0); + self.rows = row.index() as u32 + 1; + &mut self.words[start..end] + }) + } + + /// Clears all elements from a range of rows. + /// + /// Any unstored rows referenced by the range will be silently ignored. + #[inline] + pub fn clear_rows(&mut self, rows: impl IntoSliceIdx) { + self.words[rows + .into_slice_idx() + .limit_explicit_bounds(self.rows as usize) + .with_stride(self.row_stride)] + .fill(0); + } + + /// Copies a range of rows to another part of the bitset. + /// + /// All unstored rows in the source range will be treated as though they were empty. All + /// unstored rows in the destination range with a corresponding stored row in the source range + /// will be allocated. + #[expect(clippy::cast_possible_truncation)] + pub fn copy_rows(&mut self, src: impl IntoSliceIdx, dst: R) { + let (src_range, src_extra) = src.into_slice_idx().split_at(self.rows as usize); + let src_row_len = src_range.len(); + if src_row_len == 0 { + let range = (dst.index()..dst.index() + src_extra) + .with_stride(self.row_stride) + .limit_explicit_bounds(self.words.len()); + self.words[range].fill(0); + } else { + let dst_row_end = dst.index() + src_row_len; + let dst_start = dst.index() * self.row_stride as usize; + let src_range = src_range.with_stride(self.row_stride); + let dst_copy_end = dst_start + src_range.len(); + if self.rows < dst_row_end as u32 { + self.words.resize(dst_copy_end, 0); + self.rows = dst_row_end as u32; + } + self.words.copy_within(src_range, dst_start); + let dst_end = self + .words + .len() + .min(dst_copy_end + src_extra * self.row_stride as usize); + self.words[dst_copy_end..dst_end].fill(0); + } + } + + /// Moves a range of rows to another part of the bitset leaving empty rows behind. + /// + /// All unstored rows in the source range will be treated as though they were empty. All + /// unstored rows in the destination range with a corresponding stored row in the source range + /// will be allocated. + #[expect(clippy::cast_possible_truncation)] + pub fn move_rows(&mut self, src: impl IntoSliceIdx, dst: R) { + let (src_range, src_extra) = src.into_slice_idx().split_at(self.rows as usize); + let src_row_len = src_range.len(); + if src_row_len == 0 { + let range = (dst.index()..dst.index() + src_extra) + .with_stride(self.row_stride) + .limit_explicit_bounds(self.words.len()); + self.words[range].fill(0); + } else { + let dst_row_end = dst.index() + src_row_len; + let dst_start = dst.index() * self.row_stride as usize; + let src_range = src_range.with_stride(self.row_stride); + let dst_copy_end = dst_start + src_range.len(); + if self.rows < dst_row_end as u32 { + self.words.resize(dst_copy_end, 0); + self.rows = dst_row_end as u32; + } + self.words.copy_within(src_range.clone(), dst_start); + let dst_end = self + .words + .len() + .min(dst_copy_end + src_extra * self.row_stride as usize); + self.words[dst_copy_end..dst_end].fill(0); + self.words[src_range.subtract_from_edge(dst_start..dst_end)].fill(0); + } + } +} + +impl PartialEq for GrowableBitSet2d { + fn eq(&self, other: &Self) -> bool { + assert_eq!(self.columns, other.columns); + let (lhs, rhs, extra) = if let Some((lhs, extra)) = self.words.split_at_checked(other.words.len()) { + (lhs, other.words.as_slice(), extra) + } else { + let (rhs, extra) = other.words.split_at(self.words.len()); + (self.words.as_slice(), rhs, extra) + }; + lhs == rhs && extra.iter().all(|&x| x == 0) + } +} +impl Eq for GrowableBitSet2d {} + +impl Clone for GrowableBitSet2d { + #[inline] + fn clone(&self) -> Self { + Self { + words: self.words.clone(), + rows: self.rows, + columns: self.columns, + row_stride: self.row_stride, + phantom: PhantomData, + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + self.words.clone_from(&source.words); + self.rows = source.rows; + self.columns = source.columns; + self.row_stride = source.row_stride; + } +} diff --git a/clippy_data_structures/src/bit_slice.rs b/clippy_data_structures/src/bit_slice.rs new file mode 100644 index 000000000000..ff0114d41583 --- /dev/null +++ b/clippy_data_structures/src/bit_slice.rs @@ -0,0 +1,538 @@ +use core::marker::PhantomData; +use core::mem::{self, transmute}; +use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; +use core::slice::{self, SliceIndex}; +use core::{iter, range}; +use rustc_arena::DroplessArena; +use rustc_index::{Idx, IntoSliceIdx}; + +pub type Word = usize; +pub const WORD_BITS: usize = Word::BITS as usize; +#[expect(clippy::unnecessary_cast)] +pub const MAX_WORDS: usize = Word::MAX as usize / WORD_BITS; + +#[inline] +#[must_use] +#[expect(clippy::manual_div_ceil, reason = "worse codegen")] +pub const fn word_count_from_bits(bits: usize) -> usize { + (bits + (WORD_BITS - 1)) / WORD_BITS +} + +/// Gets the mask used to remove out-of-range bits from the final word. +#[inline] +#[must_use] +pub const fn final_mask_for_size(bits: usize) -> Word { + (!(!(0 as Word) << (bits % WORD_BITS))).wrapping_sub((bits % WORD_BITS == 0) as Word) +} + +pub struct BitRange { + /// The range of affected words. + words: R, + /// The amount to shift to make a bit-mask for the first word. + first_shift: u8, + /// The amount to shift to make a bit-mask for the last word. + last_shift: u8, +} +impl BitRange { + #[inline] + const fn first_mask(&self) -> Word { + !0 << self.first_shift + } + + #[inline] + const fn last_mask(&self) -> Word { + !0 >> self.last_shift + } +} + +pub trait IntoBitRange: Sized { + type Range: SliceIndex<[Word], Output = [Word]>; + fn into_bit_range(self) -> BitRange; +} +impl IntoBitRange for RangeFull { + type Range = Self; + #[inline] + fn into_bit_range(self) -> BitRange { + BitRange { + words: self, + first_shift: 0, + last_shift: 0, + } + } +} +impl IntoBitRange for Range { + type Range = Self; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let start = BitIdx::from_bit(self.start); + let end = BitIdx::from_bit(self.end); + BitRange { + words: Range { + start: start.word, + end: end.word + usize::from(end.bit != 0), + }, + first_shift: start.bit as u8, + last_shift: ((WORD_BITS - 1) - (end.bit.wrapping_sub(1) % WORD_BITS)) as u8, + } + } +} +impl IntoBitRange for RangeFrom { + type Range = Self; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let start = BitIdx::from_bit(self.start); + BitRange { + words: RangeFrom { start: start.word }, + first_shift: start.bit as u8, + last_shift: 0, + } + } +} +impl IntoBitRange for RangeTo { + type Range = Self; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let end = BitIdx::from_bit(self.end); + BitRange { + words: RangeTo { + end: end.word + usize::from(end.bit != 0), + }, + first_shift: 0, + last_shift: ((WORD_BITS - 1) - (end.bit.wrapping_sub(1) % WORD_BITS)) as u8, + } + } +} +impl IntoBitRange for RangeInclusive { + type Range = Range; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let start = BitIdx::from_bit(*self.start()); + let end = BitIdx::from_bit(*self.end()); + BitRange { + words: Range { + start: start.word, + end: end.word + 1, + }, + first_shift: start.bit as u8, + last_shift: ((WORD_BITS - 1) - end.bit) as u8, + } + } +} +impl IntoBitRange for RangeToInclusive { + type Range = RangeTo; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let end = BitIdx::from_bit(self.end); + BitRange { + words: RangeTo { end: end.word + 1 }, + first_shift: 0, + last_shift: ((WORD_BITS - 1) - end.bit) as u8, + } + } +} +impl IntoBitRange for range::Range { + type Range = range::Range; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let start = BitIdx::from_bit(self.start); + let end = BitIdx::from_bit(self.end); + BitRange { + words: range::Range { + start: start.word, + end: end.word + usize::from(end.bit != 0), + }, + first_shift: start.bit as u8, + last_shift: ((WORD_BITS - 1) - (end.bit.wrapping_sub(1) % WORD_BITS)) as u8, + } + } +} +impl IntoBitRange for range::RangeFrom { + type Range = range::RangeFrom; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let start = BitIdx::from_bit(self.start); + BitRange { + words: range::RangeFrom { start: start.word }, + first_shift: start.bit as u8, + last_shift: 0, + } + } +} +impl IntoBitRange for range::RangeInclusive { + type Range = range::Range; + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn into_bit_range(self) -> BitRange { + let start = BitIdx::from_bit(self.start); + let end = BitIdx::from_bit(self.end); + BitRange { + words: range::Range { + start: start.word, + end: end.word + 1, + }, + first_shift: start.bit as u8, + last_shift: ((WORD_BITS - 1) - end.bit) as u8, + } + } +} + +struct BitIdx { + word: usize, + bit: usize, +} +impl BitIdx { + #[inline] + fn from_bit(bit: T) -> Self { + let bit = bit.index(); + Self { + word: bit / WORD_BITS, + bit: bit % WORD_BITS, + } + } + + #[inline] + fn word_mask(&self) -> Word { + 1 << self.bit + } +} + +/// A bit set represented as a dense slice of words. +/// +/// n.b. This can only hold bits as a multiple of `WORD_SIZE`. Use +/// `mask_final_word(final_mask_for_size(len))` to clear the final bits greater than or equal to +/// `len`. +#[repr(transparent)] +pub struct BitSlice { + phantom: PhantomData, + pub words: [Word], +} +impl BitSlice { + /// Interprets `words` as a bit set of the same size. + #[inline] + #[must_use] + pub const fn from_words(words: &[Word]) -> &Self { + // Not actually a safety requirement since everything will be checked by the slice on use. + debug_assert!(words.len() <= MAX_WORDS); + // SAFETY: `BitSlice` is a transparent wrapper around `[Word]`. + unsafe { transmute::<&[Word], &Self>(words) } + } + + /// Interprets `words` as a bit set of the same size. + #[inline] + #[expect(clippy::transmute_ptr_to_ptr)] + pub fn from_words_mut(words: &mut [Word]) -> &mut Self { + // Not actually a safety requirement since everything will be checked by the slice on use. + debug_assert!(words.len() <= MAX_WORDS); + // SAFETY: `BitSlice` is a transparent wrapper around `[Word]`. + unsafe { transmute::<&mut [Word], &mut Self>(words) } + } + + /// Interprets `words` as a bit set of the same size. + #[inline] + #[must_use] + pub fn from_boxed_words(words: Box<[Word]>) -> Box { + // Not actually a safety requirement since everything will be checked by the slice on use. + debug_assert!(words.len() <= MAX_WORDS); + // SAFETY: `BitSlice` is a transparent wrapper around `[Word]`. + unsafe { transmute::, Box>(words) } + } + + /// Gets the size of this slice in bits. + #[inline] + #[must_use] + pub const fn bit_len(&self) -> usize { + self.words.len() * WORD_BITS + } + + /// Checks if the set is empty. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.words.iter().all(|&x| x == 0) + } + + /// Counts the number of elements in the set. + #[inline] + #[must_use] + pub fn count(&self) -> usize { + self.words.iter().map(|&x| x.count_ones() as usize).sum() + } + + /// Allocates a new empty boxed bit set of the given size rounded up to the nearest word size. + #[inline] + #[must_use] + pub fn empty_box(bits: usize) -> Box { + Self::from_boxed_words(vec![0; word_count_from_bits(bits)].into_boxed_slice()) + } + + /// Allocates a new empty bit set of the given size rounded up to the nearest word size. + #[inline] + pub fn empty_arena(arena: &DroplessArena, bits: usize) -> &mut Self { + Self::from_words_mut(arena.alloc_from_iter(iter::repeat_n(0, word_count_from_bits(bits)))) + } + + /// Applies a bit-mask to the final word of the slice. + #[inline] + pub fn mask_final_word(&mut self, mask: Word) { + if let Some(word) = self.words.last_mut() { + *word &= mask; + } + } + + /// Fills the entire set. + /// + /// n.b. This can only work with whole `Word`s. Use `mask_final_word(final_mask_for_size(len))` + /// to clear the final bits greater than or equal to `len`. + #[inline] + pub fn fill(&mut self) { + self.words.fill(!0); + } + + /// Remove all elements from the set. + #[inline] + pub fn clear(&mut self) { + self.words.fill(0); + } + + /// Performs a union of two sets storing the result in `self`. Returns `true` if `self` has + /// changed. + /// + /// Note: The result will be truncated to the number of bits contained in `self` + pub fn union_trunc(&mut self, other: &Self) -> bool { + self.words.iter_mut().zip(&other.words).fold(false, |res, (lhs, rhs)| { + let prev = *lhs; + *lhs |= *rhs; + prev != *lhs || res + }) + } + + /// Performs an intersection of two sets storing the result in `self`. Returns `true` if `self` + /// has changed. + pub fn intersect(&mut self, other: &Self) -> bool { + self.words.iter_mut().zip(&other.words).fold(false, |res, (lhs, rhs)| { + let prev = *lhs; + *lhs &= *rhs; + prev != *lhs || res + }) + } + + /// Performs a subtraction of other from `self` storing the result in `self`. Returns `true` if + /// `self` has changed. + pub fn subtract(&mut self, other: &Self) -> bool { + self.words.iter_mut().zip(&other.words).fold(false, |res, (lhs, rhs)| { + let prev = *lhs; + *lhs &= !*rhs; + prev != *lhs || res + }) + } +} +impl BitSlice { + /// Inserts the given element into the set. Returns `true` if `self` has changed. + /// + /// # Panics + /// Panics if the element lies outside the bounds of this slice. + #[inline] + #[track_caller] + pub fn insert(&mut self, bit: T) -> bool { + let idx = BitIdx::from_bit(bit); + let res = self.words[idx.word] & idx.word_mask() == 0; + self.words[idx.word] |= idx.word_mask(); + res + } + + /// Removes the given element from the set. Returns `true` if `self` has changed. + /// + /// # Panics + /// Panics if the element lies outside the bounds of this slice. + #[inline] + #[track_caller] + pub fn remove(&mut self, bit: T) -> bool { + let idx = BitIdx::from_bit(bit); + let res = self.words[idx.word] & idx.word_mask() != 0; + self.words[idx.word] &= !idx.word_mask(); + res + } + + /// Checks if the set contains the given element. + /// + /// # Panics + /// Panics if the element lies outside the bounds of this slice. + #[inline] + #[track_caller] + pub fn contains(&self, bit: T) -> bool { + let idx = BitIdx::from_bit(bit); + self.words.get(idx.word).map_or(0, |&x| x) & idx.word_mask() != 0 + } + + /// Inserts the given range of elements into the slice. + /// + /// # Panics + /// Panics if the range exceeds the bounds of this slice. + #[track_caller] + pub fn insert_range(&mut self, range: impl IntoSliceIdx) { + let range = range.into_slice_idx().into_bit_range(); + let first = range.first_mask(); + let last = range.last_mask(); + match &mut self.words[range.words] { + [] => {}, + [dst] => *dst |= first & last, + [first_dst, dst @ .., last_dst] => { + *first_dst |= first; + dst.fill(!0); + *last_dst |= last; + }, + } + } + + /// Creates an iterator over all items in the set. + #[inline] + #[must_use] + pub fn iter(&self) -> Iter<'_, T> { + Iter::new(&self.words) + } + + /// Creates an iterator which returns and removes all items in the set. + /// + /// If the iterator is dropped before it is fully consumed all remaining items in the set will + /// be removed. + #[inline] + #[must_use] + pub fn drain(&mut self) -> Drain<'_, T> { + Drain::new(&mut self.words) + } +} + +impl Extend for &mut BitSlice { + fn extend>(&mut self, iter: Iter) { + for i in iter { + self.insert(i); + } + } +} + +impl<'a, T: Idx> IntoIterator for &'a BitSlice { + type Item = T; + type IntoIter = Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + Iter::new(&self.words) + } +} + +/// Iterator over the set bits in a single word. +#[derive(Default, Clone)] +pub struct WordBitIter(Word); +impl WordBitIter { + #[inline] + #[must_use] + pub const fn new(word: Word) -> Self { + Self(word) + } +} +impl Iterator for WordBitIter { + type Item = u32; + #[inline] + fn next(&mut self) -> Option { + if self.0 == 0 { + None + } else { + let bit_pos = self.0.trailing_zeros(); + self.0 ^= 1 << bit_pos; + Some(bit_pos) + } + } +} + +// Copied from `rustc_data_structures::bit_set`. +pub struct Iter<'a, T: Idx> { + /// Iterator over a single word. + word: WordBitIter, + + /// The offset (measured in bits) of the current word. + offset: usize, + + /// Underlying iterator over the words. + inner: slice::Iter<'a, Word>, + + marker: PhantomData, +} +impl<'a, T: Idx> Iter<'a, T> { + #[inline] + fn new(words: &'a [Word]) -> Self { + // We initialize `word` and `offset` to degenerate values. On the first + // call to `next()` we will fall through to getting the first word from + // `iter`, which sets `word` to the first word (if there is one) and + // `offset` to 0. Doing it this way saves us from having to maintain + // additional state about whether we have started. + Self { + word: WordBitIter::new(0), + offset: usize::MAX - (WORD_BITS - 1), + inner: words.iter(), + marker: PhantomData, + } + } +} +impl Iterator for Iter<'_, T> { + type Item = T; + fn next(&mut self) -> Option { + loop { + if let Some(idx) = self.word.next() { + return Some(T::new(idx as usize + self.offset)); + } + + // Move onto the next word. `wrapping_add()` is needed to handle + // the degenerate initial value given to `offset` in `new()`. + self.word = WordBitIter::new(*self.inner.next()?); + self.offset = self.offset.wrapping_add(WORD_BITS); + } + } +} + +pub struct Drain<'a, T> { + word: WordBitIter, + offset: usize, + iter: slice::IterMut<'a, Word>, + marker: PhantomData, +} +impl<'a, T> Drain<'a, T> { + #[inline] + fn new(words: &'a mut [Word]) -> Self { + Self { + word: WordBitIter::new(0), + offset: usize::MAX - (WORD_BITS - 1), + iter: words.iter_mut(), + marker: PhantomData, + } + } +} +impl Drop for Drain<'_, T> { + #[inline] + fn drop(&mut self) { + for x in &mut self.iter { + *x = 0; + } + } +} +impl Iterator for Drain<'_, T> { + type Item = T; + fn next(&mut self) -> Option { + loop { + if let Some(idx) = self.word.next() { + return Some(T::new(idx as usize + self.offset)); + } + + // Move onto the next word. `wrapping_add()` is needed to handle + // the degenerate initial value given to `offset` in `new()`. + self.word = WordBitIter::new(mem::replace(self.iter.next()?, 0)); + self.offset = self.offset.wrapping_add(WORD_BITS); + } + } +} diff --git a/clippy_data_structures/src/lib.rs b/clippy_data_structures/src/lib.rs new file mode 100644 index 000000000000..c84b8aa75a58 --- /dev/null +++ b/clippy_data_structures/src/lib.rs @@ -0,0 +1,86 @@ +#![feature( + array_windows, + cmp_minmax, + if_let_guard, + maybe_uninit_slice, + min_specialization, + new_range_api, + rustc_private, + slice_partition_dedup +)] + +extern crate rustc_arena; +extern crate rustc_driver; +extern crate rustc_index; +extern crate rustc_mir_dataflow; + +use core::ops::RangeBounds; + +mod range; +mod sorted; +mod traits; + +pub mod bit_slice; +pub use bit_slice::BitSlice; + +pub mod bit_set_2d; +pub use bit_set_2d::{BitSlice2d, GrowableBitSet2d}; + +mod slice_set; +pub use slice_set::SliceSet; + +/// An iterator where the size hint is provided by calling `Iterator::count`. +pub struct CountedIter(pub T); +impl Iterator for CountedIter +where + T: Iterator + Clone, +{ + type Item = T::Item; + fn next(&mut self) -> Option { + self.0.next() + } + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } + fn count(self) -> usize { + self.0.count() + } + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.fold(init, f) + } + fn size_hint(&self) -> (usize, Option) { + let size = self.0.clone().count(); + (size, Some(size)) + } +} + +/// Moves items within the slice leaving behind the default value at indices from the source range +/// which are not also part of the destination range. +#[inline] +pub fn move_within_slice( + slice: &mut [impl Copy + Default], + src: impl Clone + RangeBounds + range::Len + range::SubtractFromEdge, + dst: usize, +) { + slice.copy_within(src.clone(), dst); + let src_len = src.len(); + for x in &mut slice[src.subtract_from_edge(dst..dst + src_len)] { + *x = Default::default(); + } +} + +#[test] +fn test_move_within_slice() { + let slice = &mut [0, 1, 2, 3, 4]; + move_within_slice(slice, 0..2, 2); + assert_eq!(slice, &[0, 0, 0, 1, 4]); + move_within_slice(slice, 3..5, 3); + assert_eq!(slice, &[0, 0, 0, 1, 4]); + move_within_slice(slice, 3..5, 2); + assert_eq!(slice, &[0, 0, 1, 4, 0]); + move_within_slice(slice, 2..4, 3); + assert_eq!(slice, &[0, 0, 0, 1, 4]); +} diff --git a/clippy_data_structures/src/range.rs b/clippy_data_structures/src/range.rs new file mode 100644 index 000000000000..6f84df7e4ff9 --- /dev/null +++ b/clippy_data_structures/src/range.rs @@ -0,0 +1,1018 @@ +use crate::bit_slice::Word; +use core::cmp::minmax; +use core::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; +use core::range; +use core::slice::SliceIndex; + +/// Gets the total number of steps in a range. +pub trait Len { + /// Gets the total number of steps in a range. + fn len(&self) -> usize; +} +impl Len for usize { + #[inline] + fn len(&self) -> usize { + 1 + } +} +impl Len for Range { + #[inline] + fn len(&self) -> usize { + self.end - self.start + } +} +impl Len for range::Range { + #[inline] + fn len(&self) -> usize { + self.end - self.start + } +} +impl Len for RangeTo { + #[inline] + fn len(&self) -> usize { + self.end + } +} + +/// Removes items from the current range which overlap with another. +/// +/// The other range must either start before or at the current range, or it must end at or after the +/// current range. i.e. `other.start <= self.start || self.end <= other.end` +pub trait SubtractFromEdge { + /// Removes items from the current range which overlap with another. + fn subtract_from_edge(self, other: Range) -> Range; +} +impl SubtractFromEdge for usize { + #[inline] + fn subtract_from_edge(self, other: Range) -> Range { + Range { + start: self, + end: self + usize::from(other.contains(&self)), + } + } +} +impl SubtractFromEdge for Range { + #[inline] + fn subtract_from_edge(self, other: Range) -> Range { + debug_assert!(other.start <= self.start || self.end <= other.end); + let (start, end) = if other.start <= self.start { + (self.start.max(other.end).min(self.end), self.end) + } else { + (self.start, self.end.min(other.start)) + }; + Range { start, end } + } +} +impl SubtractFromEdge for range::Range { + #[inline] + fn subtract_from_edge(self, other: Range) -> Range { + debug_assert!(other.start <= self.start || self.end <= other.end); + let (start, end) = if other.start <= self.start { + (self.start.max(other.end).min(self.end), self.end) + } else { + (self.start, self.end.min(other.start)) + }; + Range { start, end } + } +} +impl SubtractFromEdge for RangeTo { + #[inline] + fn subtract_from_edge(self, other: Range) -> Range { + debug_assert!(other.start == 0 || self.end <= other.end); + let (start, end) = if other.start == 0 { + (other.end.min(self.end), self.end) + } else { + (0, self.end.min(other.start)) + }; + Range { start, end } + } +} + +/// Applies an exclusive upper limit to any explicit bounds in a range leaving implicit bounds +/// unchanged. +pub trait LimitExplicitBounds { + type Output: Clone + + SliceIndex<[Word], Output = [Word]> + + RangeBounds + + LimitExplicitBounds + + WithStride; + /// Applies an exclusive upper limit to any explicit bounds in a range leaving implicit bounds + /// unchanged. + fn limit_explicit_bounds(self, limit: usize) -> Self::Output; +} +impl LimitExplicitBounds for usize { + type Output = Range; + #[inline] + #[expect(clippy::range_plus_one)] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + if self < limit { self..self + 1 } else { limit..limit } + } +} +impl LimitExplicitBounds for RangeFull { + type Output = Self; + #[inline] + fn limit_explicit_bounds(self, _: usize) -> Self::Output { + self + } +} +impl LimitExplicitBounds for Range { + type Output = Self; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + Self { + start: self.start.min(limit), + end: self.end.min(limit), + } + } +} +impl LimitExplicitBounds for range::Range { + type Output = Self; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + Self { + start: self.start.min(limit), + end: self.end.min(limit), + } + } +} +impl LimitExplicitBounds for RangeInclusive { + type Output = Range; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + Range { + start: (*self.start()).min(limit), + end: if *self.end() < limit { + match self.end_bound() { + Bound::Included(&x) => x + 1, + Bound::Excluded(&x) => x, + Bound::Unbounded => unreachable!(), + } + } else { + limit + }, + } + } +} +impl LimitExplicitBounds for range::RangeInclusive { + type Output = range::Range; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + range::Range { + start: self.start.min(limit), + end: if self.end < limit { self.end + 1 } else { limit }, + } + } +} +impl LimitExplicitBounds for RangeTo { + type Output = Self; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + Self { + end: self.end.min(limit), + } + } +} +impl LimitExplicitBounds for RangeToInclusive { + type Output = RangeTo; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + RangeTo { + end: if self.end < limit { self.end + 1 } else { limit }, + } + } +} +impl LimitExplicitBounds for RangeFrom { + type Output = Self; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + Self { + start: self.start.min(limit), + } + } +} +impl LimitExplicitBounds for range::RangeFrom { + type Output = Self; + #[inline] + fn limit_explicit_bounds(self, limit: usize) -> Self::Output { + Self { + start: self.start.min(limit), + } + } +} + +/// Adjusts a range/index to contain each item as though they were `n` steps apart (i.e. multiplies +/// the bounds by `n`). +pub trait WithStride { + type Output: Clone + + SliceIndex<[Word], Output = [Word]> + + RangeBounds + + LimitExplicitBounds + + WithStride; + fn with_stride(self, stride: u32) -> Self::Output; +} +impl WithStride for usize { + type Output = Range; + fn with_stride(self, stride: u32) -> Self::Output { + let start = self * stride as usize; + Range { + start, + end: start + stride as usize, + } + } +} +impl WithStride for RangeFull { + type Output = Self; + #[inline] + fn with_stride(self, _: u32) -> Self::Output { + self + } +} +impl WithStride for Range { + type Output = Self; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + Range { + start: self.start * stride as usize, + end: self.end * stride as usize, + } + } +} +impl WithStride for range::Range { + type Output = Self; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + range::Range { + start: self.start * stride as usize, + end: self.end * stride as usize, + } + } +} +impl WithStride for RangeInclusive { + type Output = Range; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + Range { + start: *self.start() * stride as usize, + end: (*self.end() + 1) * stride as usize, + } + } +} +impl WithStride for range::RangeInclusive { + type Output = range::Range; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + range::Range { + start: self.start * stride as usize, + end: (self.end + 1) * stride as usize, + } + } +} +impl WithStride for RangeFrom { + type Output = Self; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + RangeFrom { + start: self.start * stride as usize, + } + } +} +impl WithStride for range::RangeFrom { + type Output = Self; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + range::RangeFrom { + start: self.start * stride as usize, + } + } +} +impl WithStride for RangeTo { + type Output = Self; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + RangeTo { + end: self.end * stride as usize, + } + } +} +impl WithStride for RangeToInclusive { + type Output = RangeTo; + #[inline] + fn with_stride(self, stride: u32) -> Self::Output { + RangeTo { + end: (self.end + 1) * stride as usize, + } + } +} + +/// Splits a range/index into a range before `n`, and the number of steps after `n`. +pub trait SplitAt { + type Output: Clone + + SliceIndex<[Word], Output = [Word]> + + RangeBounds + + Len + + LimitExplicitBounds + + SubtractFromEdge + + WithStride; + fn split_at(self, idx: usize) -> (Self::Output, usize); +} +impl SplitAt for usize { + type Output = Range; + #[inline] + #[expect(clippy::range_plus_one)] + fn split_at(self, idx: usize) -> (Self::Output, usize) { + if self < idx { (self..self + 1, 0) } else { (idx..idx, 1) } + } +} +impl SplitAt for Range { + type Output = Range; + fn split_at(self, idx: usize) -> (Self::Output, usize) { + let [pre_start, post_start] = minmax(self.start, idx); + let [pre_end, post_end] = minmax(self.end, idx); + ( + Range { + start: pre_start, + end: pre_end, + }, + post_end - post_start, + ) + } +} +impl SplitAt for range::Range { + type Output = range::Range; + fn split_at(self, idx: usize) -> (Self::Output, usize) { + let [pre_start, post_start] = minmax(self.start, idx); + let [pre_end, post_end] = minmax(self.end, idx); + ( + range::Range { + start: pre_start, + end: pre_end, + }, + post_end - post_start, + ) + } +} +impl SplitAt for RangeInclusive { + type Output = Range; + fn split_at(self, idx: usize) -> (Self::Output, usize) { + let [pre_start, post_start] = minmax(*self.start(), idx); + let [pre_end, post_end] = minmax( + match self.end_bound() { + Bound::Unbounded => 0, + Bound::Excluded(&x) => x, + // will result in invalid or empty ranges on overflow. + Bound::Included(&x) => x + 1, + }, + idx, + ); + ( + Range { + start: pre_start, + end: pre_end, + }, + post_end - post_start, + ) + } +} +impl SplitAt for range::RangeInclusive { + type Output = range::Range; + fn split_at(self, idx: usize) -> (Self::Output, usize) { + let [pre_start, post_start] = minmax(self.start, idx); + let [pre_end, post_end] = minmax(self.end + 1, idx); + ( + range::Range { + start: pre_start, + end: pre_end, + }, + post_end - post_start, + ) + } +} +impl SplitAt for RangeTo { + type Output = RangeTo; + #[inline] + fn split_at(self, idx: usize) -> (Self::Output, usize) { + let [pre_end, post_end] = minmax(self.end, idx); + (RangeTo { end: pre_end }, post_end - idx) + } +} +impl SplitAt for RangeToInclusive { + type Output = RangeTo; + #[inline] + fn split_at(self, idx: usize) -> (Self::Output, usize) { + let [pre_end, post_end] = minmax(self.end + 1, idx); + (RangeTo { end: pre_end }, post_end - idx) + } +} + +#[test] +fn len() { + assert_eq!(Len::len(&0), 1); + assert_eq!(Len::len(&Range { start: 0, end: 0 }), 0); + assert_eq!(Len::len(&range::Range { start: 0, end: 0 }), 0); + assert_eq!(Len::len(&RangeTo { end: 0 }), 0); + + assert_eq!(Len::len(&Range { start: 0, end: 1 }), 1); + assert_eq!(Len::len(&range::Range { start: 0, end: 1 }), 1); + assert_eq!(Len::len(&RangeTo { end: 1 }), 1); + + assert_eq!( + Len::len(&Range { + start: 0, + end: usize::MAX + }), + usize::MAX + ); + assert_eq!( + Len::len(&range::Range { + start: 0, + end: usize::MAX + }), + usize::MAX + ); + assert_eq!(Len::len(&RangeTo { end: usize::MAX }), usize::MAX); +} + +#[test] +#[expect(clippy::too_many_lines)] +fn subtract_from_edge() { + assert_eq!( + Range { start: 0, end: 0 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + Range { start: 0, end: 0 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + Range { start: 0, end: 0 }.subtract_from_edge(Range { start: 1, end: 1 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 0, end: 1 }), + // `0..0`` would also be acceptable + Range { start: 1, end: 1 }, + ); + assert_eq!( + Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 1, end: 1 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + Range { start: 1, end: 1 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 1, end: 1 }, + ); + assert_eq!( + Range { start: 1, end: 1 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 1, end: 1 }, + ); + assert_eq!( + Range { start: 1, end: 1 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 1, end: 1 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 2 }), + Range { start: 2, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 2, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 3 }), + Range { start: 3, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 2, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 3, end: 3 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 3, end: 4 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 2, end: 3 }), + Range { start: 1, end: 2 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 2, end: 4 }), + Range { start: 1, end: 2 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 1, end: 4 }), + // `1..1` would alsop be acceptable + Range { start: 3, end: 3 }, + ); + assert_eq!( + Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 4 }), + // `1..1` would alsop be acceptable + Range { start: 3, end: 3 }, + ); + + assert_eq!( + range::Range { start: 0, end: 0 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 0, end: 0 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 0, end: 0 }.subtract_from_edge(Range { start: 1, end: 1 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + range::Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 0, end: 1 }), + // `0..0`` would also be acceptable + Range { start: 1, end: 1 }, + ); + assert_eq!( + range::Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 1, end: 1 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + range::Range { start: 0, end: 1 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + range::Range { start: 1, end: 1 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 1, end: 1 }, + ); + assert_eq!( + range::Range { start: 1, end: 1 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 1, end: 1 }, + ); + assert_eq!( + range::Range { start: 1, end: 1 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 1, end: 1 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 2 }), + Range { start: 2, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 2, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 3 }), + Range { start: 3, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 2, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 3, end: 3 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 3, end: 4 }), + Range { start: 1, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 2, end: 3 }), + Range { start: 1, end: 2 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 2, end: 4 }), + Range { start: 1, end: 2 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 1, end: 4 }), + // `1..1` would alsop be acceptable + Range { start: 3, end: 3 }, + ); + assert_eq!( + range::Range { start: 1, end: 3 }.subtract_from_edge(Range { start: 0, end: 4 }), + // `1..1` would alsop be acceptable + Range { start: 3, end: 3 }, + ); + + // RangeTo + assert_eq!( + RangeTo { end: 0 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + RangeTo { end: 0 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + RangeTo { end: 0 }.subtract_from_edge(Range { start: 1, end: 1 }), + Range { start: 0, end: 0 }, + ); + assert_eq!( + RangeTo { end: 1 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + RangeTo { end: 1 }.subtract_from_edge(Range { start: 0, end: 1 }), + // `0..0`` would also be acceptable + Range { start: 1, end: 1 }, + ); + assert_eq!( + RangeTo { end: 1 }.subtract_from_edge(Range { start: 1, end: 1 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + RangeTo { end: 1 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 0, end: 0 }), + Range { start: 0, end: 2 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 0, end: 1 }), + Range { start: 1, end: 2 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 0, end: 2 }), + Range { start: 2, end: 2 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 0, end: 3 }), + Range { start: 2, end: 2 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 2, end: 2 }), + Range { start: 0, end: 2 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 2, end: 3 }), + Range { start: 0, end: 2 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 1, end: 2 }), + Range { start: 0, end: 1 }, + ); + assert_eq!( + RangeTo { end: 2 }.subtract_from_edge(Range { start: 1, end: 3 }), + Range { start: 0, end: 1 }, + ); +} + +#[test] +fn limit_explicit_bounds() { + assert_eq!(0.limit_explicit_bounds(0), Range { start: 0, end: 0 }); + assert_eq!(0.limit_explicit_bounds(1), Range { start: 0, end: 1 }); + assert_eq!(1.limit_explicit_bounds(1), Range { start: 1, end: 1 }); + assert_eq!(5.limit_explicit_bounds(2), Range { start: 2, end: 2 }); + + assert_eq!( + Range { start: 0, end: 0 }.limit_explicit_bounds(0), + Range { start: 0, end: 0 }, + ); + assert_eq!( + Range { start: 0, end: 1 }.limit_explicit_bounds(0), + Range { start: 0, end: 0 }, + ); + assert_eq!( + Range { start: 2, end: 4 }.limit_explicit_bounds(0), + Range { start: 0, end: 0 }, + ); + assert_eq!( + Range { start: 1, end: 20 }.limit_explicit_bounds(5), + Range { start: 1, end: 5 }, + ); + + assert_eq!( + range::Range { start: 0, end: 0 }.limit_explicit_bounds(0), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 0, end: 1 }.limit_explicit_bounds(0), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 2, end: 4 }.limit_explicit_bounds(0), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 1, end: 20 }.limit_explicit_bounds(5), + range::Range { start: 1, end: 5 }, + ); + + assert_eq!( + RangeInclusive::new(0, 0).limit_explicit_bounds(0), + Range { start: 0, end: 0 }, + ); + assert_eq!( + RangeInclusive::new(0, 1).limit_explicit_bounds(0), + Range { start: 0, end: 0 }, + ); + assert_eq!( + RangeInclusive::new(2, 4).limit_explicit_bounds(0), + Range { start: 0, end: 0 }, + ); + assert_eq!( + RangeInclusive::new(1, 20).limit_explicit_bounds(5), + Range { start: 1, end: 5 }, + ); + + assert_eq!( + range::RangeInclusive { start: 0, end: 0 }.limit_explicit_bounds(0), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 1 }.limit_explicit_bounds(0), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::RangeInclusive { start: 2, end: 4 }.limit_explicit_bounds(0), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::RangeInclusive { start: 1, end: 20 }.limit_explicit_bounds(5), + range::Range { start: 1, end: 5 }, + ); + + assert_eq!(RangeTo { end: 0 }.limit_explicit_bounds(0), RangeTo { end: 0 },); + assert_eq!(RangeTo { end: 1 }.limit_explicit_bounds(0), RangeTo { end: 0 },); + assert_eq!(RangeTo { end: 20 }.limit_explicit_bounds(5), RangeTo { end: 5 },); + + assert_eq!(RangeToInclusive { end: 0 }.limit_explicit_bounds(0), RangeTo { end: 0 },); + assert_eq!(RangeToInclusive { end: 1 }.limit_explicit_bounds(0), RangeTo { end: 0 },); + assert_eq!( + RangeToInclusive { end: 20 }.limit_explicit_bounds(5), + RangeTo { end: 5 }, + ); + + assert_eq!(RangeFrom { start: 0 }.limit_explicit_bounds(0), RangeFrom { start: 0 },); + assert_eq!(RangeFrom { start: 1 }.limit_explicit_bounds(0), RangeFrom { start: 0 },); + assert_eq!(RangeFrom { start: 20 }.limit_explicit_bounds(5), RangeFrom { start: 5 },); + + assert_eq!( + range::RangeFrom { start: 0 }.limit_explicit_bounds(0), + range::RangeFrom { start: 0 }, + ); + assert_eq!( + range::RangeFrom { start: 1 }.limit_explicit_bounds(0), + range::RangeFrom { start: 0 }, + ); + assert_eq!( + range::RangeFrom { start: 20 }.limit_explicit_bounds(5), + range::RangeFrom { start: 5 }, + ); +} + +#[test] +#[expect(clippy::too_many_lines)] +fn with_stride() { + assert_eq!(0.with_stride(1), Range { start: 0, end: 1 }); + assert_eq!(0.with_stride(2), Range { start: 0, end: 2 }); + assert_eq!(1.with_stride(1), Range { start: 1, end: 2 }); + assert_eq!(1.with_stride(2), Range { start: 2, end: 4 }); + assert_eq!(2.with_stride(4), Range { start: 8, end: 12 }); + + assert_eq!(Range { start: 0, end: 0 }.with_stride(1), Range { start: 0, end: 0 },); + assert_eq!(Range { start: 0, end: 1 }.with_stride(1), Range { start: 0, end: 1 },); + assert_eq!(Range { start: 2, end: 6 }.with_stride(1), Range { start: 2, end: 6 },); + assert_eq!(Range { start: 0, end: 0 }.with_stride(2), Range { start: 0, end: 0 },); + assert_eq!(Range { start: 0, end: 2 }.with_stride(2), Range { start: 0, end: 4 },); + assert_eq!(Range { start: 4, end: 10 }.with_stride(5), Range { start: 20, end: 50 },); + + assert_eq!( + range::Range { start: 0, end: 0 }.with_stride(1), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 0, end: 1 }.with_stride(1), + range::Range { start: 0, end: 1 }, + ); + assert_eq!( + range::Range { start: 2, end: 6 }.with_stride(1), + range::Range { start: 2, end: 6 }, + ); + assert_eq!( + range::Range { start: 0, end: 0 }.with_stride(2), + range::Range { start: 0, end: 0 }, + ); + assert_eq!( + range::Range { start: 0, end: 2 }.with_stride(2), + range::Range { start: 0, end: 4 }, + ); + assert_eq!( + range::Range { start: 4, end: 10 }.with_stride(5), + range::Range { start: 20, end: 50 }, + ); + + assert_eq!(RangeInclusive::new(0, 0).with_stride(1), Range { start: 0, end: 1 },); + assert_eq!(RangeInclusive::new(0, 1).with_stride(1), Range { start: 0, end: 2 },); + assert_eq!(RangeInclusive::new(2, 6).with_stride(1), Range { start: 2, end: 7 },); + assert_eq!(RangeInclusive::new(0, 0).with_stride(2), Range { start: 0, end: 2 },); + assert_eq!(RangeInclusive::new(0, 2).with_stride(2), Range { start: 0, end: 6 },); + assert_eq!(RangeInclusive::new(4, 10).with_stride(5), Range { start: 20, end: 55 },); + + assert_eq!( + range::RangeInclusive { start: 0, end: 0 }.with_stride(1), + range::Range { start: 0, end: 1 }, + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 1 }.with_stride(1), + range::Range { start: 0, end: 2 }, + ); + assert_eq!( + range::RangeInclusive { start: 2, end: 6 }.with_stride(1), + range::Range { start: 2, end: 7 }, + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 0 }.with_stride(2), + range::Range { start: 0, end: 2 }, + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 2 }.with_stride(2), + range::Range { start: 0, end: 6 }, + ); + assert_eq!( + range::RangeInclusive { start: 4, end: 10 }.with_stride(5), + range::Range { start: 20, end: 55 }, + ); + + assert_eq!(RangeTo { end: 0 }.with_stride(1), RangeTo { end: 0 },); + assert_eq!(RangeTo { end: 1 }.with_stride(1), RangeTo { end: 1 },); + assert_eq!(RangeTo { end: 6 }.with_stride(1), RangeTo { end: 6 },); + assert_eq!(RangeTo { end: 0 }.with_stride(2), RangeTo { end: 0 },); + assert_eq!(RangeTo { end: 2 }.with_stride(2), RangeTo { end: 4 },); + assert_eq!(RangeTo { end: 10 }.with_stride(5), RangeTo { end: 50 },); + + assert_eq!(RangeToInclusive { end: 0 }.with_stride(1), RangeTo { end: 1 },); + assert_eq!(RangeToInclusive { end: 1 }.with_stride(1), RangeTo { end: 2 },); + assert_eq!(RangeToInclusive { end: 6 }.with_stride(1), RangeTo { end: 7 },); + assert_eq!(RangeToInclusive { end: 0 }.with_stride(2), RangeTo { end: 2 },); + assert_eq!(RangeToInclusive { end: 2 }.with_stride(2), RangeTo { end: 6 },); + assert_eq!(RangeToInclusive { end: 10 }.with_stride(5), RangeTo { end: 55 },); + + assert_eq!(RangeFrom { start: 0 }.with_stride(1), RangeFrom { start: 0 },); + assert_eq!(RangeFrom { start: 1 }.with_stride(1), RangeFrom { start: 1 },); + assert_eq!(RangeFrom { start: 6 }.with_stride(1), RangeFrom { start: 6 },); + assert_eq!(RangeFrom { start: 0 }.with_stride(2), RangeFrom { start: 0 },); + assert_eq!(RangeFrom { start: 2 }.with_stride(2), RangeFrom { start: 4 },); + assert_eq!(RangeFrom { start: 10 }.with_stride(5), RangeFrom { start: 50 },); + + assert_eq!( + range::RangeFrom { start: 0 }.with_stride(1), + range::RangeFrom { start: 0 }, + ); + assert_eq!( + range::RangeFrom { start: 1 }.with_stride(1), + range::RangeFrom { start: 1 }, + ); + assert_eq!( + range::RangeFrom { start: 6 }.with_stride(1), + range::RangeFrom { start: 6 }, + ); + assert_eq!( + range::RangeFrom { start: 0 }.with_stride(2), + range::RangeFrom { start: 0 }, + ); + assert_eq!( + range::RangeFrom { start: 2 }.with_stride(2), + range::RangeFrom { start: 4 }, + ); + assert_eq!( + range::RangeFrom { start: 10 }.with_stride(5), + range::RangeFrom { start: 50 }, + ); +} + +#[test] +#[expect(clippy::too_many_lines)] +fn split_at() { + assert_eq!(0.split_at(0), (Range { start: 0, end: 0 }, 1)); + assert_eq!(0.split_at(1), (Range { start: 0, end: 1 }, 0)); + assert_eq!(1.split_at(0), (Range { start: 0, end: 0 }, 1)); + assert_eq!(1.split_at(1), (Range { start: 1, end: 1 }, 1)); + assert_eq!(5.split_at(20), (Range { start: 5, end: 6 }, 0)); + + assert_eq!(Range { start: 0, end: 0 }.split_at(0), (Range { start: 0, end: 0 }, 0),); + assert_eq!(Range { start: 0, end: 1 }.split_at(0), (Range { start: 0, end: 0 }, 1),); + assert_eq!(Range { start: 0, end: 0 }.split_at(1), (Range { start: 0, end: 0 }, 0),); + assert_eq!(Range { start: 0, end: 5 }.split_at(1), (Range { start: 0, end: 1 }, 4),); + assert_eq!(Range { start: 1, end: 1 }.split_at(0), (Range { start: 0, end: 0 }, 0),); + assert_eq!(Range { start: 1, end: 2 }.split_at(0), (Range { start: 0, end: 0 }, 1),); + assert_eq!(Range { start: 1, end: 1 }.split_at(1), (Range { start: 1, end: 1 }, 0),); + assert_eq!(Range { start: 1, end: 5 }.split_at(2), (Range { start: 1, end: 2 }, 3),); + assert_eq!( + Range { start: 20, end: 200 }.split_at(55), + (Range { start: 20, end: 55 }, 145), + ); + + assert_eq!( + range::Range { start: 0, end: 0 }.split_at(0), + (range::Range { start: 0, end: 0 }, 0), + ); + assert_eq!( + range::Range { start: 0, end: 1 }.split_at(0), + (range::Range { start: 0, end: 0 }, 1), + ); + assert_eq!( + range::Range { start: 0, end: 0 }.split_at(1), + (range::Range { start: 0, end: 0 }, 0), + ); + assert_eq!( + range::Range { start: 0, end: 5 }.split_at(1), + (range::Range { start: 0, end: 1 }, 4), + ); + assert_eq!( + range::Range { start: 1, end: 1 }.split_at(0), + (range::Range { start: 0, end: 0 }, 0), + ); + assert_eq!( + range::Range { start: 1, end: 2 }.split_at(0), + (range::Range { start: 0, end: 0 }, 1), + ); + assert_eq!( + range::Range { start: 1, end: 1 }.split_at(1), + (range::Range { start: 1, end: 1 }, 0), + ); + assert_eq!( + range::Range { start: 1, end: 5 }.split_at(2), + (range::Range { start: 1, end: 2 }, 3), + ); + assert_eq!( + range::Range { start: 20, end: 200 }.split_at(55), + (range::Range { start: 20, end: 55 }, 145), + ); + + assert_eq!(RangeInclusive::new(0, 0).split_at(0), (Range { start: 0, end: 0 }, 1),); + assert_eq!(RangeInclusive::new(0, 1).split_at(0), (Range { start: 0, end: 0 }, 2),); + assert_eq!(RangeInclusive::new(0, 0).split_at(1), (Range { start: 0, end: 1 }, 0),); + assert_eq!(RangeInclusive::new(0, 5).split_at(1), (Range { start: 0, end: 1 }, 5),); + assert_eq!(RangeInclusive::new(1, 1).split_at(0), (Range { start: 0, end: 0 }, 1),); + assert_eq!(RangeInclusive::new(1, 2).split_at(0), (Range { start: 0, end: 0 }, 2),); + assert_eq!(RangeInclusive::new(1, 1).split_at(1), (Range { start: 1, end: 1 }, 1),); + assert_eq!(RangeInclusive::new(1, 5).split_at(2), (Range { start: 1, end: 2 }, 4),); + assert_eq!( + RangeInclusive::new(20, 200).split_at(55), + (Range { start: 20, end: 55 }, 146), + ); + + assert_eq!( + range::RangeInclusive { start: 0, end: 0 }.split_at(0), + (range::Range { start: 0, end: 0 }, 1), + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 1 }.split_at(0), + (range::Range { start: 0, end: 0 }, 2), + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 0 }.split_at(1), + (range::Range { start: 0, end: 1 }, 0), + ); + assert_eq!( + range::RangeInclusive { start: 0, end: 5 }.split_at(1), + (range::Range { start: 0, end: 1 }, 5), + ); + assert_eq!( + range::RangeInclusive { start: 1, end: 1 }.split_at(0), + (range::Range { start: 0, end: 0 }, 1), + ); + assert_eq!( + range::RangeInclusive { start: 1, end: 2 }.split_at(0), + (range::Range { start: 0, end: 0 }, 2), + ); + assert_eq!( + range::RangeInclusive { start: 1, end: 1 }.split_at(1), + (range::Range { start: 1, end: 1 }, 1), + ); + assert_eq!( + range::RangeInclusive { start: 1, end: 5 }.split_at(2), + (range::Range { start: 1, end: 2 }, 4), + ); + assert_eq!( + range::RangeInclusive { start: 20, end: 200 }.split_at(55), + (range::Range { start: 20, end: 55 }, 146), + ); +} diff --git a/clippy_data_structures/src/slice_set.rs b/clippy_data_structures/src/slice_set.rs new file mode 100644 index 000000000000..5636b7735ba3 --- /dev/null +++ b/clippy_data_structures/src/slice_set.rs @@ -0,0 +1,195 @@ +use crate::sorted; +use crate::traits::SortedIndex; +use core::borrow::Borrow; +use core::mem::{MaybeUninit, transmute}; +use core::ops::Deref; +use core::{iter, slice}; +use rustc_arena::DroplessArena; + +/// A wrapper around a slice where all items are unique and sorted. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct SliceSet { + data: [T], +} +impl SliceSet { + /// Gets an empty set. + #[inline] + #[must_use] + pub const fn empty<'a>() -> &'a Self { + Self::from_sorted_unchecked(&[]) + } + + /// Interprets the reference as a set containing a single item. + #[inline] + #[must_use] + pub const fn from_ref(value: &T) -> &Self { + Self::from_sorted_unchecked(slice::from_ref(value)) + } + + /// Same as `from_sorted`, but without debug assertions. + #[inline] + pub(crate) const fn from_sorted_unchecked(slice: &[T]) -> &Self { + // SAFETY: `SliceSet`` is a transparent wrapper around `T`. + unsafe { transmute::<&[T], &SliceSet>(slice) } + } + + /// Gets the current set as a regular slice. + #[inline] + #[must_use] + pub const fn as_raw_slice(&self) -> &[T] { + &self.data + } + + /// Checks if the set contains the given value. + #[inline] + #[must_use] + pub fn contains(&self, item: &Q) -> bool + where + T: Borrow, + Q: Ord + ?Sized, + { + self.data.binary_search_by(|x| x.borrow().cmp(item)).is_ok() + } + + /// Gets the specified item from the set. Returns `None` if it doesn't exist. + #[inline] + #[must_use] + pub fn get(&self, item: &Q) -> Option<&T> + where + T: Borrow, + Q: Ord + ?Sized, + { + self.data + .binary_search_by(|x| x.borrow().cmp(item)) + .ok() + .map(|i| &self.data[i]) + } + + /// Gets the index of the specified item in the set. Returns `None` if it doesn't exist. + #[inline] + #[must_use] + pub fn get_index(&self, item: &Q) -> Option + where + T: Borrow, + Q: Ord + ?Sized, + { + self.data.binary_search_by(|x| x.borrow().cmp(item)).ok() + } + + /// Gets a subset of the current set. + #[inline] + #[must_use] + pub fn get_range(&self, range: impl SortedIndex) -> &Self + where + T: Borrow, + Q: Ord + ?Sized, + { + Self::from_sorted_unchecked( + &self.data[range.find_range(&self.data, |slice, target| { + slice.binary_search_by(|x| x.borrow().cmp(target)) + })], + ) + } +} +impl SliceSet { + /// Assumes the given slice is sorted with no duplicates. + /// + /// Will panic with debug assertions enabled if the given slice is unsorted or contains + /// duplicates. + #[inline] + #[must_use] + pub fn from_sorted(slice: &[T]) -> &Self { + debug_assert!(sorted::is_slice_set(slice)); + Self::from_sorted_unchecked(slice) + } + + /// Sorts the given slice and assumes no duplicates. + /// + /// Will panic with debug assertions enabled if the given slice contains duplicates. + #[inline] + #[must_use] + pub fn from_unsorted_slice(slice: &mut [T]) -> &Self { + slice.sort_unstable(); + Self::from_sorted(slice) + } + + /// Sorts and partitions out duplicates from the given slice. + #[inline] + #[must_use] + pub fn from_unsorted_slice_dedup(slice: &mut [T]) -> &Self { + slice.sort_unstable(); + Self::from_sorted_unchecked(slice.partition_dedup().0) + } + + /// Checks if this set is a subset of another. + #[inline] + #[must_use] + pub fn is_subset_of(&self, other: &Self) -> bool { + if self.len() > other.len() { + return false; + } + if sorted::should_binary_search(other.len(), self.len()) { + sorted::is_subset_of_binary(self, other) + } else { + sorted::is_subset_of_linear(self, other) + } + } + + /// Checks if this set is a superset of another. + #[inline] + #[must_use] + pub fn is_superset_of(&self, other: &Self) -> bool { + other.is_subset_of(self) + } +} +impl SliceSet { + /// Creates a new set allocated into an arena which is the union of two sorted lists. + /// + /// # Panics + /// Panics if either iterator returns more than their `len` functions indicate. + #[inline] + #[must_use] + pub fn from_sorted_union_into_arena( + arena: &DroplessArena, + xs: impl IntoIterator, + ys: impl IntoIterator, + ) -> &Self { + let xs = xs.into_iter(); + let ys = ys.into_iter(); + let len = xs.len().checked_add(ys.len()).unwrap(); + if len == 0 { + Self::empty() + } else { + Self::from_sorted(sorted::union_fill_uninit( + arena.alloc_from_iter(iter::repeat_with(|| MaybeUninit::uninit()).take(len)), + xs, + ys, + Ord::cmp, + )) + } + } +} + +impl Deref for SliceSet { + type Target = [T]; + #[inline] + fn deref(&self) -> &Self::Target { + &self.data + } +} +impl Borrow<[T]> for SliceSet { + #[inline] + fn borrow(&self) -> &[T] { + &self.data + } +} + +impl<'a, T> IntoIterator for &'a SliceSet { + type Item = &'a T; + type IntoIter = slice::Iter<'a, T>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.data.iter() + } +} diff --git a/clippy_data_structures/src/sorted.rs b/clippy_data_structures/src/sorted.rs new file mode 100644 index 000000000000..c5166e02fb3d --- /dev/null +++ b/clippy_data_structures/src/sorted.rs @@ -0,0 +1,106 @@ +use core::cmp::Ordering; +use core::mem::MaybeUninit; + +/// Determines whether a binary or linear search should be used when searching for `search_count` +/// sorted items in a sorted list of the given size. +#[inline] +pub fn should_binary_search(list_size: usize, search_count: usize) -> bool { + // Using binary search has a complexity of `O(log2(list_size) * search_count)` with an average + // case only slightly better. This roughly calculates if the binary search will be faster, + // erring on the side of a linear search. + + // This is essentially `search_count < list_size / list_size.ilog2().next_power_of_two() / 2`, + // but with better codegen. + let log2 = (usize::BITS - 1).wrapping_sub(list_size.leading_zeros()); + // If `log2` is `MAX` then `list_size` is zero. Shifting by the maximum amount is fine. + // If `log2` is 64 then `list_size` is one. Shifting by zero is fine. + // In all other cases `log2` will be in the `0..BITS` range. + search_count < list_size.wrapping_shr(usize::BITS - log2.leading_zeros()) +} + +/// Merges the two sorted lists into `dst`, discarding any duplicates between the two. +/// +/// # Panics +/// Panics if `dst` is too small to contain the merged list. +pub fn union_fill_uninit( + dst: &mut [MaybeUninit], + mut xs: impl Iterator, + mut ys: impl Iterator, + mut cmp: impl FnMut(&T, &T) -> Ordering, +) -> &mut [T] { + // n.b. `dst_iter` must be moved exactly once for each item written. + let mut dst_iter = dst.iter_mut(); + let mut next_x = xs.next(); + let mut next_y = ys.next(); + loop { + match (next_x, next_y) { + (Some(x), Some(y)) => match cmp(&x, &y) { + Ordering::Equal => { + dst_iter.next().unwrap().write(x); + next_x = xs.next(); + next_y = ys.next(); + }, + Ordering::Less => { + dst_iter.next().unwrap().write(x); + next_x = xs.next(); + next_y = Some(y); + }, + Ordering::Greater => { + dst_iter.next().unwrap().write(y); + next_x = Some(x); + next_y = ys.next(); + }, + }, + (Some(x), None) => { + dst_iter.next().unwrap().write(x); + xs.for_each(|x| { + dst_iter.next().unwrap().write(x); + }); + break; + }, + (None, Some(y)) => { + dst_iter.next().unwrap().write(y); + ys.for_each(|y| { + dst_iter.next().unwrap().write(y); + }); + break; + }, + (None, None) => break, + } + } + + let remain = dst_iter.into_slice().len(); + let end = dst.len() - remain; + // Safety: Every item returned by `dst_iter` was written to. + unsafe { dst[..end].assume_init_mut() } +} + +pub fn is_subset_of_linear(xs: &[T], ys: &[T]) -> bool { + let mut ys = ys.iter(); + 'outer: for x in xs { + for y in &mut ys { + match x.cmp(y) { + Ordering::Equal => continue 'outer, + Ordering::Less => return false, + Ordering::Greater => {}, + } + } + return false; + } + true +} + +pub fn is_subset_of_binary(xs: &[T], mut ys: &[T]) -> bool { + for x in xs { + match ys.binary_search(x) { + Ok(i) => ys = &ys[i + 1..], + Err(_) => return false, + } + } + true +} + +/// Checks is a slice is ordered with no duplicates. +pub fn is_slice_set(slice: &[T]) -> bool { + slice.array_windows::<2>().all(|[x, y]| x.cmp(y).is_lt()) +} diff --git a/clippy_data_structures/src/traits.rs b/clippy_data_structures/src/traits.rs new file mode 100644 index 000000000000..140ffc07e217 --- /dev/null +++ b/clippy_data_structures/src/traits.rs @@ -0,0 +1,92 @@ +use core::ops::{Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; +use core::range; +use core::slice::SliceIndex; + +/// A helper trait for getting a range of items from a sorted slice. +pub trait SortedIndex { + type Result: SliceIndex<[T], Output = [T]> + RangeBounds; + fn find_range(self, slice: &[T], find: impl FnMut(&[T], &Q) -> Result) -> Self::Result; +} +impl SortedIndex for RangeFull { + type Result = RangeFull; + fn find_range(self, _: &[T], _: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + self + } +} +impl SortedIndex for Range<&Q> { + type Result = Range; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(start) | Err(start)) = find(slice, self.start); + let (Ok(end) | Err(end)) = find(slice, self.end); + Range { start, end } + } +} +impl SortedIndex for range::Range<&Q> { + type Result = range::Range; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(start) | Err(start)) = find(slice, self.start); + let (Ok(end) | Err(end)) = find(slice, self.end); + range::Range { start, end } + } +} +impl SortedIndex for RangeInclusive<&Q> { + type Result = RangeInclusive; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(start) | Err(start)) = find(slice, *self.start()); + let end = match find(slice, *self.end()) { + Ok(i) => i + 1, + Err(i) => i, + }; + RangeInclusive::new(start, end) + } +} +impl SortedIndex for range::RangeInclusive<&Q> { + type Result = range::RangeInclusive; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(start) | Err(start)) = find(slice, self.start); + let end = match find(slice, self.end) { + Ok(i) => i + 1, + Err(i) => i, + }; + range::RangeInclusive { start, end } + } +} +impl SortedIndex for RangeFrom<&Q> { + type Result = RangeFrom; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(start) | Err(start)) = find(slice, self.start); + RangeFrom { start } + } +} +impl SortedIndex for range::RangeFrom<&Q> { + type Result = range::RangeFrom; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(start) | Err(start)) = find(slice, self.start); + range::RangeFrom { start } + } +} +impl SortedIndex for RangeTo<&Q> { + type Result = RangeTo; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let (Ok(end) | Err(end)) = find(slice, self.end); + RangeTo { end } + } +} +impl SortedIndex for RangeToInclusive<&Q> { + type Result = RangeToInclusive; + #[inline] + fn find_range(self, slice: &[T], mut find: impl FnMut(&[T], &Q) -> Result) -> Self::Result { + let end = match find(slice, self.end) { + Ok(i) => i + 1, + Err(i) => i, + }; + RangeToInclusive { end } + } +} diff --git a/clippy_data_structures/tests/bit_slice.rs b/clippy_data_structures/tests/bit_slice.rs new file mode 100644 index 000000000000..ffa4ea686eca --- /dev/null +++ b/clippy_data_structures/tests/bit_slice.rs @@ -0,0 +1,120 @@ +#![feature(rustc_private)] +#![allow( + clippy::cast_possible_truncation, + clippy::unreadable_literal, + clippy::range_minus_one +)] + +use clippy_data_structures::bit_slice::{BitSlice, WORD_BITS}; + +#[test] +fn union_intersect_subtract() { + let mut x = [0b10101010, 0b10101010]; + let mut y = [0b01010101, 0b11110000]; + let x = BitSlice::::from_words_mut(&mut x); + let y = BitSlice::::from_words_mut(&mut y); + + assert!(x.union_trunc(y)); + assert_eq!(&x.words, &[0b11111111, 0b11111010]); + assert!(!x.union_trunc(y)); + assert_eq!(&x.words, &[0b11111111, 0b11111010]); + assert!(x.subtract(y)); + assert_eq!(&x.words, &[0b10101010, 0b00001010]); + assert!(!x.subtract(y)); + assert_eq!(&x.words, &[0b10101010, 0b00001010]); + + assert!(x.union_trunc(y)); + assert_eq!(&x.words, &[0b11111111, 0b11111010]); + assert!(x.intersect(y)); + assert_eq!(&x.words, &[0b01010101, 0b11110000]); + assert!(!x.intersect(y)); + assert_eq!(&x.words, &[0b01010101, 0b11110000]); + + x.clear(); + assert!(x.is_empty()); + assert!(!x.subtract(y)); + assert!(!x.intersect(y)); + assert!(x.union_trunc(y)); + assert!(x.words == y.words); +} + +#[test] +fn insert_range() { + let mut x = [0, 0, 0]; + let x = BitSlice::::from_words_mut(&mut x); + + x.insert_range(0..WORD_BITS); + assert_eq!(&x.words, &[!0, 0, 0]); + + x.insert_range(1..=WORD_BITS); + assert_eq!(&x.words, &[!0, 1, 0]); + + x.insert_range(..WORD_BITS + 3); + assert_eq!(&x.words, &[!0, 0b111, 0]); + + x.insert_range(..=WORD_BITS * 2 - 1); + assert_eq!(&x.words, &[!0, !0, 0]); + + x.insert_range(WORD_BITS * 2 + 1..); + assert_eq!(&x.words, &[!0, !0, !1]); + + x.clear(); + x.insert_range(WORD_BITS / 2..WORD_BITS * 2 + WORD_BITS / 2); + assert_eq!(&x.words, &[!0 << (WORD_BITS / 2), !0, !0 >> (WORD_BITS / 2)]); + + x.clear(); + x.insert_range(0..0); + assert_eq!(&x.words, &[0, 0, 0]); + + x.insert_range(1..WORD_BITS - 1); + assert_eq!(&x.words, &[!1 & (!0 >> 1), 0, 0]); +} + +#[test] +fn iter_insert_remove_contains() { + let mut x = [0, 0, 0]; + let x = BitSlice::::from_words_mut(&mut x); + + assert!(x.iter().eq::<[usize; 0]>([])); + + assert!(!x.contains(1)); + assert!(x.insert(1)); + assert!(x.contains(1)); + assert!(x.iter().eq([1])); + + assert!(!x.contains(2)); + assert!(x.insert(2)); + assert!(x.contains(2)); + assert!(x.iter().eq([1, 2])); + + assert!(!x.contains(0)); + assert!(x.insert(0)); + assert!(x.contains(0)); + assert!(x.iter().eq([0, 1, 2])); + + assert!(!x.contains(WORD_BITS)); + assert!(x.insert(WORD_BITS)); + assert!(x.contains(WORD_BITS)); + assert!(x.iter().eq([0, 1, 2, WORD_BITS])); + + assert!(!x.contains(WORD_BITS * 2 + 1)); + assert!(x.insert(WORD_BITS * 2 + 1)); + assert!(x.contains(WORD_BITS * 2 + 1)); + assert!(x.iter().eq([0, 1, 2, WORD_BITS, WORD_BITS * 2 + 1])); + + assert!(!x.insert(0)); + assert!(x.iter().eq([0, 1, 2, WORD_BITS, WORD_BITS * 2 + 1])); + + assert!(x.remove(0)); + assert!(!x.contains(0)); + assert!(x.iter().eq([1, 2, WORD_BITS, WORD_BITS * 2 + 1])); + + assert!(!x.remove(0)); + assert!(x.iter().eq([1, 2, WORD_BITS, WORD_BITS * 2 + 1])); + + assert!(!x.contains(WORD_BITS * 2)); + assert!(x.insert(WORD_BITS * 2)); + assert!(x.contains(WORD_BITS * 2)); + assert!(x.drain().eq([1, 2, WORD_BITS, WORD_BITS * 2, WORD_BITS * 2 + 1])); + assert!(x.is_empty()); +} diff --git a/clippy_data_structures/tests/bit_slice_2d.rs b/clippy_data_structures/tests/bit_slice_2d.rs new file mode 100644 index 000000000000..a827285d1f68 --- /dev/null +++ b/clippy_data_structures/tests/bit_slice_2d.rs @@ -0,0 +1,221 @@ +#![feature(rustc_private)] +#![allow(clippy::too_many_lines, clippy::cast_possible_truncation)] + +use clippy_data_structures::BitSlice2d; +use clippy_data_structures::bit_slice::WORD_BITS; + +#[test] +#[rustfmt::skip] +fn row_iter_copy_move_3x3() { + let mut x = [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + ]; + let mut x = BitSlice2d::<'_, usize, usize>::from_mut_words(&mut x, 3, 3 * WORD_BITS as u32); + + assert!(x.iter_rows(..).map(|x| &x.words).eq([ + [0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101].as_slice(), + ])); + assert!(x.iter_rows(0).map(|x| &x.words).eq([ + [0, 0, 0].as_slice(), + ])); + assert!(x.iter_rows(1).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(..2).map(|x| &x.words).eq([ + [0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(1..).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101].as_slice(), + ])); + assert!(x.iter_rows(1..2).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010].as_slice(), + ])); + + x.copy_rows(0..1, 2); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ], + ); + x.copy_rows(1, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ],); + x.copy_rows(1.., 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + ],); + + x.move_rows(0, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ],); + x.move_rows(1..3, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + ],); + x.move_rows(..2, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ], + ); +} + +#[test] +#[rustfmt::skip] +fn row_iter_copy_move_4x5() { + let mut x = [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + ]; + let mut x = BitSlice2d::<'_, usize, usize>::from_mut_words(&mut x, 5, 4 * WORD_BITS as u32 - 1); + + assert!(x.iter_rows(..).map(|x| &x.words).eq([ + [0, 0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101, 0b0101].as_slice(), + [0b1111, 0b1111, 0b1111, 0b1111].as_slice(), + [0b1001, 0b1001, 0b1001, 0b1001].as_slice(), + ])); + assert!(x.iter_rows(0).map(|x| &x.words).eq([ + [0, 0, 0, 0].as_slice(), + ])); + assert!(x.iter_rows(1).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(..2).map(|x| &x.words).eq([ + [0, 0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(1..).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101, 0b0101].as_slice(), + [0b1111, 0b1111, 0b1111, 0b1111].as_slice(), + [0b1001, 0b1001, 0b1001, 0b1001].as_slice(), + ])); + assert!(x.iter_rows(1..2).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + ])); + + x.copy_rows(0..1, 2); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0, 0, 0, 0, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.copy_rows(1, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0, 0, 0, 0, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.copy_rows(1.., 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0, 0, 0, 0, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + + x.move_rows(0, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(1..3, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0, 0, 0, 0, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(..2, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(1..4, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0, 0, 0, 0, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(2..=4, 1); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1001, 0b1001, 0b1001, 0b1001, + 0, 0, 0, 0, + 0b1001, 0b1001, 0b1001, 0b1001, + 0, 0, 0, 0, + ] + ); +} diff --git a/clippy_data_structures/tests/growable_bit_set_2d.rs b/clippy_data_structures/tests/growable_bit_set_2d.rs new file mode 100644 index 000000000000..dab657252829 --- /dev/null +++ b/clippy_data_structures/tests/growable_bit_set_2d.rs @@ -0,0 +1,490 @@ +#![feature(rustc_private)] +#![allow(clippy::too_many_lines, clippy::cast_possible_truncation)] + +use clippy_data_structures::GrowableBitSet2d; +use clippy_data_structures::bit_slice::WORD_BITS; + +#[test] +#[rustfmt::skip] +fn row_iter_copy_move_3x3() { + let mut x = GrowableBitSet2d::::new(3 * WORD_BITS as u32); + x.ensure_row(0).words.copy_from_slice(&[0, 0, 0]); + x.ensure_row(1).words.copy_from_slice(&[0b1010, 0b1010, 0b1010]); + x.ensure_row(2).words.copy_from_slice(&[0b0101, 0b0101, 0b0101]); + + assert!(x.iter_rows(..).map(|x| &x.words).eq([ + [0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101].as_slice(), + ])); + assert!(x.iter_rows(0).map(|x| &x.words).eq([ + [0, 0, 0].as_slice(), + ])); + assert!(x.iter_rows(1).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(..2).map(|x| &x.words).eq([ + [0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(1..).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101].as_slice(), + ])); + assert!(x.iter_rows(1..2).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010].as_slice(), + ])); + + x.copy_rows(0..1, 2); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ], + ); + x.copy_rows(1, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ],); + x.copy_rows(1..3, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + ],); + + x.move_rows(0, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ],); + x.move_rows(1..3, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + ],); + x.move_rows(..2, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + ], + ); +} + +#[test] +#[rustfmt::skip] +fn row_iter_copy_move_4x5() { + let mut x = GrowableBitSet2d::::new(4 * WORD_BITS as u32 - 1); + x.ensure_row(0).words.copy_from_slice(&[0, 0, 0, 0]); + x.ensure_row(1).words.copy_from_slice(&[0b1010, 0b1010, 0b1010, 0b1010]); + x.ensure_row(2).words.copy_from_slice(&[0b0101, 0b0101, 0b0101, 0b0101]); + x.ensure_row(3).words.copy_from_slice(&[0b1111, 0b1111, 0b1111, 0b1111]); + x.ensure_row(4).words.copy_from_slice(&[0b1001, 0b1001, 0b1001, 0b1001]); + + assert!(x.iter_rows(..).map(|x| &x.words).eq([ + [0, 0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101, 0b0101].as_slice(), + [0b1111, 0b1111, 0b1111, 0b1111].as_slice(), + [0b1001, 0b1001, 0b1001, 0b1001].as_slice(), + ])); + assert!(x.iter_rows(0).map(|x| &x.words).eq([ + [0, 0, 0, 0].as_slice(), + ])); + assert!(x.iter_rows(1).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(..2).map(|x| &x.words).eq([ + [0, 0, 0, 0].as_slice(), + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + ])); + assert!(x.iter_rows(1..).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + [0b0101, 0b0101, 0b0101, 0b0101].as_slice(), + [0b1111, 0b1111, 0b1111, 0b1111].as_slice(), + [0b1001, 0b1001, 0b1001, 0b1001].as_slice(), + ])); + assert!(x.iter_rows(1..2).map(|x| &x.words).eq([ + [0b1010, 0b1010, 0b1010, 0b1010].as_slice(), + ])); + + x.copy_rows(0..1, 2); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0, 0, 0, 0, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.copy_rows(1, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0, 0, 0, 0, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.copy_rows(1..5, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0, 0, 0, 0, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + + x.move_rows(0, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(1..3, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0, 0, 0, 0, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(..2, 1); + assert_eq!( + x.words(), + [ + 0, 0, 0, 0, + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(1..4, 0); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1111, 0b1111, 0b1111, 0b1111, + 0b1001, 0b1001, 0b1001, 0b1001, + 0, 0, 0, 0, + 0b1001, 0b1001, 0b1001, 0b1001, + ] + ); + x.move_rows(2..=4, 1); + assert_eq!( + x.words(), + [ + 0b1010, 0b1010, 0b1010, 0b1010, + 0b1001, 0b1001, 0b1001, 0b1001, + 0, 0, 0, 0, + 0b1001, 0b1001, 0b1001, 0b1001, + 0, 0, 0, 0, + ] + ); +} + +#[test] +#[rustfmt::skip] +fn row_copy_oob() { + let mut x = GrowableBitSet2d::::new(3 * WORD_BITS as u32 - 2); + x.ensure_row(0).words.copy_from_slice(&[0, 0, 0]); + x.ensure_row(1).words.copy_from_slice(&[0b1010, 0b1010, 0b1010]); + x.ensure_row(2).words.copy_from_slice(&[0b0101, 0b0101, 0b0101]); + x.ensure_row(3).words.copy_from_slice(&[0b1111, 0b1111, 0b1111]); + + x.copy_rows(0, 4); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + ] + ); + x.copy_rows(1, 5); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + ] + ); + x.copy_rows(..3, 7); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + ] + ); + x.copy_rows(1..3, 9); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + ] + ); + x.copy_rows(11..15, 0); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + ] + ); + x.copy_rows(9..12, 3); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + ] + ); + x.copy_rows(20..30, 9); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + ] + ); + x.copy_rows(20, 30); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + ] + ); +} + +#[test] +#[rustfmt::skip] +fn row_move_oob() { + let mut x = GrowableBitSet2d::::new(3 * WORD_BITS as u32 - 2); + x.ensure_row(0).words.copy_from_slice(&[0, 0, 0]); + x.ensure_row(1).words.copy_from_slice(&[0b1010, 0b1010, 0b1010]); + x.ensure_row(2).words.copy_from_slice(&[0b0101, 0b0101, 0b0101]); + x.ensure_row(3).words.copy_from_slice(&[0b1111, 0b1111, 0b1111]); + + x.move_rows(0, 4); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + ] + ); + x.move_rows(1, 5); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0b0101, 0b0101, 0b0101, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + ] + ); + x.move_rows(..3, 7); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b0101, 0b0101, 0b0101, + ] + ); + x.ensure_row(1).words.fill(1); + x.move_rows(1..3, 9); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 1, 1, 1, + 0, 0, 0, + ] + ); + x.ensure_row(0).words.fill(0b10); + x.move_rows(11..15, 0); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0b1010, 0b1010, 0b1010, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 1, 1, 1, + 0, 0, 0, + ] + ); + x.ensure_row(10).words.fill(0b1111); + x.move_rows(9..12, 3); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 1, 1, 1, + 0b1111, 0b1111, 0b1111, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + ] + ); + x.move_rows(20..40, 4); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 1, 1, 1, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + ] + ); + x.move_rows(20, 30); + assert_eq!( + x.words(), + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 1, 1, 1, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + ] + ); +} diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 16a1a415102c..92ee08f6a582 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -35,6 +35,7 @@ fn dogfood() { for package in [ "./", + "clippy_data_structures", "clippy_dev", "clippy_lints_internal", "clippy_lints",