#![allow(
// We follow libstd's lead and prefer to define both.
clippy::partialeq_ne_impl,
// This is a really annoying clippy lint, since it's required for so many cases...
clippy::cast_ptr_alignment,
// For macros
clippy::redundant_slicing,
)]
#![cfg_attr(feature = "substr-usize-indices", allow(clippy::unnecessary_cast))]
use crate::ArcStr;
use core::ops::{Range, RangeBounds};
#[cfg(feature = "substr-usize-indices")]
type Idx = usize;
#[cfg(not(feature = "substr-usize-indices"))]
type Idx = u32;
#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
compile_error!(
"Non-32/64-bit pointers not supported right now due to insufficient \
testing on a platform like that. Please file a issue with the \
`arcstr` crate so we can talk about your use case if this is \
important to you."
);
#[derive(Clone)]
#[repr(C)] pub struct Substr(ArcStr, Idx, Idx);
#[inline]
#[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))]
#[allow(clippy::let_unit_value)]
const fn to_idx_const(i: usize) -> Idx {
const DUMMY: [(); 1] = [()];
let _ = DUMMY[i >> 32];
i as Idx
}
#[inline]
#[cfg(any(not(target_pointer_width = "64"), feature = "substr-usize-indices"))]
const fn to_idx_const(i: usize) -> Idx {
i as Idx
}
#[inline]
#[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))]
fn to_idx(i: usize) -> Idx {
if i > 0xffff_ffff {
index_overflow(i);
}
i as Idx
}
#[inline]
#[cfg(any(not(target_pointer_width = "64"), feature = "substr-usize-indices"))]
fn to_idx(i: usize) -> Idx {
i as Idx
}
#[cold]
#[inline(never)]
#[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))]
fn index_overflow(i: usize) -> ! {
panic!("The index {} is too large for arcstr::Substr (enable the `substr-usize-indices` feature in `arcstr` if you need this)", i);
}
#[cold]
#[inline(never)]
fn bad_substr_idx(s: &ArcStr, i: usize, e: usize) -> ! {
assert!(i <= e, "Bad substr range: start {} must be <= end {}", i, e);
let max = if cfg!(all(
target_pointer_width = "64",
not(feature = "substr-usize-indices")
)) {
u32::MAX as usize
} else {
usize::MAX
};
let len = s.len().min(max);
assert!(
e <= len,
"Bad substr range: end {} must be <= string length/index max size {}",
e,
len
);
assert!(
s.is_char_boundary(i) && s.is_char_boundary(e),
"Bad substr range: start and end must be on char boundaries"
);
unreachable!(
"[arcstr bug]: should have failed one of the above tests: \
please report me. debugging info: b={}, e={}, l={}, max={:#x}",
i,
e,
s.len(),
max
);
}
impl Substr {
#[inline]
pub const fn new() -> Self {
Substr(ArcStr::new(), 0, 0)
}
#[inline]
pub fn full(a: ArcStr) -> Self {
let l = to_idx(a.len());
Substr(a, 0, l)
}
#[inline]
pub(crate) fn from_parts(a: &ArcStr, range: impl RangeBounds<usize>) -> Self {
use core::ops::Bound;
let begin = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n + 1,
Bound::Excluded(&n) => n,
Bound::Unbounded => a.len(),
};
let _ = &a.as_str()[begin..end];
Self(ArcStr::clone(a), to_idx(begin), to_idx(end))
}
#[inline]
pub fn substr(&self, range: impl RangeBounds<usize>) -> Self {
use core::ops::Bound;
let my_end = self.2 as usize;
let begin = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n + 1,
Bound::Excluded(&n) => n,
Bound::Unbounded => self.len(),
};
let new_begin = self.1 as usize + begin;
let new_end = self.1 as usize + end;
if begin > end
|| end > my_end
|| !self.0.is_char_boundary(new_begin)
|| !self.0.is_char_boundary(new_end)
{
bad_substr_idx(&self.0, new_begin, new_end);
}
debug_assert!(self.0.get(new_begin..new_end).is_some());
debug_assert!(new_begin <= (Idx::MAX as usize) && new_end <= (Idx::MAX as usize));
Self(ArcStr::clone(&self.0), new_begin as Idx, new_end as Idx)
}
#[inline]
pub fn as_str(&self) -> &str {
self
}
#[inline]
pub fn len(&self) -> usize {
debug_assert!(self.2 >= self.1);
(self.2 - self.1) as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.2 == self.1
}
#[inline]
#[allow(clippy::inherent_to_string_shadow_display)]
pub fn to_string(&self) -> alloc::string::String {
#[cfg(not(feature = "std"))]
use alloc::borrow::ToOwned;
self.as_str().to_owned()
}
#[inline]
pub const unsafe fn from_parts_unchecked(s: ArcStr, range: Range<usize>) -> Self {
Self(s, to_idx_const(range.start), to_idx_const(range.end))
}
#[inline]
pub fn shallow_eq(this: &Self, o: &Self) -> bool {
ArcStr::ptr_eq(&this.0, &o.0) && (this.1 == o.1) && (this.2 == o.2)
}
#[inline]
pub fn parent(&self) -> &ArcStr {
&self.0
}
#[inline]
pub fn range(&self) -> Range<usize> {
(self.1 as usize)..(self.2 as usize)
}
pub fn substr_from(&self, substr: &str) -> Substr {
self.try_substr_from(substr)
.expect("non-substring passed to Substr::substr_from")
}
pub fn try_substr_from(&self, substr: &str) -> Option<Substr> {
if substr.is_empty() {
return Some(Substr::new());
}
let parent_ptr = self.0.as_ptr() as usize;
let self_start = parent_ptr + (self.1 as usize);
let self_end = parent_ptr + (self.2 as usize);
let substr_start = substr.as_ptr() as usize;
let substr_end = substr_start + substr.len();
if substr_start < self_start || substr_end > self_end {
return None;
}
let index = substr_start - self_start;
let end = index + substr.len();
Some(self.substr(index..end))
}
pub fn try_substr_using(&self, f: impl FnOnce(&str) -> &str) -> Option<Self> {
self.try_substr_from(f(self.as_str()))
}
pub fn substr_using(&self, f: impl FnOnce(&str) -> &str) -> Self {
self.substr_from(f(self.as_str()))
}
}
impl From<ArcStr> for Substr {
#[inline]
fn from(a: ArcStr) -> Self {
Self::full(a)
}
}
impl From<&ArcStr> for Substr {
#[inline]
fn from(a: &ArcStr) -> Self {
Self::full(a.clone())
}
}
impl core::ops::Deref for Substr {
type Target = str;
#[inline]
fn deref(&self) -> &str {
debug_assert!(self.0.get((self.1 as usize)..(self.2 as usize)).is_some());
unsafe { self.0.get_unchecked((self.1 as usize)..(self.2 as usize)) }
}
}
impl PartialEq for Substr {
#[inline]
fn eq(&self, o: &Self) -> bool {
Substr::shallow_eq(self, o) || PartialEq::eq(self.as_str(), o.as_str())
}
#[inline]
fn ne(&self, o: &Self) -> bool {
!Substr::shallow_eq(self, o) && PartialEq::ne(self.as_str(), o.as_str())
}
}
impl PartialEq<ArcStr> for Substr {
#[inline]
fn eq(&self, o: &ArcStr) -> bool {
(ArcStr::ptr_eq(&self.0, o) && (self.1 == 0) && (self.2 as usize == o.len()))
|| PartialEq::eq(self.as_str(), o.as_str())
}
#[inline]
fn ne(&self, o: &ArcStr) -> bool {
(!ArcStr::ptr_eq(&self.0, o) || (self.1 != 0) || (self.2 as usize != o.len()))
&& PartialEq::ne(self.as_str(), o.as_str())
}
}
impl PartialEq<Substr> for ArcStr {
#[inline]
fn eq(&self, o: &Substr) -> bool {
PartialEq::eq(o, self)
}
#[inline]
fn ne(&self, o: &Substr) -> bool {
PartialEq::ne(o, self)
}
}
impl Eq for Substr {}
impl PartialOrd for Substr {
#[inline]
fn partial_cmp(&self, s: &Self) -> Option<core::cmp::Ordering> {
Some(self.as_str().cmp(s.as_str()))
}
}
impl Ord for Substr {
#[inline]
fn cmp(&self, s: &Self) -> core::cmp::Ordering {
self.as_str().cmp(s.as_str())
}
}
impl core::hash::Hash for Substr {
#[inline]
fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
self.as_str().hash(h)
}
}
impl core::fmt::Debug for Substr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self.as_str(), f)
}
}
impl core::fmt::Display for Substr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_str(), f)
}
}
impl Default for Substr {
#[inline]
fn default() -> Self {
Self::new()
}
}
macro_rules! impl_from_via_arcstr {
($($SrcTy:ty),+) => {$(
impl From<$SrcTy> for Substr {
#[inline]
fn from(v: $SrcTy) -> Self {
Self::full(ArcStr::from(v))
}
}
)+};
}
impl_from_via_arcstr![
&str,
&mut str,
alloc::string::String,
&alloc::string::String,
alloc::boxed::Box<str>,
alloc::rc::Rc<str>,
alloc::sync::Arc<str>,
alloc::borrow::Cow<'_, str>
];
impl<'a> From<&'a Substr> for alloc::borrow::Cow<'a, str> {
#[inline]
fn from(s: &'a Substr) -> Self {
alloc::borrow::Cow::Borrowed(s)
}
}
impl<'a> From<Substr> for alloc::borrow::Cow<'a, str> {
#[inline]
fn from(s: Substr) -> Self {
if let Some(st) = ArcStr::as_static(&s.0) {
debug_assert!(st.get(s.range()).is_some());
alloc::borrow::Cow::Borrowed(unsafe { st.get_unchecked(s.range()) })
} else {
alloc::borrow::Cow::Owned(s.to_string())
}
}
}
macro_rules! impl_peq {
(@one $a:ty, $b:ty) => {
#[allow(clippy::extra_unused_lifetimes)]
impl<'a> PartialEq<$b> for $a {
#[inline]
fn eq(&self, s: &$b) -> bool {
PartialEq::eq(&self[..], &s[..])
}
#[inline]
fn ne(&self, s: &$b) -> bool {
PartialEq::ne(&self[..], &s[..])
}
}
};
($(($a:ty, $b:ty),)+) => {$(
impl_peq!(@one $a, $b);
impl_peq!(@one $b, $a);
)+};
}
impl_peq! {
(Substr, str),
(Substr, &'a str),
(Substr, alloc::string::String),
(Substr, alloc::borrow::Cow<'a, str>),
(Substr, alloc::boxed::Box<str>),
(Substr, alloc::sync::Arc<str>),
(Substr, alloc::rc::Rc<str>),
}
macro_rules! impl_index {
($($IdxT:ty,)*) => {$(
impl core::ops::Index<$IdxT> for Substr {
type Output = str;
#[inline]
fn index(&self, i: $IdxT) -> &Self::Output {
&self.as_str()[i]
}
}
)*};
}
impl_index! {
core::ops::RangeFull,
core::ops::Range<usize>,
core::ops::RangeFrom<usize>,
core::ops::RangeTo<usize>,
core::ops::RangeInclusive<usize>,
core::ops::RangeToInclusive<usize>,
}
impl AsRef<str> for Substr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<[u8]> for Substr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl core::borrow::Borrow<str> for Substr {
#[inline]
fn borrow(&self) -> &str {
self
}
}
impl core::str::FromStr for Substr {
type Err = core::convert::Infallible;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(ArcStr::from(s)))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[should_panic]
#[cfg(not(miri))] #[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))]
fn test_from_parts_unchecked_err() {
let s = crate::literal!("foo");
let _u = unsafe { Substr::from_parts_unchecked(s, 0x1_0000_0000usize..0x1_0000_0001) };
}
#[test]
fn test_from_parts_unchecked_valid() {
let s = crate::literal!("foobar");
let u = unsafe { Substr::from_parts_unchecked(s, 2..5) };
assert_eq!(&*u, "oba");
}
}