diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..34e4cec --- /dev/null +++ b/NOTICE @@ -0,0 +1,37 @@ + Licenses for third party components used by this project can be found below. + +================================================================================ +The MIT License applies to: + +* The code in `src/vendor/slice.rs` which is adapted from the Rust standard library. + (https://github.com/rust-lang/rust/blob/3ea2fbcb2a872b7e1fa3cada256f8e97f9e6636f/library/core/src/slice/index.rs) + Copyright (c) The Rust Project Contributors + +* The code in `src/vendor/vec/drain.rs` which is adapted from the Rust standard library. + (https://github.com/rust-lang/rust/blob/3ea2fbcb2a872b7e1fa3cada256f8e97f9e6636f/library/alloc/src/vec/drain.rs) + Copyright (c) The Rust Project Contributors + +* The code in `src/vendor/vec/splice.rs` which is adapted from the Rust standard library. + (https://github.com/rust-lang/rust/blob/3ea2fbcb2a872b7e1fa3cada256f8e97f9e6636f/library/alloc/src/vec/splice.rs) + Copyright (c) The Rust Project Contributors + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +================================================================================ diff --git a/src/dynamic.rs b/src/dynamic.rs index eee501a..5c2b3cb 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1,5 +1,6 @@ use core::mem::{self, ManuallyDrop}; use core::ptr; +use std::ops::RangeBounds; use super::EcoVec; @@ -162,6 +163,26 @@ impl DynamicVec { } } + #[inline] + pub fn insert_slice(&mut self, index: usize, bytes: &[u8]) { + match self.variant_mut() { + VariantMut::Inline(inline) => { + if inline.insert_slice(index, bytes).is_err() { + let needed = inline.len() + bytes.len(); + let mut eco = EcoVec::with_capacity(needed.next_power_of_two()); + let (a, b) = inline.as_slice().split_at(index); + eco.extend_from_byte_slice(a); + eco.extend_from_byte_slice(bytes); + eco.extend_from_byte_slice(b); + *self = Self::from_eco(eco); + } + } + VariantMut::Spilled(spilled) => { + spilled.splice(index..index, bytes.iter().copied()); + } + } + } + #[inline] pub fn clear(&mut self) { match self.variant_mut() { @@ -177,6 +198,19 @@ impl DynamicVec { VariantMut::Spilled(spilled) => spilled.truncate(target), } } + + #[inline] + pub fn remove_range(&mut self, range: R) + where + R: RangeBounds, + { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.remove_range(range), + VariantMut::Spilled(spilled) => { + spilled.drain(range); + } + } + } } impl DynamicVec { @@ -349,6 +383,37 @@ impl InlineVec { } } + #[inline] + pub fn insert_slice(&mut self, index: usize, bytes: &[u8]) -> Result<(), ()> { + let len = self.len(); + assert!(index <= len, "index {index} out of range for slice of length {len}"); + + let grown = len + bytes.len(); + let tail_len = len - index; + if grown <= LIMIT { + let ptr = self.buf.as_mut_ptr(); + unsafe { + // Safety: Checked that `index <= len` and + // `len + bytes.len() == grown < LIMIT`. + core::ptr::copy(ptr.add(index), ptr.add(index + bytes.len()), tail_len); + + // Safety: Checked that `index <= len` and + // `len + bytes.len() == grown < LIMIT`. + core::ptr::copy_nonoverlapping( + bytes.as_ptr(), + ptr.add(index), + bytes.len(), + ); + + // Safety: Checked that `grown < LIMIT` + self.set_len(grown); + } + Ok(()) + } else { + Err(()) + } + } + #[inline] pub fn truncate(&mut self, target: usize) { if target < self.len() { @@ -359,4 +424,25 @@ impl InlineVec { } } } + + #[inline] + pub fn remove_range(&mut self, range: R) + where + R: RangeBounds, + { + let len = self.len(); + let range = crate::vendor::slice::range(range, ..len); + + let tail_len = len - range.end; + let target = len - range.len(); + let ptr = self.buf.as_mut_ptr(); + unsafe { + // Safety: The range is in bounds + core::ptr::copy(ptr.add(range.end), ptr.add(range.start), tail_len); + + // Safety: Checked that it's smaller than the current length, + // which cannot exceed LIMIT itself. + self.set_len(target); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 24e6ab5..e108956 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ pub mod string; pub mod vec; mod dynamic; +mod vendor; pub use self::string::EcoString; pub use self::vec::EcoVec; diff --git a/src/string.rs b/src/string.rs index 91b6a58..c393385 100644 --- a/src/string.rs +++ b/src/string.rs @@ -158,6 +158,18 @@ impl EcoString { self.0.extend_from_slice(string.as_bytes()); } + /// Insert the given character at the index. + pub fn insert(&mut self, index: usize, c: char) { + assert!(self.is_char_boundary(index)); + self.0.insert_slice(index, c.encode_utf8(&mut [0; 4]).as_bytes()); + } + + /// Insert the given string slice at the index. + pub fn insert_str(&mut self, index: usize, string: &str) { + assert!(self.is_char_boundary(index)); + self.0.insert_slice(index, string.as_bytes()); + } + /// Remove the last character from the string. #[inline] pub fn pop(&mut self) -> Option { @@ -187,6 +199,14 @@ impl EcoString { } } + /// Remove the character at the index. + pub fn remove(&mut self, index: usize) -> char { + assert!(self.is_char_boundary(index)); + let char = self[index..].chars().next().unwrap(); + self.0.remove_range(index..index + char.len_utf8()); + char + } + /// Replaces all matches of a string with another string. /// /// This is a bit less general that [`str::replace`] because the `Pattern` diff --git a/src/vec.rs b/src/vec.rs index 130c6ab..5321f1e 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -9,12 +9,15 @@ use core::hash::{Hash, Hasher}; use core::marker::PhantomData; use core::mem; use core::ops::Deref; +use core::ops::{Range, RangeBounds}; use core::ptr::{self, NonNull}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::sync::atomic::{self, AtomicUsize, Ordering::*}; +use crate::vendor::slice; +use crate::vendor::vec::{Drain, Splice}; /// Create a new [`EcoVec`] with the given elements. /// ``` @@ -420,22 +423,62 @@ impl EcoVec { return; } - let rest = self.len - target; unsafe { - // Safety: - // - Since `target < len`, we maintain `len <= capacity`. - self.len = target; + // Safety: reference count is `1` and `target < len`. + self.truncate_unchecked(target); + } + } - // Safety: - // The reference count is `1` because of `make_unique`. - // - The pointer returned by `data_mut()` is valid for `capacity` - // writes. - // - We have the invariant `len <= capacity`. - // - Thus, `data_mut() + target` is valid for `len - target` writes. - ptr::drop_in_place(ptr::slice_from_raw_parts_mut( - self.data_mut().add(target), - rest, - )); + /// Removes the subslice indicated by the given range from the vector, + /// returning a double-ended iterator over the removed subslice. + /// + /// The vector will be cloned if its reference count is larger than 1. + pub fn drain(&mut self, range: R) -> Drain<'_, T> + where + R: RangeBounds, + { + // Memory safety + // + // When the Drain is first created, it shortens the length of + // the source vector to make sure no uninitialized or moved-from elements + // are accessible at all if the Drain's destructor never gets to run. + // + // Drain will ptr::read out the values to remove. + // When finished, remaining tail of the vec is copied back to cover + // the hole, and the vector length is restored to the new length. + // + let len = self.len(); + let Range { start, end } = slice::range(range, ..len); + + self.make_unique(); + + unsafe { + // set self.vec length's to start, to be safe in case Drain is leaked + self.len = start; + let range_slice = + core::slice::from_raw_parts(self.ptr.as_ptr().add(start), end - start); + Drain { + tail_start: end, + tail_len: len - end, + iter: range_slice.iter(), + vec: NonNull::from(self), + } + } + } + + /// Creates a splicing iterator that replaces the specified range in the vector + /// with the given `replace_with` iterator and yields the removed items. + /// + /// The vector will be cloned if its reference count is larger than 1. + #[inline] + pub fn splice(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter> + where + R: RangeBounds, + I: IntoIterator, + { + Splice { + drain: self.drain(range), + replace_with: replace_with.into_iter(), } } @@ -448,16 +491,7 @@ impl EcoVec { let mut target = capacity; if additional > capacity - self.len { - // Reserve at least the `additional` capacity, but also at least - // double the capacity to ensure exponential growth and finally - // jump directly to a minimum capacity to prevent frequent - // reallocation for small vectors. - target = self - .len - .checked_add(additional) - .unwrap_or_else(|| capacity_overflow()) - .max(2 * capacity) - .max(Self::min_cap()); + target = Self::amortized_cap(self.len, additional, capacity); } if !self.is_unique() { @@ -527,12 +561,58 @@ impl EcoVec { } impl EcoVec { + /// Forces the length of the vector to `new_len`. + /// + /// # Safety + /// - `new_len` must be less than or equal to [`Self::capacity()`]. + /// - The elements at `old_len..new_len` must be initialized. + pub unsafe fn set_len(&mut self, new_len: usize) { + self.len = new_len; + } + + /// Returns a raw pointer to the vector's buffer, or a dangling raw pointer + /// valid for zero sized reads if the vector didn't allocate. + pub fn as_ptr(&self) -> *const T { + self.ptr.as_ptr() + } + + /// Returns a raw mutable pointer to the vector's buffer, or a dangling + /// raw pointer valid for zero sized reads if the vector didn't allocate. + pub fn as_mut_ptr(&mut self) -> *mut T { + self.ptr.as_ptr() + } + + /// # Safety + /// + /// May only be called if: + /// - the reference count is `1`, and + /// - `target < len` (i.e., this methods shrinks, it doesn't grow). + pub(crate) unsafe fn truncate_unchecked(&mut self, target: usize) { + let rest = self.len - target; + unsafe { + // Safety: + // - Since `target < len`, we maintain `len <= capacity`. + self.len = target; + + // Safety: + // The reference count is `1` because of `make_unique`. + // - The pointer returned by `data_mut()` is valid for `capacity` + // writes. + // - We have the invariant `len <= capacity`. + // - Thus, `data_mut() + target` is valid for `len - target` writes. + ptr::drop_in_place(ptr::slice_from_raw_parts_mut( + self.data_mut().add(target), + rest, + )); + } + } + /// Grow the capacity to at least the `target` size. /// /// May only be called if: /// - the reference count is `1`, and /// - `target > capacity` (i.e., this methods grows, it doesn't shrink). - unsafe fn grow(&mut self, mut target: usize) { + pub(crate) unsafe fn grow(&mut self, mut target: usize) { debug_assert!(self.is_unique()); debug_assert!(target > self.capacity()); @@ -717,6 +797,20 @@ impl EcoVec { 1 } } + + /// Compute the new amortized capacity when growing the allocation. + /// + /// Reserve at least the `additional` capacity, but also at least + /// double the capacity to ensure exponential growth and finally + /// jump directly to a minimum capacity to prevent frequent + /// reallocation for small vectors. + #[inline] + pub(crate) fn amortized_cap(len: usize, additional: usize, capacity: usize) -> usize { + len.checked_add(additional) + .unwrap_or_else(|| capacity_overflow()) + .max(2 * capacity) + .max(Self::min_cap()) + } } impl EcoVec { diff --git a/src/vendor/mod.rs b/src/vendor/mod.rs new file mode 100644 index 0000000..b7379e0 --- /dev/null +++ b/src/vendor/mod.rs @@ -0,0 +1,5 @@ +//! This module contains code that has been adapted from Rust standard library +//! with minimal changes. + +pub mod slice; +pub mod vec; diff --git a/src/vendor/slice.rs b/src/vendor/slice.rs new file mode 100644 index 0000000..63bce16 --- /dev/null +++ b/src/vendor/slice.rs @@ -0,0 +1,57 @@ +//! Ported from the rust standard libaray. +//! Copyright (c) The Rust Project Contributors +//! See NOTICE for full attribution. + +use core::ops; + +/// Copy of [`core::slice::range`], which is currently unstable. +pub(crate) fn range(range: R, bounds: ops::RangeTo) -> ops::Range +where + R: ops::RangeBounds, +{ + let len = bounds.end; + + let end = match range.end_bound() { + ops::Bound::Included(&end) if end >= len => slice_index_fail(0, end, len), + // Cannot overflow because `end < len` implies `end < usize::MAX`. + ops::Bound::Included(&end) => end + 1, + + ops::Bound::Excluded(&end) if end > len => slice_index_fail(0, end, len), + ops::Bound::Excluded(&end) => end, + ops::Bound::Unbounded => len, + }; + + let start = match range.start_bound() { + ops::Bound::Excluded(&start) if start >= end => slice_index_fail(start, end, len), + // Cannot overflow because `start < end` implies `start < usize::MAX`. + ops::Bound::Excluded(&start) => start + 1, + + ops::Bound::Included(&start) if start > end => slice_index_fail(start, end, len), + ops::Bound::Included(&start) => start, + + ops::Bound::Unbounded => 0, + }; + + ops::Range { start, end } +} + +#[cfg_attr(not(panic = "immediate-abort"), inline(never), cold)] +#[cfg_attr(panic = "immediate-abort", inline)] +#[track_caller] +pub(crate) fn slice_index_fail(start: usize, end: usize, len: usize) -> ! { + if start > len { + panic!("range start index {start} out of range for slice of length {len}"); + } + + if end > len { + panic!("range end index {end} out of range for slice of length {len}"); + } + + if start > end { + panic!("slice index starts at {start} but ends at {end}"); + } + + // Only reachable if the range was a `RangeInclusive` or a + // `RangeToInclusive`, with `end == len`. + panic!("range end index {end} out of range for slice of length {len}"); +} diff --git a/src/vendor/vec/drain.rs b/src/vendor/vec/drain.rs new file mode 100644 index 0000000..a712da1 --- /dev/null +++ b/src/vendor/vec/drain.rs @@ -0,0 +1,158 @@ +//! Ported from the rust standard libaray. +//! Copyright (c) The Rust Project Contributors +//! See NOTICE for full attribution. + +use core::fmt; +use core::iter::FusedIterator; +use core::mem; +use core::ptr::{self, NonNull}; +use core::slice::{self}; + +use crate::EcoVec; + +/// A draining iterator for [`EcoVec`]. +/// +/// This `struct` is created by [`EcoVec::drain`]. +pub struct Drain<'a, T: 'a> { + /// Index of tail to preserve + pub(crate) tail_start: usize, + /// Length of tail + pub(crate) tail_len: usize, + /// Current remaining range to remove + pub(crate) iter: slice::Iter<'a, T>, + /// Invariant: the ref count of `vec` is `1`, because it has been made + /// unique in the corresponding [`EcoVec::drain`] call, and is mutably + /// borrowed by the [`Drain`] struct. + pub(crate) vec: NonNull>, +} + +impl fmt::Debug for Drain<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Drain").field(&self.iter.as_slice()).finish() + } +} + +impl<'a, T> Drain<'a, T> { + /// Returns the remaining items of this iterator as a slice. + /// + /// # Examples + /// + /// ``` + /// let mut vec = vec!['a', 'b', 'c']; + /// let mut drain = vec.drain(..); + /// assert_eq!(drain.as_slice(), &['a', 'b', 'c']); + /// let _ = drain.next().unwrap(); + /// assert_eq!(drain.as_slice(), &['b', 'c']); + /// ``` + #[must_use] + pub fn as_slice(&self) -> &[T] { + self.iter.as_slice() + } +} + +impl<'a, T> AsRef<[T]> for Drain<'a, T> { + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +unsafe impl Sync for Drain<'_, T> {} +unsafe impl Send for Drain<'_, T> {} + +impl Iterator for Drain<'_, T> { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|elt| unsafe { ptr::read(elt as *const _) }) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl DoubleEndedIterator for Drain<'_, T> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|elt| unsafe { ptr::read(elt as *const _) }) + } +} + +impl Drop for Drain<'_, T> { + fn drop(&mut self) { + /// Moves back the un-`Drain`ed elements to restore the original `Vec`. + struct DropGuard<'r, 'a, T>(&'r mut Drain<'a, T>); + + impl<'r, 'a, T> Drop for DropGuard<'r, 'a, T> { + fn drop(&mut self) { + if self.0.tail_len > 0 { + unsafe { + let source_vec = self.0.vec.as_mut(); + // memmove back untouched tail, update to new length + let start = source_vec.len(); + let tail = self.0.tail_start; + if tail != start { + let src = source_vec.as_ptr().add(tail); + let dst = source_vec.as_mut_ptr().add(start); + ptr::copy(src, dst, self.0.tail_len); + } + source_vec.set_len(start + self.0.tail_len); + } + } + } + } + + let iter = mem::take(&mut self.iter); + let drop_len = iter.len(); + + let mut vec = self.vec; + + if core::mem::size_of::() == 0 { + // ZSTs have no identity, so we don't need to move them around, we only need to drop the correct amount. + // this can be achieved by manipulating the Vec length instead of moving values out from `iter`. + unsafe { + let vec = vec.as_mut(); + let old_len = vec.len(); + vec.set_len(old_len + drop_len + self.tail_len); + + // Safety: the invariant of `vec` says that the ref count is `1` + // and `old_len + self.tail_len` is smaller than the real length + // of the vec, which has been modified upon construction of the + // `Drain` struct. + vec.truncate_unchecked(old_len + self.tail_len); + } + + return; + } + + // ensure elements are moved back into their appropriate places, even when drop_in_place panics + let _guard = DropGuard(self); + + if drop_len == 0 { + return; + } + + // as_slice() must only be called when iter.len() is > 0 because + // it also gets touched by vec::Splice which may turn it into a dangling pointer + // which would make it and the vec pointer point to different allocations which would + // lead to invalid pointer arithmetic below. + let drop_ptr = iter.as_slice().as_ptr(); + + unsafe { + // drop_ptr comes from a slice::Iter which only gives us a &[T] but for drop_in_place + // a pointer with mutable provenance is necessary. Therefore we must reconstruct + // it from the original vec but also avoid creating a &mut to the front since that could + // invalidate raw pointers to it which some unsafe code might rely on. + let vec_ptr = vec.as_mut().as_mut_ptr(); + let drop_offset = drop_ptr.offset_from(vec_ptr) as usize; + let to_drop = + ptr::slice_from_raw_parts_mut(vec_ptr.add(drop_offset), drop_len); + ptr::drop_in_place(to_drop); + } + } +} + +impl ExactSizeIterator for Drain<'_, T> {} + +impl FusedIterator for Drain<'_, T> {} diff --git a/src/vendor/vec/mod.rs b/src/vendor/vec/mod.rs new file mode 100644 index 0000000..15e4944 --- /dev/null +++ b/src/vendor/vec/mod.rs @@ -0,0 +1,5 @@ +mod drain; +mod splice; + +pub use crate::vendor::vec::drain::Drain; +pub use crate::vendor::vec::splice::Splice; diff --git a/src/vendor/vec/splice.rs b/src/vendor/vec/splice.rs new file mode 100644 index 0000000..498c1c5 --- /dev/null +++ b/src/vendor/vec/splice.rs @@ -0,0 +1,149 @@ +//! Ported from the rust standard libaray. +//! Copyright (c) The Rust Project Contributors +//! See NOTICE for full attribution. + +use core::ptr; +use core::slice; + +use crate::EcoVec; + +use super::Drain; + +/// A splicing iterator for [`EcoVec`]. +/// +/// This struct is created by [`EcoVec::splice()`]. +/// See its documentation for more. +#[derive(Debug)] +pub struct Splice<'a, I: Iterator + 'a> +where + // The Clone bound is required here, because the Drop implementation calls + // methods that require it. + I::Item: Clone, +{ + pub(crate) drain: Drain<'a, I::Item>, + pub(crate) replace_with: I, +} + +impl Iterator for Splice<'_, I> +where + I::Item: Clone, +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + self.drain.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.drain.size_hint() + } +} + +impl DoubleEndedIterator for Splice<'_, I> +where + I::Item: Clone, +{ + fn next_back(&mut self) -> Option { + self.drain.next_back() + } +} + +impl ExactSizeIterator for Splice<'_, I> where I::Item: Clone {} + +// See also: [`crate::collections::vec_deque::Splice`]. +impl Drop for Splice<'_, I> +where + I::Item: Clone, +{ + fn drop(&mut self) { + self.drain.by_ref().for_each(drop); + // At this point draining is done and the only remaining tasks are splicing + // and moving things into the final place. + // Which means we can replace the slice::Iter with pointers that won't point to deallocated + // memory, so that Drain::drop is still allowed to call iter.len(), otherwise it would break + // the ptr.offset_from_unsigned contract. + self.drain.iter = [].iter(); + + unsafe { + if self.drain.tail_len == 0 { + self.drain.vec.as_mut().extend(self.replace_with.by_ref()); + return; + } + + // First fill the range left by drain(). + if !self.drain.fill(&mut self.replace_with) { + return; + } + + // There may be more elements. Use the lower bound as an estimate. + // FIXME: Is the upper bound a better guess? Or something else? + let (lower_bound, _upper_bound) = self.replace_with.size_hint(); + if lower_bound > 0 { + self.drain.move_tail(lower_bound); + if !self.drain.fill(&mut self.replace_with) { + return; + } + } + + // Collect any remaining elements. + // This is a zero-length vector which does not allocate if `lower_bound` was exact. + let mut collected = + self.replace_with.by_ref().collect::>().into_iter(); + // Now we have an exact count. + if collected.len() > 0 { + self.drain.move_tail(collected.len()); + let filled = self.drain.fill(&mut collected); + debug_assert!(filled); + debug_assert_eq!(collected.len(), 0); + } + } + // Let `Drain::drop` move the tail back if necessary and restore `vec.len`. + } +} + +/// Private helper methods for `Splice::drop` +impl Drain<'_, T> { + /// The range from `self.vec.len` to `self.tail_start` contains elements + /// that have been moved out. + /// Fill that range as much as possible with new elements from the `replace_with` iterator. + /// Returns `true` if we filled the entire range. (`replace_with.next()` didn’t return `None`.) + unsafe fn fill>(&mut self, replace_with: &mut I) -> bool { + let vec = unsafe { self.vec.as_mut() }; + let range_start = vec.len(); + let range_end = self.tail_start; + let range_slice = unsafe { + slice::from_raw_parts_mut( + vec.as_mut_ptr().add(range_start), + range_end - range_start, + ) + }; + + for place in range_slice { + let Some(new_item) = replace_with.next() else { + return false; + }; + unsafe { ptr::write(place, new_item) }; + vec.set_len(vec.len() + 1); + } + true + } + + /// Makes room for inserting more elements before the tail. + unsafe fn move_tail(&mut self, additional: usize) { + let vec = unsafe { self.vec.as_mut() }; + let len = self.tail_start + self.tail_len; + let capacity = vec.capacity(); + if additional > capacity - len { + let target = EcoVec::::amortized_cap(len, additional, capacity); + vec.grow(target); + } + + let new_tail_start = self.tail_start + additional; + unsafe { + let src = vec.as_ptr().add(self.tail_start); + let dst = vec.as_mut_ptr().add(new_tail_start); + ptr::copy(src, dst, self.tail_len); + } + self.tail_start = new_tail_start; + } +} diff --git a/tests/tests.rs b/tests/tests.rs index df02e28..fdbd958 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -203,6 +203,48 @@ fn test_vec_truncate() { assert_eq!(cloned, ["ok"; 2]); } +#[test] +fn test_vec_drain() { + let mut vec = EcoVec::from_elem(v("a"), 10); + vec.drain(3..6); + assert_eq!(vec.len(), 7); + + // Manually pull an item, before dropping the drain. + let mut drain = vec.drain(5..); + drain.next(); + drop(drain); + assert_eq!(vec.len(), 5); + + let mut cloned = vec.clone(); + cloned.drain(..2); + assert_eq!(cloned.len(), 3); + + let drained = cloned.drain(..).collect::>(); + assert_eq!(drained.len(), 3); + assert_eq!(cloned, []); +} + +#[test] +fn test_vec_splice() { + let mut vec = eco_vec!["a"; 6]; + // Inserted iterator is smaller. + vec.splice(2..4, ["b"; 1]); + assert_eq!(vec, ["a", "a", "b", "a", "a"]); + + // Inserted iterator is larger. + let mut cloned = vec.clone(); + cloned.splice(3..4, ["c"; 3]); + assert_eq!(cloned, ["a", "a", "b", "c", "c", "c", "a"]); + + // Inserted iterator is the same size. + cloned.splice(2..6, ["d"; 4]); + assert_eq!(cloned, ["a", "a", "d", "d", "d", "d", "a"]); + + // Insert an interator that doesn't have exact size hints. + cloned.splice(1..6, ["e"; 3].into_iter().filter(|_| true)); + assert_eq!(cloned, ["a", "e", "e", "e", "a"]); +} + #[test] fn test_vec_extend() { let mut vec = EcoVec::new(); @@ -375,6 +417,36 @@ fn test_str_push() { assert_eq!(v.len(), 15); } +#[test] +fn test_str_insert() { + let mut v = EcoString::new(); + v.insert(0, 'a'); + v.insert(1, '😀'); + v.insert_str(1, "bcd"); + assert_eq!(v, "abcd😀"); + assert_eq!(v.len(), 8); + + // Test fully filling the inline storage. + v.insert_str(8, "efghijk"); + assert_eq!(v.len(), 15); + + // Test spilling with `insert`. + let mut a = v.clone(); + assert_eq!(a, "abcd😀efghijk"); + a.insert(8, '_'); + assert_eq!(a, "abcd😀_efghijk"); + assert_eq!(a.len(), 16); + + // Test spilling with `insert_str`. + let mut b = v.clone(); + b.insert_str(2, "._."); + assert_eq!(b, "ab._.cd😀efghijk"); + assert_eq!(b.len(), 18); + + // v should be unchanged. + assert_eq!(v.len(), 15); +} + #[test] fn test_str_pop() { // Test with inline string. @@ -398,6 +470,30 @@ fn test_str_pop() { assert_eq!(v.len(), 25); } +#[test] +fn test_str_remove() { + // Test with inline string. + let mut v = EcoString::from("Hello World!"); + assert_eq!(v.remove(0), 'H'); + assert_eq!(v, "ello World!"); + + // Remove one-by-one. + for i in (4..10).rev() { + v.remove(i); + } + assert_eq!(v, "ello!"); + + for _ in 0..5 { + v.remove(0); + } + assert!(v.is_empty()); + + // Test with large string. + let mut v = EcoString::from(ALPH); + assert_eq!(v.remove(21), 'v'); + assert_eq!(v.len(), 25); +} + #[test] fn test_str_index() { // Test that we can use the index syntax.