use alloc::borrow::Cow;
use alloc::boxed::Box;
use core::str::Utf8Error;
use core::{mem, ptr};
#[cfg(feature = "bytes")]
mod bytes;
#[cfg(feature = "smallvec")]
mod smallvec;
mod capacity;
mod heap;
mod inline;
mod iter;
mod last_utf8_char;
mod num;
#[allow(unexpected_cfgs)]
#[cfg(kani)]
mod proofs;
mod static_str;
mod traits;
use alloc::string::String;
use capacity::Capacity;
use heap::HeapBuffer;
use inline::InlineBuffer;
use last_utf8_char::LastByte;
use static_str::StaticStr;
pub(crate) use traits::IntoRepr;
use crate::{ReserveError, UnwrapWithMsg};
pub(crate) const MAX_SIZE: usize = core::mem::size_of::<String>();
pub(crate) const HEAP_MASK: u8 = LastByte::Heap as u8;
pub(crate) const STATIC_STR_MASK: u8 = LastByte::Static as u8;
pub(crate) const LENGTH_MASK: u8 = 0b11000000;
const EMPTY: Repr = Repr::const_new("");
#[repr(C)]
pub(crate) struct Repr(
*const (),
usize,
#[cfg(target_pointer_width = "64")]
u32,
u16,
u8,
LastByte,
);
static_assertions::assert_eq_size!([u8; MAX_SIZE], Repr);
unsafe impl Send for Repr {}
unsafe impl Sync for Repr {}
impl Repr {
#[inline]
pub(crate) fn new(text: &str) -> Result<Self, ReserveError> {
let len = text.len();
if len == 0 {
Ok(EMPTY)
} else if len <= MAX_SIZE {
let inline = unsafe { InlineBuffer::new(text) };
Repr::from_inline_ok(inline)
} else {
HeapBuffer::new(text).map(Repr::from_heap)
}
}
#[inline]
#[track_caller]
pub(crate) fn new_panic(text: &str) -> Self {
let len = text.len();
if len <= MAX_SIZE {
let inline = unsafe { InlineBuffer::new(text) };
Repr::from_inline(inline)
} else {
let heap = HeapBuffer::alloc_copy_panic(text);
Repr::from_heap(heap)
}
}
#[inline]
pub(crate) const fn const_new(text: &'static str) -> Self {
if text.len() <= MAX_SIZE {
let inline = InlineBuffer::new_const(text);
Repr::from_inline(inline)
} else {
let repr = StaticStr::new(text);
Repr::from_static(repr)
}
}
#[inline]
pub(crate) fn with_capacity(capacity: usize) -> Result<Self, ReserveError> {
if capacity <= MAX_SIZE {
Ok(EMPTY)
} else {
HeapBuffer::with_capacity(capacity).map(Repr::from_heap)
}
}
#[inline]
pub(crate) fn from_utf8<B: AsRef<[u8]>>(buf: B) -> Result<Self, Utf8Error> {
let s = core::str::from_utf8(buf.as_ref())?;
Ok(Self::new(s).unwrap_with_msg())
}
#[inline]
pub(crate) unsafe fn from_utf8_unchecked<B: AsRef<[u8]>>(buf: B) -> Result<Self, ReserveError> {
let bytes = buf.as_ref();
let bytes_len = bytes.len();
let mut repr = Repr::with_capacity(bytes_len)?;
unsafe {
repr.as_mut_ptr()
.copy_from_nonoverlapping(bytes.as_ptr(), bytes_len)
};
repr.set_len(bytes_len);
Ok(repr)
}
#[inline]
pub(crate) fn from_string(s: String, should_inline: bool) -> Result<Self, ReserveError> {
let og_cap = s.capacity();
let cap = Capacity::new(og_cap);
if cap.is_heap() {
let len = s.len();
let (ptr, cap) = HeapBuffer::alloc_copy(s.as_str())?;
Ok(Repr::from_heap(HeapBuffer { ptr, len, cap }))
} else if og_cap == 0 {
Ok(EMPTY)
} else if should_inline && s.len() <= MAX_SIZE {
let inline = unsafe { InlineBuffer::new(s.as_str()) };
Repr::from_inline_ok(inline)
} else {
let mut s = mem::ManuallyDrop::new(s.into_bytes());
let len = s.len();
let raw_ptr = s.as_mut_ptr();
let ptr = ptr::NonNull::new(raw_ptr).expect("string with capacity has null ptr?");
let heap = HeapBuffer { ptr, len, cap };
Ok(Repr::from_heap(heap))
}
}
#[inline]
pub(crate) fn into_string(self) -> String {
#[cold]
fn into_string_heap(this: HeapBuffer) -> String {
let slice = unsafe { core::slice::from_raw_parts(this.ptr.as_ptr(), this.len) };
let s = unsafe { core::str::from_utf8_unchecked(slice) };
String::from(s)
}
if self.is_heap_allocated() {
let heap_buffer = unsafe { self.into_heap() };
if heap_buffer.cap.is_heap() {
into_string_heap(heap_buffer)
} else {
let this = mem::ManuallyDrop::new(heap_buffer);
let cap = unsafe { this.cap.as_usize() };
unsafe { String::from_raw_parts(this.ptr.as_ptr(), this.len, cap) }
}
} else {
String::from(self.as_str())
}
}
#[inline]
pub(crate) fn reserve(&mut self, additional: usize) -> Result<(), ReserveError> {
let len = self.len();
let needed_capacity = len.checked_add(additional).ok_or(ReserveError(()))?;
if !self.is_static_str() && needed_capacity <= self.capacity() {
Ok(())
} else if needed_capacity <= MAX_SIZE {
let inline = unsafe { InlineBuffer::new(self.as_str()) };
*self = Repr::from_inline(inline);
Ok(())
} else if !self.is_heap_allocated() {
let heap = HeapBuffer::with_additional(self.as_str(), additional)?;
*self = Repr::from_heap(heap);
Ok(())
} else {
let heap_buffer = unsafe { self.as_mut_heap() };
let amortized_capacity = heap::amortized_growth(len, additional);
if heap_buffer.realloc(amortized_capacity).is_err() {
let heap = HeapBuffer::with_additional(self.as_str(), additional)?;
*self = Repr::from_heap(heap);
}
Ok(())
}
}
pub(crate) fn shrink_to(&mut self, min_capacity: usize) {
if !self.is_heap_allocated() {
return;
}
let heap = unsafe { self.as_mut_heap() };
let old_capacity = heap.capacity();
let new_capacity = heap.len.max(min_capacity);
if new_capacity <= MAX_SIZE {
let mut inline = InlineBuffer::empty();
unsafe {
inline
.0
.as_mut_ptr()
.copy_from_nonoverlapping(heap.ptr.as_ptr(), heap.len)
};
unsafe { inline.set_len(heap.len) }
*self = Repr::from_inline(inline);
return;
}
if new_capacity >= old_capacity {
return;
}
if heap.realloc(new_capacity).is_ok() {
return;
}
if let Ok(mut new_this) = Repr::with_capacity(new_capacity) {
new_this.push_str(self.as_str());
*self = new_this;
}
}
#[inline]
pub(crate) fn push_str(&mut self, s: &str) {
if s.is_empty() {
return;
}
let len = self.len();
let str_len = s.len();
if self.is_static_str() || len + str_len > self.capacity() {
self.reserve(str_len).unwrap_with_msg();
}
unsafe {
self.as_mut_ptr()
.add(len)
.copy_from_nonoverlapping(s.as_ptr(), str_len)
};
unsafe { self.set_len(len + str_len) };
}
#[inline]
pub(crate) fn pop(&mut self) -> Option<char> {
let ch = self.as_str().chars().next_back()?;
unsafe { self.set_len(self.len() - ch.len_utf8()) };
Some(ch)
}
#[inline]
pub(crate) fn as_slice(&self) -> &[u8] {
let mut pointer = self as *const Self as *const u8;
let heap_pointer = self.0 as *const u8;
if self.last_byte() >= HEAP_MASK {
pointer = heap_pointer;
}
let mut length = core::cmp::min(
self.last_byte().wrapping_sub(LENGTH_MASK) as usize,
MAX_SIZE,
);
let heap_length = self.1;
if self.last_byte() >= HEAP_MASK {
length = heap_length;
}
unsafe { core::slice::from_raw_parts(pointer, length) }
}
#[inline]
pub(crate) fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_slice()) }
}
#[inline]
pub(crate) fn len(&self) -> usize {
let len_heap = ensure_read(self.1);
let last_byte = self.last_byte();
let mut len = (last_byte as usize)
.wrapping_sub(LENGTH_MASK as usize)
.min(MAX_SIZE);
if last_byte >= HEAP_MASK {
len = len_heap;
}
len
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
let len_heap = ensure_read(self.1);
let last_byte = self.last_byte() as usize;
let mut len = last_byte.wrapping_sub(LastByte::L0 as u8 as usize);
if last_byte >= LastByte::Heap as u8 as usize {
len = len_heap;
}
len == 0
}
#[inline]
pub(crate) fn capacity(&self) -> usize {
#[cold]
fn heap_capacity(this: &Repr) -> usize {
let heap_buffer = unsafe { this.as_heap() };
heap_buffer.capacity()
}
if let Some(s) = self.as_static_str() {
s.len()
} else if self.is_heap_allocated() {
heap_capacity(self)
} else {
MAX_SIZE
}
}
#[inline(always)]
pub(crate) fn is_heap_allocated(&self) -> bool {
let last_byte = self.last_byte();
last_byte == HEAP_MASK
}
#[inline(always)]
const fn is_static_str(&self) -> bool {
let last_byte = self.last_byte();
last_byte == STATIC_STR_MASK
}
#[inline]
pub(crate) const fn as_static_str(&self) -> Option<&'static str> {
if self.is_static_str() {
let s: &StaticStr = unsafe { &*(self as *const Self as *const StaticStr) };
Some(s.get_text())
} else {
None
}
}
#[inline]
fn as_static_variant_mut(&mut self) -> Option<&mut StaticStr> {
if self.is_static_str() {
let s: &mut StaticStr = unsafe { &mut *(self as *mut Self as *mut StaticStr) };
Some(s)
} else {
None
}
}
#[inline]
pub(crate) fn as_mut_ptr(&mut self) -> *mut u8 {
#[cold]
fn inline_static_str(this: &mut Repr) {
if let Some(s) = this.as_static_str() {
*this = Repr::new(s).unwrap_with_msg();
}
}
if self.is_static_str() {
inline_static_str(self);
}
if self.is_heap_allocated() {
unsafe { self.as_mut_heap().ptr.as_ptr() }
} else {
self as *mut Self as *mut u8
}
}
#[inline]
pub(crate) unsafe fn set_len(&mut self, len: usize) {
if let Some(s) = self.as_static_variant_mut() {
s.set_len(len);
} else if self.is_heap_allocated() {
let heap_buffer = self.as_mut_heap();
heap_buffer.set_len(len);
} else {
let inline_buffer = self.as_mut_inline();
inline_buffer.set_len(len);
}
}
#[cfg(feature = "zeroize")]
pub(crate) fn zeroize(&mut self) {
if self.is_static_str() {
*self = EMPTY;
return;
}
#[inline(always)]
unsafe fn volatile_zero(dst: *mut u8, count: usize) {
for i in 0..count {
let dst = dst.add(i);
ptr::write_volatile(dst, 0);
}
}
#[inline(always)]
fn atomic_fence() {
use core::sync::atomic;
atomic::compiler_fence(atomic::Ordering::SeqCst);
}
let last_byte = self.last_byte();
let (ptr, cap) = if last_byte == HEAP_MASK {
let heap_buffer = unsafe { self.as_mut_heap() };
unsafe { heap_buffer.set_len(0) };
let ptr = heap_buffer.ptr.as_ptr();
let cap = heap_buffer.capacity();
(ptr, cap)
} else {
let inline_buffer = unsafe { self.as_mut_inline() };
unsafe { inline_buffer.set_len(0) };
let ptr = self as *mut Self as *mut u8;
let cap = MAX_SIZE - 1;
(ptr, cap)
};
unsafe { volatile_zero(ptr, cap) };
atomic_fence()
}
#[inline(always)]
const fn last_byte(&self) -> u8 {
cfg_if::cfg_if! {
if #[cfg(target_pointer_width = "64")] {
let last_byte = self.5;
} else if #[cfg(target_pointer_width = "32")] {
let last_byte = self.4;
} else {
compile_error!("Unsupported target_pointer_width");
}
};
last_byte as u8
}
#[inline(always)]
const fn from_inline(inline: InlineBuffer) -> Self {
unsafe { core::mem::transmute(inline) }
}
#[inline(always)]
fn from_inline_ok(inline: InlineBuffer) -> Result<Self, ReserveError> {
let repr = Self::from_inline(inline);
unsafe { assume(repr.last_byte() < HEAP_MASK) };
Ok(repr)
}
#[inline(always)]
const fn from_heap(heap: HeapBuffer) -> Self {
unsafe { core::mem::transmute(heap) }
}
#[inline(always)]
const fn from_static(heap: StaticStr) -> Self {
unsafe { core::mem::transmute(heap) }
}
#[inline(always)]
const unsafe fn into_heap(self) -> HeapBuffer {
core::mem::transmute(self)
}
#[inline(always)]
unsafe fn as_mut_heap(&mut self) -> &mut HeapBuffer {
&mut *(self as *mut _ as *mut HeapBuffer)
}
#[inline(always)]
unsafe fn as_heap(&self) -> &HeapBuffer {
&*(self as *const _ as *const HeapBuffer)
}
#[inline(always)]
#[cfg(feature = "smallvec")]
const unsafe fn into_inline(self) -> InlineBuffer {
core::mem::transmute(self)
}
#[inline(always)]
unsafe fn as_mut_inline(&mut self) -> &mut InlineBuffer {
&mut *(self as *mut _ as *mut InlineBuffer)
}
}
impl Clone for Repr {
#[inline]
fn clone(&self) -> Self {
if self.is_heap_allocated() {
Repr::new_panic(self.as_str())
} else {
unsafe { core::ptr::read(self) }
}
}
#[inline]
fn clone_from(&mut self, source: &Self) {
#[inline(never)]
fn clone_from_heap(this: &mut Repr, source: &Repr) {
unsafe { this.set_len(0) };
this.push_str(source.as_str());
}
if source.is_heap_allocated() {
clone_from_heap(self, source)
} else {
*self = unsafe { core::ptr::read(source) }
}
}
}
impl Drop for Repr {
#[inline]
fn drop(&mut self) {
if self.is_heap_allocated() {
let heap = unsafe { self.as_heap() };
outlined_drop(heap.ptr, heap.cap)
}
#[cold]
fn outlined_drop(ptr: ptr::NonNull<u8>, cap: Capacity) {
heap::deallocate_ptr(ptr, cap);
}
}
}
impl Extend<char> for Repr {
#[inline]
fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
let mut iter = iter.into_iter();
let (lower_bound, _) = iter.size_hint();
if lower_bound > 0 {
let _: Result<(), ReserveError> = self.reserve(lower_bound);
}
'refetch: loop {
let mut next = iter.next();
if next.is_none() {
return;
}
let cap = self.capacity();
let mut cur = self.len();
let ptr = self.as_mut_ptr();
let mut buf = [0u8; 4];
while let Some(c) = next {
let encoded = c.encode_utf8(&mut buf);
let n = encoded.len();
if cur + n > cap {
unsafe { self.set_len(cur) };
self.push_str(encoded);
continue 'refetch;
}
unsafe {
ptr.add(cur).copy_from_nonoverlapping(encoded.as_ptr(), n);
}
cur += n;
next = iter.next();
}
unsafe { self.set_len(cur) };
return;
}
}
}
impl<'a> Extend<&'a char> for Repr {
fn extend<T: IntoIterator<Item = &'a char>>(&mut self, iter: T) {
self.extend(iter.into_iter().copied());
}
}
impl<'a> Extend<&'a str> for Repr {
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
iter.into_iter().for_each(|s| self.push_str(s));
}
}
impl Extend<Box<str>> for Repr {
fn extend<T: IntoIterator<Item = Box<str>>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
impl<'a> Extend<Cow<'a, str>> for Repr {
fn extend<T: IntoIterator<Item = Cow<'a, str>>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
impl Extend<String> for Repr {
fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
#[inline(always)]
fn ensure_read(value: usize) -> usize {
#[allow(unexpected_cfgs)]
#[cfg(all(
not(any(kani, miri)),
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "arm",
target_arch = "aarch64",
)
))]
unsafe {
core::arch::asm!(
"/* {value} */",
value = in(reg) value,
options(nomem, nostack),
);
};
value
}
#[inline(always)]
const unsafe fn assume(cond: bool) {
debug_assert!(cond);
if !cond {
core::hint::unreachable_unchecked();
}
}
#[cfg(not(all(target_pointer_width = "64", target_endian = "little")))]
#[inline(always)]
pub(crate) unsafe fn copy_small(src: *const u8, dst: *mut u8, len: usize) {
debug_assert!(len <= MAX_SIZE);
if len >= 16 {
ptr::copy_nonoverlapping(src, dst, 16);
ptr::copy_nonoverlapping(src.add(len - 16), dst.add(len - 16), 16);
} else if len >= 8 {
ptr::copy_nonoverlapping(src, dst, 8);
ptr::copy_nonoverlapping(src.add(len - 8), dst.add(len - 8), 8);
} else if len >= 4 {
ptr::copy_nonoverlapping(src, dst, 4);
ptr::copy_nonoverlapping(src.add(len - 4), dst.add(len - 4), 4);
} else if len >= 2 {
ptr::copy_nonoverlapping(src, dst, 2);
ptr::copy_nonoverlapping(src.add(len - 2), dst.add(len - 2), 2);
} else if len == 1 {
*dst = *src;
}
}
#[inline]
pub(crate) unsafe fn copy_medium(src: *const u8, dst: *mut u8, len: usize) {
if len > 64 {
ptr::copy_nonoverlapping(src, dst, len);
} else if len >= 32 {
ptr::copy_nonoverlapping(src, dst, 32);
ptr::copy_nonoverlapping(src.add(len - 32), dst.add(len - 32), 32);
} else if len >= 16 {
ptr::copy_nonoverlapping(src, dst, 16);
ptr::copy_nonoverlapping(src.add(len - 16), dst.add(len - 16), 16);
} else {
ptr::copy_nonoverlapping(src, dst, len);
}
}
#[cfg(test)]
mod tests {
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use quickcheck_macros::quickcheck;
use test_case::test_case;
use super::{Repr, MAX_SIZE};
use crate::ReserveError;
const EIGHTEEN_MB: usize = 18 * 1024 * 1024;
const EIGHTEEN_MB_STR: &str = unsafe { core::str::from_utf8_unchecked(&[42; EIGHTEEN_MB]) };
#[test_case("hello world!"; "inline")]
#[test_case("this is a long string that should be stored on the heap"; "heap")]
fn test_create(s: &'static str) {
let repr = Repr::new(s).unwrap();
assert_eq!(repr.as_str(), s);
assert_eq!(repr.len(), s.len());
let repr = Repr::const_new(s);
assert_eq!(repr.as_str(), s);
assert_eq!(repr.len(), s.len());
}
#[quickcheck]
#[cfg_attr(miri, ignore)]
fn quickcheck_create(s: String) {
let repr = Repr::new(&s).unwrap();
assert_eq!(repr.as_str(), s);
assert_eq!(repr.len(), s.len());
}
#[test_case(0; "empty")]
#[test_case(10; "short")]
#[test_case(64; "long")]
#[test_case(EIGHTEEN_MB; "huge")]
fn test_with_capacity(cap: usize) {
let r = Repr::with_capacity(cap).unwrap();
assert!(r.capacity() >= MAX_SIZE);
assert_eq!(r.len(), 0);
}
#[test_case(""; "empty")]
#[test_case("abc"; "short")]
#[test_case("hello world! I am a longer string 🦀"; "long")]
fn test_from_utf8_valid(s: &'static str) {
let bytes = s.as_bytes();
let r = Repr::from_utf8(bytes).expect("valid UTF-8");
assert_eq!(r.as_str(), s);
assert_eq!(r.len(), s.len());
}
#[quickcheck]
#[cfg_attr(miri, ignore)]
fn quickcheck_from_utf8(buf: Vec<u8>) {
match (core::str::from_utf8(&buf), Repr::from_utf8(&buf)) {
(Ok(s), Ok(r)) => {
assert_eq!(r.as_str(), s);
assert_eq!(r.len(), s.len());
}
(Err(e), Err(r)) => assert_eq!(e, r),
_ => panic!("core::str and Repr differ on what is valid UTF-8!"),
}
}
#[test_case(String::new(), true; "empty should inline")]
#[test_case(String::new(), false; "empty not inline")]
#[test_case(String::with_capacity(10), true ; "empty with small capacity inline")]
#[test_case(String::with_capacity(10), false ; "empty with small capacity not inline")]
#[test_case(String::with_capacity(128), true ; "empty with large capacity inline")]
#[test_case(String::with_capacity(128), false ; "empty with large capacity not inline")]
#[test_case(String::from("nyc 🗽"), true; "short should inline")]
#[test_case(String::from("nyc 🗽"), false ; "short not inline")]
#[test_case(String::from("this is a really long string, which is intended"), true; "long")]
#[test_case(String::from("this is a really long string, which is intended"), false; "long not inline")]
#[test_case(EIGHTEEN_MB_STR.to_string(), true ; "huge should inline")]
#[test_case(EIGHTEEN_MB_STR.to_string(), false ; "huge not inline")]
fn test_from_string(s: String, try_to_inline: bool) {
let s_len = s.len();
let s_cap = s.capacity();
let s_str = s.clone();
let r = Repr::from_string(s, try_to_inline).unwrap();
assert_eq!(r.len(), s_len);
assert_eq!(r.as_str(), s_str.as_str());
if s_cap == 0 || (try_to_inline && s_len <= MAX_SIZE) {
assert!(!r.is_heap_allocated());
} else {
assert!(r.is_heap_allocated());
}
}
#[quickcheck]
#[cfg_attr(miri, ignore)]
fn quickcheck_from_string(s: String, try_to_inline: bool) {
let r = Repr::from_string(s.clone(), try_to_inline).unwrap();
assert_eq!(r.len(), s.len());
assert_eq!(r.as_str(), s.as_str());
if s.capacity() == 0 {
assert!(!r.is_heap_allocated());
} else if s.capacity() <= MAX_SIZE {
assert_eq!(!r.is_heap_allocated(), try_to_inline);
} else {
assert!(r.is_heap_allocated());
}
}
#[test_case(""; "empty")]
#[test_case("nyc 🗽"; "short")]
#[test_case("this is a really long string, which is intended"; "long")]
fn test_into_string(control: &'static str) {
let r = Repr::new(control).unwrap();
let s = r.into_string();
assert_eq!(control.len(), s.len());
assert_eq!(control, s.as_str());
let r = Repr::const_new(control);
let s = r.into_string();
assert_eq!(control.len(), s.len());
assert_eq!(control, s.as_str());
}
#[quickcheck]
#[cfg_attr(miri, ignore)]
fn quickcheck_into_string(control: String) {
let r = Repr::new(&control).unwrap();
let s = r.into_string();
assert_eq!(control.len(), s.len());
assert_eq!(control, s.as_str());
}
#[test_case("", "a", false; "empty")]
#[test_case("", "🗽", false; "empty_emoji")]
#[test_case("abc", "🗽🙂🦀🌈👏🐶", true; "inline_to_heap")]
#[test_case("i am a long string that will be on the heap", "extra", true; "heap_to_heap")]
fn test_push_str(control: &'static str, append: &'static str, is_heap: bool) {
let mut r = Repr::new(control).unwrap();
let mut c = String::from(control);
r.push_str(append);
c.push_str(append);
assert_eq!(r.as_str(), c.as_str());
assert_eq!(r.len(), c.len());
assert_eq!(r.is_heap_allocated(), is_heap);
let mut r = Repr::const_new(control);
let mut c = String::from(control);
r.push_str(append);
c.push_str(append);
assert_eq!(r.as_str(), c.as_str());
assert_eq!(r.len(), c.len());
assert_eq!(r.is_heap_allocated(), is_heap);
}
#[quickcheck]
#[cfg_attr(miri, ignore)]
fn quickcheck_push_str(control: String, append: String) {
let mut r = Repr::new(&control).unwrap();
let mut c = control;
r.push_str(&append);
c.push_str(&append);
assert_eq!(r.as_str(), c.as_str());
assert_eq!(r.len(), c.len());
}
#[test_case(&[42; 0], &[42; EIGHTEEN_MB]; "empty_to_heap_capacity")]
#[test_case(&[42; 8], &[42; EIGHTEEN_MB]; "inline_to_heap_capacity")]
#[test_case(&[42; 128], &[42; EIGHTEEN_MB]; "heap_inline_to_heap_capacity")]
#[test_case(&[42; EIGHTEEN_MB], &[42; 64]; "heap_capacity_to_heap_capacity")]
fn test_push_str_from_buf(buf: &[u8], append: &[u8]) {
let control = unsafe { core::str::from_utf8_unchecked(buf) };
let append = unsafe { core::str::from_utf8_unchecked(append) };
let mut r = Repr::new(control).unwrap();
let mut c = String::from(control);
r.push_str(append);
c.push_str(append);
assert_eq!(r.as_str(), c.as_str());
assert_eq!(r.len(), c.len());
assert!(r.is_heap_allocated());
}
#[test_case("", 0, false; "empty_zero")]
#[test_case("", 10, false; "empty_small")]
#[test_case("", 64, true; "empty_large")]
#[test_case("abc", 0, false; "short_zero")]
#[test_case("abc", 8, false; "short_small")]
#[test_case("abc", 64, true; "short_large")]
#[test_case("I am a long string that will be on the heap", 0, true; "large_zero")]
#[test_case("I am a long string that will be on the heap", 10, true; "large_small")]
#[test_case("I am a long string that will be on the heap", EIGHTEEN_MB, true; "large_huge")]
fn test_reserve(initial: &'static str, additional: usize, is_heap: bool) {
let mut r = Repr::new(initial).unwrap();
r.reserve(additional).unwrap();
assert!(r.capacity() >= initial.len() + additional);
assert_eq!(r.is_heap_allocated(), is_heap);
let mut r = Repr::const_new(initial);
r.reserve(additional).unwrap();
assert!(r.capacity() >= initial.len() + additional);
assert_eq!(r.is_heap_allocated(), is_heap);
}
#[test]
fn test_reserve_overflow() {
let mut r = Repr::new("abc").unwrap();
let err = r.reserve(usize::MAX).unwrap_err();
assert_eq!(err, ReserveError(()));
}
#[test_case(""; "empty")]
#[test_case("abc"; "short")]
#[test_case("i am a longer string that will be on the heap"; "long")]
#[test_case(EIGHTEEN_MB_STR; "huge")]
fn test_clone(initial: &'static str) {
let r_a = Repr::new(initial).unwrap();
let r_b = r_a.clone();
assert_eq!(r_a.as_str(), initial);
assert_eq!(r_a.len(), initial.len());
assert_eq!(r_a.as_str(), r_b.as_str());
assert_eq!(r_a.len(), r_b.len());
assert_eq!(r_a.capacity(), r_b.capacity());
assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());
let r_a = Repr::const_new(initial);
let r_b = r_a.clone();
assert_eq!(r_a.as_str(), initial);
assert_eq!(r_a.len(), initial.len());
assert_eq!(r_a.as_str(), r_b.as_str());
assert_eq!(r_a.len(), r_b.len());
assert_eq!(r_a.capacity(), r_b.capacity());
assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());
}
#[test_case(Repr::const_new(""), Repr::const_new(""); "empty clone from static")]
#[test_case(Repr::const_new("abc"), Repr::const_new("efg"); "short clone from static")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::const_new(EIGHTEEN_MB_STR); "long clone from static")]
#[test_case(Repr::const_new(""), Repr::const_new(""); "empty clone from inline")]
#[test_case(Repr::const_new("abc"), Repr::const_new("efg"); "short clone from inline")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::const_new("small"); "long clone from inline")]
#[test_case(Repr::const_new(""), Repr::new(EIGHTEEN_MB_STR).unwrap(); "empty clone from heap")]
#[test_case(Repr::const_new("abc"), Repr::new(EIGHTEEN_MB_STR).unwrap(); "short clone from heap")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::new(EIGHTEEN_MB_STR).unwrap(); "long clone from heap")]
fn test_clone_from(mut initial: Repr, source: Repr) {
initial.clone_from(&source);
assert_eq!(initial.as_str(), source.as_str());
assert_eq!(initial.is_heap_allocated(), source.is_heap_allocated());
}
#[quickcheck]
#[cfg_attr(miri, ignore)]
fn quickcheck_clone(initial: String) {
let r_a = Repr::new(&initial).unwrap();
let r_b = r_a.clone();
assert_eq!(r_a.as_str(), initial);
assert_eq!(r_a.len(), initial.len());
assert_eq!(r_a.as_str(), r_b.as_str());
assert_eq!(r_a.len(), r_b.len());
assert_eq!(r_a.capacity(), r_b.capacity());
assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());
}
}