Skip to main content

smart_string/smart_string/
mod.rs

1use std::borrow::Borrow;
2use std::borrow::BorrowMut;
3use std::borrow::Cow;
4use std::cmp;
5use std::convert::Infallible;
6use std::fmt;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::ops;
10use std::rc::Rc;
11use std::string::FromUtf16Error;
12use std::string::FromUtf8Error;
13use std::sync::Arc;
14
15use crate::pascal_string;
16use crate::DisplayExt;
17use crate::PascalString;
18
19#[cfg(feature = "serde")]
20mod with_serde;
21
22mod error;
23pub use error::Utf16DecodeError;
24
25mod into_chars;
26pub use into_chars::IntoChars;
27
28pub const DEFAULT_CAPACITY: usize = 30;
29
30/// A string that stores short values on the stack and longer values on the heap.
31///
32/// ### Storage semantics (explicit conversions)
33///
34/// This type may **promote** from stack to heap during mutating operations (e.g. `push_str`, `reserve`) when the stack
35/// capacity is exceeded.
36///
37/// It does **not** automatically demote from heap to stack when the contents become shorter (including during
38/// in-place deserialization). This is intentional: implicit demotion can introduce surprising realloc/dealloc churn in
39/// real workloads (e.g. shorten → re-grow). If you want to attempt a demotion, call `try_into_stack`.
40#[derive(Clone)]
41pub enum SmartString<const N: usize = DEFAULT_CAPACITY> {
42    Heap(String),
43    Stack(PascalString<N>),
44}
45
46impl<const N: usize> SmartString<N> {
47    #[inline]
48    fn ensure_heap_mut(&mut self) -> &mut String {
49        if let Self::Stack(s) = self {
50            *self = Self::Heap(s.to_string());
51        }
52        match self {
53            Self::Heap(s) => s,
54            Self::Stack(_) => unreachable!("just promoted to heap"),
55        }
56    }
57
58    #[inline]
59    #[must_use]
60    pub const fn new() -> Self {
61        Self::Stack(PascalString::new())
62    }
63
64    #[inline]
65    #[must_use]
66    pub fn with_capacity(capacity: usize) -> Self {
67        if capacity <= N {
68            Self::new()
69        } else {
70            Self::Heap(String::with_capacity(capacity))
71        }
72    }
73
74    /// Fallible version of `with_capacity`.
75    ///
76    /// This never panics; allocation failures are returned as `TryReserveError`.
77    ///
78    /// ## Note on `String::try_with_capacity`
79    ///
80    /// Newer Rust versions provide `String::try_with_capacity`, but this crate’s MSRV is **Rust 1.59**
81    /// (see crate `README.md`). To keep `SmartString::try_with_capacity` available under MSRV, we
82    /// implement the equivalent semantics via `String::new()` + `try_reserve_exact`, which is
83    /// available starting from **Rust 1.57**.
84    ///
85    /// When MSRV is bumped to a version where `String::try_with_capacity` is available, this should
86    /// be revisited for closer upstream parity and to reduce “looks like a copy/paste bug” risk.
87    #[inline]
88    pub fn try_with_capacity(capacity: usize) -> Result<Self, std::collections::TryReserveError> {
89        if capacity <= N {
90            return Ok(Self::new());
91        }
92
93        let mut s = String::new();
94        // NOTE(MSRV): use `try_reserve_exact` instead of `String::try_with_capacity` (not available on our MSRV).
95        s.try_reserve_exact(capacity)?;
96        Ok(Self::Heap(s))
97    }
98
99    #[inline]
100    pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
101        String::from_utf8(vec).map(Self::Heap)
102    }
103
104    pub fn from_utf16(v: &[u16]) -> Result<Self, FromUtf16Error> {
105        String::from_utf16(v).map(|s| Self::from(s.as_str()))
106    }
107
108    #[must_use]
109    #[inline]
110    pub fn from_utf16_lossy(v: &[u16]) -> Self {
111        let s = String::from_utf16_lossy(v);
112        Self::from(s.as_str())
113    }
114
115    #[inline]
116    #[must_use]
117    pub fn as_str(&self) -> &str {
118        self
119    }
120
121    /// Returns the underlying UTF-8 bytes.
122    ///
123    /// This is equivalent to `self.as_str().as_bytes()`, but provided as an inherent method for
124    /// `std::string::String` API parity and rustdoc discoverability.
125    #[inline]
126    #[must_use]
127    pub fn as_bytes(&self) -> &[u8] {
128        self.as_str().as_bytes()
129    }
130
131    #[inline]
132    #[must_use]
133    pub fn len(&self) -> usize {
134        self.as_str().len()
135    }
136
137    #[inline]
138    #[must_use]
139    pub fn is_empty(&self) -> bool {
140        self.as_str().is_empty()
141    }
142
143    #[inline]
144    #[must_use]
145    pub fn as_mut_str(&mut self) -> &mut str {
146        self
147    }
148
149    #[inline]
150    pub fn is_heap(&self) -> bool {
151        matches!(self, Self::Heap(_))
152    }
153
154    #[inline]
155    pub fn is_stack(&self) -> bool {
156        matches!(self, Self::Stack(_))
157    }
158
159    #[inline]
160    #[must_use]
161    pub fn into_heap(self) -> Self {
162        Self::Heap(match self {
163            Self::Stack(s) => s.to_string(),
164            Self::Heap(s) => s,
165        })
166    }
167
168    #[inline]
169    #[must_use]
170    pub fn try_into_stack(self) -> Self {
171        match self {
172            Self::Stack(s) => Self::Stack(s),
173            Self::Heap(s) => match PascalString::try_from(s.as_str()) {
174                Ok(s) => Self::Stack(s),
175                Err(pascal_string::TryFromStrError::TooLong) => Self::Heap(s),
176            },
177        }
178    }
179
180    #[inline]
181    pub fn push_str(&mut self, string: &str) {
182        match self {
183            Self::Heap(s) => s.push_str(string),
184            Self::Stack(s) => match s.try_push_str(string) {
185                Ok(()) => (),
186                Err(pascal_string::TryFromStrError::TooLong) => {
187                    let mut new = String::with_capacity(s.len() + string.len());
188                    new.push_str(s.as_str());
189                    new.push_str(string);
190                    *self = Self::Heap(new);
191                }
192            },
193        }
194    }
195
196    #[inline]
197    pub fn capacity(&self) -> usize {
198        match self {
199            Self::Heap(s) => s.capacity(),
200            Self::Stack(s) => s.capacity(),
201        }
202    }
203
204    #[inline]
205    pub fn reserve(&mut self, additional: usize) {
206        match self {
207            Self::Heap(s) => s.reserve(additional),
208            Self::Stack(s) => {
209                if s.capacity() - s.len() < additional {
210                    let mut new = String::with_capacity(s.len() + additional);
211                    new.push_str(s.as_str());
212                    *self = Self::Heap(new);
213                }
214            }
215        }
216    }
217
218    pub fn reserve_exact(&mut self, additional: usize) {
219        match self {
220            Self::Heap(s) => s.reserve_exact(additional),
221            Self::Stack(s) => {
222                if s.capacity() - s.len() < additional {
223                    let mut new = String::new();
224                    new.reserve_exact(s.len() + additional);
225                    new.push_str(s.as_str());
226                    *self = Self::Heap(new);
227                }
228            }
229        }
230    }
231
232    pub fn try_reserve(
233        &mut self,
234        additional: usize,
235    ) -> Result<(), std::collections::TryReserveError> {
236        match self {
237            Self::Heap(s) => s.try_reserve(additional),
238            Self::Stack(s) => {
239                if s.capacity() - s.len() < additional {
240                    let mut new = String::new();
241                    new.try_reserve(s.len() + additional)?;
242                    new.push_str(s.as_str());
243                    *self = Self::Heap(new);
244                }
245                Ok(())
246            }
247        }
248    }
249
250    pub fn try_reserve_exact(
251        &mut self,
252        additional: usize,
253    ) -> Result<(), std::collections::TryReserveError> {
254        match self {
255            Self::Heap(s) => s.try_reserve_exact(additional),
256            Self::Stack(s) => {
257                if s.capacity() - s.len() < additional {
258                    let mut new = String::new();
259                    new.try_reserve_exact(s.len() + additional)?;
260                    new.push_str(s.as_str());
261                    *self = Self::Heap(new);
262                }
263                Ok(())
264            }
265        }
266    }
267
268    #[inline]
269    pub fn shrink_to_fit(&mut self) {
270        match self {
271            Self::Heap(s) => s.shrink_to_fit(),
272            Self::Stack(_) => (),
273        }
274    }
275
276    #[inline]
277    pub fn shrink_to(&mut self, min_capacity: usize) {
278        match self {
279            Self::Heap(s) => s.shrink_to(min_capacity),
280            Self::Stack(_) => (),
281        }
282    }
283
284    pub fn push(&mut self, ch: char) {
285        match self {
286            Self::Heap(s) => s.push(ch),
287            Self::Stack(s) => match s.try_push(ch) {
288                Ok(()) => (),
289                Err(pascal_string::TryFromStrError::TooLong) => {
290                    let mut new = String::with_capacity(s.len() + ch.len_utf8());
291                    new.push_str(s.as_str());
292                    new.push(ch);
293                    *self = Self::Heap(new);
294                }
295            },
296        }
297    }
298
299    #[inline]
300    pub fn truncate(&mut self, new_len: usize) {
301        match self {
302            Self::Heap(s) => s.truncate(new_len),
303            Self::Stack(s) => s.truncate(new_len),
304        }
305    }
306
307    #[inline]
308    pub fn pop(&mut self) -> Option<char> {
309        match self {
310            Self::Heap(s) => s.pop(),
311            Self::Stack(s) => s.pop(),
312        }
313    }
314
315    #[inline]
316    pub fn clear(&mut self) {
317        match self {
318            Self::Heap(s) => s.clear(),
319            Self::Stack(s) => s.clear(),
320        }
321    }
322
323    // --- String-like APIs that require heap delegation -------------------------------------------
324
325    /// Converts the `SmartString` into an iterator over its `char`s.
326    ///
327    /// The returned [`IntoChars`] iterator consumes `self`.  It supports forward and backward
328    /// traversal, exact remaining-char counting, and fused behaviour.
329    #[inline]
330    #[must_use]
331    pub fn into_chars(self) -> IntoChars<N> {
332        match self {
333            Self::Stack(s) => IntoChars::new_stack(s),
334            Self::Heap(s) => IntoChars::new_heap(s),
335        }
336    }
337
338    #[inline]
339    #[must_use]
340    pub fn into_string(self) -> String {
341        self.into()
342    }
343
344    #[inline]
345    #[must_use]
346    pub fn into_bytes(self) -> Vec<u8> {
347        self.into_string().into_bytes()
348    }
349
350    #[inline]
351    #[must_use]
352    pub fn into_boxed_str(self) -> Box<str> {
353        self.into_string().into_boxed_str()
354    }
355
356    /// Consumes the `SmartString` and leaks it, returning a `&'static mut str`.
357    ///
358    /// Available on Rust 1.72+ (`String::leak` stabilization). On older compilers this
359    /// method is silently absent, preserving the crate's MSRV of 1.59.
360    #[rustversion::since(1.72)]
361    #[inline]
362    #[must_use]
363    pub fn leak<'a>(self) -> &'a mut str {
364        self.into_string().leak()
365    }
366
367    #[inline]
368    #[must_use]
369    pub fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str> {
370        String::from_utf8_lossy(v)
371    }
372
373    /// Like `String::from_utf8_lossy_owned`: consumes the byte buffer and returns an owned string,
374    /// replacing invalid sequences with U+FFFD.
375    ///
376    /// This never panics. The returned value uses the stack variant when it fits.
377    #[inline]
378    #[must_use]
379    pub fn from_utf8_lossy_owned(v: Vec<u8>) -> Self {
380        let owned = match String::from_utf8(v) {
381            Ok(s) => s,
382            Err(e) => String::from_utf8_lossy(&e.into_bytes()).into_owned(),
383        };
384
385        if owned.len() <= N {
386            // Avoid panics: fall back to heap on unexpected conversion failure.
387            if let Ok(ps) = PascalString::<N>::try_from(owned.as_str()) {
388                return Self::Stack(ps);
389            }
390        }
391        Self::Heap(owned)
392    }
393
394    #[inline]
395    pub fn insert(&mut self, idx: usize, ch: char) {
396        match self {
397            Self::Heap(s) => s.insert(idx, ch),
398            Self::Stack(s) => match s.try_insert(idx, ch) {
399                Ok(()) => (),
400                Err(pascal_string::InsertError::TooLong) => self.ensure_heap_mut().insert(idx, ch),
401                Err(_) => panic!("invalid index or char boundary"),
402            },
403        }
404    }
405
406    #[inline]
407    pub fn insert_str(&mut self, idx: usize, string: &str) {
408        match self {
409            Self::Heap(s) => s.insert_str(idx, string),
410            Self::Stack(s) => match s.try_insert_str(idx, string) {
411                Ok(()) => (),
412                Err(pascal_string::InsertError::TooLong) => {
413                    self.ensure_heap_mut().insert_str(idx, string)
414                }
415                Err(_) => panic!("invalid index or char boundary"),
416            },
417        }
418    }
419
420    /// Inserts a string slice, truncating when stored on stack; returns the remainder that did not fit.
421    ///
422    /// - If this value is stored on the heap, insertion is complete and the remainder is always `""`.
423    /// - If this value is stored on the stack, insertion is best-effort and the remainder is returned.
424    ///
425    /// Panics if `idx` is out of bounds or not on a UTF-8 boundary (matches `String` semantics).
426    #[inline]
427    pub fn insert_str_truncated<'s>(&mut self, idx: usize, string: &'s str) -> &'s str {
428        self.try_insert_str_truncated(idx, string)
429            .expect("invalid index or char boundary")
430    }
431
432    /// Non-panicking variant of `insert_str_truncated`.
433    ///
434    /// Returns `InsertError` on invalid indices/boundaries.
435    #[inline]
436    pub fn try_insert_str_truncated<'s>(
437        &mut self,
438        idx: usize,
439        string: &'s str,
440    ) -> Result<&'s str, pascal_string::InsertError> {
441        match self {
442            Self::Heap(s) => {
443                let len = s.len();
444                if idx > len {
445                    return Err(pascal_string::InsertError::OutOfBounds { idx, len });
446                }
447                if !s.is_char_boundary(idx) {
448                    return Err(pascal_string::InsertError::NotCharBoundary { idx });
449                }
450                s.insert_str(idx, string);
451                Ok("")
452            }
453            Self::Stack(s) => s.try_insert_str_truncated(idx, string),
454        }
455    }
456
457    #[inline]
458    pub fn remove(&mut self, idx: usize) -> char {
459        match self {
460            Self::Heap(s) => s.remove(idx),
461            Self::Stack(s) => s.remove(idx),
462        }
463    }
464
465    #[inline]
466    pub fn retain<F>(&mut self, mut f: F)
467    where
468        F: FnMut(char) -> bool,
469    {
470        match self {
471            Self::Heap(s) => s.retain(f),
472            Self::Stack(s) => {
473                // `retain` can only keep or delete existing characters, so it cannot overflow capacity.
474                let mut out = PascalString::<N>::new();
475                for ch in s.as_str().chars() {
476                    if f(ch) {
477                        out.try_push(ch).expect("retain cannot overflow");
478                    }
479                }
480                *s = out;
481            }
482        }
483    }
484
485    #[inline]
486    pub fn drain<R>(&mut self, range: R) -> std::string::Drain<'_>
487    where
488        R: std::ops::RangeBounds<usize>,
489    {
490        self.ensure_heap_mut().drain(range)
491    }
492
493    #[inline]
494    pub fn split_off(&mut self, at: usize) -> Self {
495        match self {
496            Self::Heap(s) => {
497                let len = s.len();
498                assert!(at <= len, "index out of bounds");
499                assert!(s.is_char_boundary(at), "index is not a char boundary");
500
501                // Optimization: if the tail fits on the stack, avoid allocating a new `String` via
502                // `String::split_off`. Instead, copy the tail into a `PascalString` and truncate in place.
503                let tail = &s[at..];
504                if tail.len() <= N {
505                    let other = PascalString::try_from(tail)
506                        .expect("tail length checked against stack capacity");
507                    s.truncate(at);
508                    return SmartString::Stack(other);
509                }
510
511                SmartString::Heap(s.split_off(at))
512            }
513            Self::Stack(s) => {
514                let len = s.len();
515                assert!(at <= len, "index out of bounds");
516                assert!(s.is_char_boundary(at), "index is not a char boundary");
517                let tail = &s.as_str()[at..];
518                let other = PascalString::try_from(tail)
519                    .expect("tail of a PascalString must fit within the same capacity");
520                s.truncate(at);
521                SmartString::Stack(other)
522            }
523        }
524    }
525
526    #[inline]
527    pub fn replace_range<R>(&mut self, range: R, replace_with: &str)
528    where
529        R: std::ops::RangeBounds<usize>,
530    {
531        let s = self.as_str();
532        let len = s.len();
533
534        let start = match range.start_bound() {
535            std::ops::Bound::Included(&n) => n,
536            std::ops::Bound::Excluded(&n) => n + 1,
537            std::ops::Bound::Unbounded => 0,
538        };
539        let end = match range.end_bound() {
540            std::ops::Bound::Included(&n) => n + 1,
541            std::ops::Bound::Excluded(&n) => n,
542            std::ops::Bound::Unbounded => len,
543        };
544
545        match self {
546            Self::Heap(s) => s.replace_range(start..end, replace_with),
547            Self::Stack(ps) => match ps.try_replace_range_bounds(start, end, replace_with) {
548                Ok(()) => (),
549                Err(pascal_string::ReplaceRangeError::TooLong) => {
550                    self.ensure_heap_mut()
551                        .replace_range(start..end, replace_with);
552                }
553                Err(_) => panic!("invalid range or char boundary"),
554            },
555        }
556    }
557
558    /// Copies the byte range `src` from `self` and appends it to the end.
559    ///
560    /// Panics if the range is out of bounds or not on UTF-8 char boundaries.
561    pub fn extend_from_within<R: std::ops::RangeBounds<usize>>(&mut self, src: R) {
562        use std::ops::Bound;
563        let len = self.len();
564        let start = match src.start_bound() {
565            Bound::Included(&n) => n,
566            Bound::Excluded(&n) => n
567                .checked_add(1)
568                .expect("extend_from_within: start overflow"),
569            Bound::Unbounded => 0,
570        };
571        let end = match src.end_bound() {
572            Bound::Included(&n) => n.checked_add(1).expect("extend_from_within: end overflow"),
573            Bound::Excluded(&n) => n,
574            Bound::Unbounded => len,
575        };
576        assert!(
577            start <= end,
578            "extend_from_within: start ({start}) > end ({end})"
579        );
580        assert!(end <= len, "extend_from_within: end ({end}) > len ({len})");
581        let s = self.as_str();
582        assert!(
583            s.is_char_boundary(start),
584            "extend_from_within: start not on char boundary"
585        );
586        assert!(
587            s.is_char_boundary(end),
588            "extend_from_within: end not on char boundary"
589        );
590        // Extract the slice as an owned string to avoid borrow conflict
591        let to_append = self.as_str()[start..end].to_string();
592        self.push_str(&to_append);
593    }
594
595    /// Removes all non-overlapping occurrences of `pat` from the string in-place.
596    ///
597    /// If `pat` is empty, this is a no-op.
598    pub fn remove_matches(&mut self, pat: &str) {
599        if pat.is_empty() {
600            return;
601        }
602        match self {
603            Self::Stack(s) => s.remove_matches(pat),
604            Self::Heap(s) => {
605                // Manual implementation since String::remove_matches is post-MSRV (1.59).
606                let mut result = String::with_capacity(s.len());
607                let mut src = 0;
608                let pat_len = pat.len();
609                while src < s.len() {
610                    if s[src..].starts_with(pat) {
611                        src += pat_len;
612                    } else {
613                        let ch = s[src..].chars().next().unwrap();
614                        result.push(ch);
615                        src += ch.len_utf8();
616                    }
617                }
618                *s = result;
619            }
620        }
621    }
622
623    /// Removes all occurrences of the character `pat` from the string in-place.
624    ///
625    /// This is a convenience wrapper around `retain`.
626    #[inline]
627    pub fn remove_matches_char(&mut self, pat: char) {
628        self.retain(|c| c != pat);
629    }
630
631    /// Replaces the first occurrence of `pat` with `replacement`.
632    ///
633    /// If `pat` is not found, `self` is unchanged.
634    #[inline]
635    pub fn replace_first(&mut self, pat: &str, replacement: &str) {
636        if let Some(start) = self.as_str().find(pat) {
637            let end = start + pat.len();
638            self.replace_range(start..end, replacement);
639        }
640    }
641
642    /// Replaces the first occurrence of the character `pat` with `replacement`.
643    ///
644    /// If `pat` is not found, `self` is unchanged.
645    #[inline]
646    pub fn replace_first_char(&mut self, pat: char, replacement: &str) {
647        if let Some(start) = self.as_str().find(pat) {
648            let end = start + pat.len_utf8();
649            self.replace_range(start..end, replacement);
650        }
651    }
652
653    /// Replaces the last occurrence of `pat` with `replacement`.
654    ///
655    /// If `pat` is not found, `self` is unchanged.
656    #[inline]
657    pub fn replace_last(&mut self, pat: &str, replacement: &str) {
658        if let Some(start) = self.as_str().rfind(pat) {
659            let end = start + pat.len();
660            self.replace_range(start..end, replacement);
661        }
662    }
663
664    /// Replaces the last occurrence of the character `pat` with `replacement`.
665    ///
666    /// If `pat` is not found, `self` is unchanged.
667    #[inline]
668    pub fn replace_last_char(&mut self, pat: char, replacement: &str) {
669        if let Some(start) = self.as_str().rfind(pat) {
670            let end = start + pat.len_utf8();
671            self.replace_range(start..end, replacement);
672        }
673    }
674
675    /// Decode a UTF-16BE byte slice into a `SmartString`.
676    ///
677    /// Takes `&[u8]` (raw bytes in big-endian order), matching the upstream
678    /// `String::from_utf16be` signature.
679    ///
680    /// Returns [`Utf16DecodeError`] if the input contains an unpaired surrogate or
681    /// an odd number of bytes.
682    pub fn from_utf16be(v: &[u8]) -> Result<Self, Utf16DecodeError> {
683        decode_utf16_bytes(v, u16::from_be_bytes, false)
684    }
685
686    /// Decode a UTF-16BE byte slice into a `SmartString`, replacing invalid data with U+FFFD.
687    ///
688    /// Accepts an odd trailing byte or unpaired surrogates and substitutes U+FFFD in their place.
689    #[must_use]
690    pub fn from_utf16be_lossy(v: &[u8]) -> Self {
691        decode_utf16_bytes(v, u16::from_be_bytes, true).unwrap()
692    }
693
694    /// Decode a UTF-16LE byte slice into a `SmartString`.
695    ///
696    /// Takes `&[u8]` (raw bytes in little-endian order), matching the upstream
697    /// `String::from_utf16le` signature.
698    ///
699    /// Returns [`Utf16DecodeError`] if the input contains an unpaired surrogate or
700    /// an odd number of bytes.
701    pub fn from_utf16le(v: &[u8]) -> Result<Self, Utf16DecodeError> {
702        decode_utf16_bytes(v, u16::from_le_bytes, false)
703    }
704
705    /// Decode a UTF-16LE byte slice into a `SmartString`, replacing invalid data with U+FFFD.
706    ///
707    /// Accepts an odd trailing byte or unpaired surrogates and substitutes U+FFFD in their place.
708    #[must_use]
709    pub fn from_utf16le_lossy(v: &[u8]) -> Self {
710        decode_utf16_bytes(v, u16::from_le_bytes, true).unwrap()
711    }
712}
713
714// -- UTF-16 decode helper -------------------------------------------------------------------------
715
716/// Decode a UTF-16 byte sequence with explicit byte order into a `SmartString`.
717///
718/// `to_u16` converts a pair of bytes into a `u16` value (BE or LE).
719/// If `lossy` is `true`, invalid surrogates and odd trailing bytes are replaced with U+FFFD.
720/// If `lossy` is `false`, returns `Err` on any invalid surrogate sequence or odd byte count.
721fn decode_utf16_bytes<const N: usize>(
722    v: &[u8],
723    to_u16: fn([u8; 2]) -> u16,
724    lossy: bool,
725) -> Result<SmartString<N>, Utf16DecodeError> {
726    let pair_count = v.len() / 2;
727    let mut buf = String::with_capacity(pair_count); // at least one char per code unit
728
729    let mut i = 0;
730    while i < pair_count {
731        let code_unit = to_u16([v[i * 2], v[i * 2 + 1]]);
732
733        if (0xD800..=0xDBFF).contains(&code_unit) {
734            // High surrogate — must be followed by a low surrogate.
735            if i + 1 < pair_count {
736                let low = to_u16([v[(i + 1) * 2], v[(i + 1) * 2 + 1]]);
737                if (0xDC00..=0xDFFF).contains(&low) {
738                    // Valid surrogate pair.
739                    let cp = 0x10000 + ((code_unit as u32 - 0xD800) << 10) + (low as u32 - 0xDC00);
740                    if let Some(ch) = char::from_u32(cp) {
741                        buf.push(ch);
742                    } else if lossy {
743                        buf.push('\u{FFFD}');
744                    } else {
745                        return Err(Utf16DecodeError::new());
746                    }
747                    i += 2;
748                    continue;
749                }
750            }
751            // Unpaired high surrogate.
752            if lossy {
753                buf.push('\u{FFFD}');
754            } else {
755                return Err(Utf16DecodeError::new());
756            }
757        } else if (0xDC00..=0xDFFF).contains(&code_unit) {
758            // Unpaired low surrogate.
759            if lossy {
760                buf.push('\u{FFFD}');
761            } else {
762                return Err(Utf16DecodeError::new());
763            }
764        } else {
765            // BMP character — all values in 0x0000..=0xFFFF excluding surrogates are valid Unicode.
766            debug_assert!(
767                code_unit <= 0xD7FF || code_unit >= 0xE000,
768                "surrogate {:#06X} reached BMP branch",
769                code_unit,
770            );
771            // SAFETY: values 0x0000–0xD7FF and 0xE000–0xFFFF are all valid Unicode scalar values.
772            // The if/else chain above handles surrogates (0xD800–0xDFFF) in separate branches.
773            let ch = unsafe { char::from_u32_unchecked(code_unit as u32) };
774            buf.push(ch);
775        }
776        i += 1;
777    }
778
779    // Odd trailing byte — not a complete code unit.
780    if v.len() % 2 != 0 {
781        if lossy {
782            buf.push('\u{FFFD}');
783        } else {
784            return Err(Utf16DecodeError::new());
785        }
786    }
787
788    Ok(SmartString::from(buf.as_str()))
789}
790
791// -- Common traits --------------------------------------------------------------------------------
792
793impl<const N: usize> Default for SmartString<N> {
794    #[inline]
795    fn default() -> Self {
796        Self::new()
797    }
798}
799
800impl<T: ops::Deref<Target = str> + ?Sized, const CAPACITY: usize> PartialEq<T>
801    for SmartString<CAPACITY>
802{
803    #[inline(always)]
804    fn eq(&self, other: &T) -> bool {
805        self.as_str().eq(other.deref())
806    }
807}
808
809macro_rules! impl_reverse_eq_for_str_types {
810    ($($t:ty),*) => {
811        $(
812            impl<const N: usize> PartialEq<SmartString<N>> for $t {
813                #[inline(always)]
814                fn eq(&self, other: &SmartString<N>) -> bool {
815                    let a: &str = self.as_ref();
816                    let b = other.as_str();
817                    a.eq(b)
818                }
819            }
820
821            impl<const N: usize> PartialEq<SmartString<N>> for &$t {
822                #[inline(always)]
823                fn eq(&self, other: &SmartString<N>) -> bool {
824                    let a: &str = self.as_ref();
825                    let b = other.as_str();
826                    a.eq(b)
827                }
828            }
829
830            impl<const N: usize> PartialEq<SmartString<N>> for &mut $t {
831                #[inline(always)]
832                fn eq(&self, other: &SmartString<N>) -> bool {
833                    let a: &str = self.as_ref();
834                    let b = other.as_str();
835                    a.eq(b)
836                }
837            }
838        )*
839    };
840}
841
842impl_reverse_eq_for_str_types!(String, str, Cow<'_, str>, Box<str>, Rc<str>, Arc<str>);
843
844impl<const M: usize, const N: usize> PartialEq<SmartString<N>> for &PascalString<M> {
845    #[inline(always)]
846    fn eq(&self, other: &SmartString<N>) -> bool {
847        let a: &str = self.as_ref();
848        let b = other.as_str();
849        a.eq(b)
850    }
851}
852
853impl<const M: usize, const N: usize> PartialEq<SmartString<N>> for &mut PascalString<M> {
854    #[inline(always)]
855    fn eq(&self, other: &SmartString<N>) -> bool {
856        let a: &str = self.as_ref();
857        let b = other.as_str();
858        a.eq(b)
859    }
860}
861
862impl<const N: usize> Eq for SmartString<N> {}
863
864impl<T: ops::Deref<Target = str>, const N: usize> PartialOrd<T> for SmartString<N> {
865    #[inline(always)]
866    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
867        self.as_str().partial_cmp(other.deref())
868    }
869}
870
871impl<const N: usize> Ord for SmartString<N> {
872    #[inline(always)]
873    fn cmp(&self, other: &Self) -> cmp::Ordering {
874        self.as_str().cmp(other.as_str())
875    }
876}
877
878impl<const N: usize> Hash for SmartString<N> {
879    #[inline(always)]
880    fn hash<H: Hasher>(&self, state: &mut H) {
881        self.as_str().hash(state)
882    }
883}
884
885// -- Formatting -----------------------------------------------------------------------------------
886
887impl<const N: usize> fmt::Debug for SmartString<N> {
888    #[inline(always)]
889    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
890        let name: PascalString<39> = format_args!("SmartString<{N}>")
891            .try_to_fmt()
892            .unwrap_or_else(|_| "SmartString<?>".to_fmt());
893        f.debug_tuple(&name).field(&self.as_str()).finish()
894    }
895}
896
897impl<const N: usize> fmt::Display for SmartString<N> {
898    #[inline]
899    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
900        match self {
901            Self::Heap(s) => s.fmt(f),
902            Self::Stack(s) => s.fmt(f),
903        }
904    }
905}
906
907// -- Reference ------------------------------------------------------------------------------------
908
909impl<const N: usize> ops::Deref for SmartString<N> {
910    type Target = str;
911
912    #[inline]
913    fn deref(&self) -> &Self::Target {
914        match self {
915            Self::Heap(s) => s.deref(),
916            Self::Stack(s) => s.deref(),
917        }
918    }
919}
920
921impl<const N: usize> ops::DerefMut for SmartString<N> {
922    #[inline]
923    fn deref_mut(&mut self) -> &mut Self::Target {
924        match self {
925            Self::Heap(s) => s.deref_mut(),
926            Self::Stack(s) => s.deref_mut(),
927        }
928    }
929}
930
931impl<const N: usize> Borrow<str> for SmartString<N> {
932    #[inline(always)]
933    fn borrow(&self) -> &str {
934        self
935    }
936}
937
938impl<const N: usize> AsRef<str> for SmartString<N> {
939    #[inline(always)]
940    fn as_ref(&self) -> &str {
941        self
942    }
943}
944
945impl<const N: usize> AsRef<[u8]> for SmartString<N> {
946    #[inline(always)]
947    fn as_ref(&self) -> &[u8] {
948        self.as_bytes()
949    }
950}
951
952impl<const N: usize> AsMut<str> for SmartString<N> {
953    #[inline(always)]
954    fn as_mut(&mut self) -> &mut str {
955        self
956    }
957}
958
959impl<const N: usize> BorrowMut<str> for SmartString<N> {
960    #[inline(always)]
961    fn borrow_mut(&mut self) -> &mut str {
962        self
963    }
964}
965
966// -- Conversion -----------------------------------------------------------------------------------
967
968impl<const N: usize> From<String> for SmartString<N> {
969    #[inline]
970    fn from(s: String) -> Self {
971        Self::Heap(s)
972    }
973}
974
975impl<const N: usize> From<SmartString<N>> for String {
976    #[inline]
977    fn from(s: SmartString<N>) -> Self {
978        match s {
979            SmartString::Heap(s) => s,
980            SmartString::Stack(s) => s.to_string(),
981        }
982    }
983}
984
985impl<const N: usize> std::str::FromStr for SmartString<N> {
986    type Err = Infallible;
987
988    #[inline]
989    fn from_str(s: &str) -> Result<Self, Self::Err> {
990        Ok(Self::from(s))
991    }
992}
993
994impl<const M: usize, const N: usize> From<PascalString<M>> for SmartString<N> {
995    #[inline]
996    fn from(s: PascalString<M>) -> Self {
997        PascalString::try_from(s.as_str())
998            .map(Self::Stack)
999            .unwrap_or_else(|pascal_string::TryFromStrError::TooLong| Self::Heap(s.to_string()))
1000    }
1001}
1002
1003impl<const N: usize> From<&str> for SmartString<N> {
1004    #[inline]
1005    fn from(s: &str) -> Self {
1006        PascalString::try_from(s)
1007            .map(Self::Stack)
1008            .unwrap_or_else(|pascal_string::TryFromStrError::TooLong| Self::Heap(String::from(s)))
1009    }
1010}
1011
1012impl<const N: usize> From<char> for SmartString<N> {
1013    #[inline]
1014    fn from(ch: char) -> Self {
1015        let mut s = Self::new();
1016        s.push(ch);
1017        s
1018    }
1019}
1020
1021impl<const N: usize> From<&String> for SmartString<N> {
1022    #[inline]
1023    fn from(s: &String) -> Self {
1024        Self::from(s.as_str())
1025    }
1026}
1027
1028impl<const N: usize> From<&mut str> for SmartString<N> {
1029    #[inline]
1030    fn from(s: &mut str) -> Self {
1031        Self::from(&*s)
1032    }
1033}
1034
1035impl<const N: usize> From<Box<str>> for SmartString<N> {
1036    #[inline]
1037    fn from(s: Box<str>) -> Self {
1038        Self::from(s.as_ref())
1039    }
1040}
1041
1042impl<const N: usize> From<&Box<str>> for SmartString<N> {
1043    #[inline]
1044    fn from(s: &Box<str>) -> Self {
1045        Self::from(s.as_ref())
1046    }
1047}
1048
1049impl<const N: usize> From<Rc<str>> for SmartString<N> {
1050    #[inline]
1051    fn from(s: Rc<str>) -> Self {
1052        Self::from(s.as_ref())
1053    }
1054}
1055
1056impl<const N: usize> From<&Rc<str>> for SmartString<N> {
1057    #[inline]
1058    fn from(s: &Rc<str>) -> Self {
1059        Self::from(s.as_ref())
1060    }
1061}
1062
1063impl<const N: usize> From<Arc<str>> for SmartString<N> {
1064    #[inline]
1065    fn from(s: Arc<str>) -> Self {
1066        Self::from(s.as_ref())
1067    }
1068}
1069
1070impl<const N: usize> From<&Arc<str>> for SmartString<N> {
1071    #[inline]
1072    fn from(s: &Arc<str>) -> Self {
1073        Self::from(s.as_ref())
1074    }
1075}
1076
1077impl<const N: usize> From<Cow<'_, str>> for SmartString<N> {
1078    #[inline]
1079    fn from(s: Cow<'_, str>) -> Self {
1080        match s {
1081            Cow::Borrowed(s) => Self::from(s),
1082            Cow::Owned(s) => Self::Heap(s),
1083        }
1084    }
1085}
1086
1087impl<const N: usize> From<&Cow<'_, str>> for SmartString<N> {
1088    #[inline]
1089    fn from(s: &Cow<'_, str>) -> Self {
1090        Self::from(s.as_ref())
1091    }
1092}
1093
1094impl<const N: usize> FromIterator<char> for SmartString<N> {
1095    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
1096        let mut s = Self::new();
1097        s.extend(iter);
1098        s
1099    }
1100}
1101
1102impl<'a, const N: usize> FromIterator<&'a str> for SmartString<N> {
1103    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
1104        let mut s = Self::new();
1105        s.extend(iter);
1106        s
1107    }
1108}
1109
1110impl<const N: usize> Extend<char> for SmartString<N> {
1111    #[inline]
1112    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
1113        for ch in iter {
1114            self.push(ch);
1115        }
1116    }
1117}
1118
1119impl<'a, const N: usize> Extend<&'a str> for SmartString<N> {
1120    #[inline]
1121    fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
1122        for s in iter {
1123            self.push_str(s);
1124        }
1125    }
1126}
1127
1128impl<'a, const N: usize> Extend<&'a char> for SmartString<N> {
1129    #[inline]
1130    fn extend<T: IntoIterator<Item = &'a char>>(&mut self, iter: T) {
1131        for ch in iter {
1132            self.push(*ch);
1133        }
1134    }
1135}
1136
1137impl<const N: usize> Extend<String> for SmartString<N> {
1138    #[inline]
1139    fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
1140        for s in iter {
1141            self.push_str(&s);
1142        }
1143    }
1144}
1145
1146impl<'a, const N: usize> Extend<&'a String> for SmartString<N> {
1147    #[inline]
1148    fn extend<T: IntoIterator<Item = &'a String>>(&mut self, iter: T) {
1149        for s in iter {
1150            self.push_str(s.as_str());
1151        }
1152    }
1153}
1154
1155// -- IO -------------------------------------------------------------------------------------------
1156
1157impl<const N: usize> fmt::Write for SmartString<N> {
1158    #[inline]
1159    fn write_str(&mut self, s: &str) -> fmt::Result {
1160        self.push_str(s);
1161        Ok(())
1162    }
1163}
1164
1165impl<const N: usize> From<SmartString<N>> for Box<str> {
1166    #[inline]
1167    fn from(s: SmartString<N>) -> Self {
1168        s.into_boxed_str()
1169    }
1170}
1171
1172impl<const N: usize> From<SmartString<N>> for Vec<u8> {
1173    #[inline]
1174    fn from(s: SmartString<N>) -> Self {
1175        s.into_bytes()
1176    }
1177}
1178
1179impl<const N: usize> From<SmartString<N>> for Rc<str> {
1180    #[inline]
1181    fn from(s: SmartString<N>) -> Self {
1182        // NOTE: converting an owned string into Rc/Arc necessarily allocates an Rc/Arc-managed buffer.
1183        // We go through `String` here for correctness and std-like ergonomics; if this turns out hot,
1184        // we can evaluate alternative paths and document the cost model.
1185        Rc::from(s.into_string())
1186    }
1187}
1188
1189impl<const N: usize> From<SmartString<N>> for Arc<str> {
1190    #[inline]
1191    fn from(s: SmartString<N>) -> Self {
1192        // See note on `Rc<str>` above.
1193        Arc::from(s.into_string())
1194    }
1195}
1196
1197// -- ops ------------------------------------------------------------------------------------------
1198
1199impl<const N: usize, T: ops::Deref<Target = str>> ops::Add<T> for SmartString<N> {
1200    type Output = Self;
1201
1202    #[inline]
1203    fn add(mut self, rhs: T) -> Self::Output {
1204        self.push_str(&rhs);
1205        self
1206    }
1207}
1208
1209impl<const N: usize, T: ops::Deref<Target = str>> ops::AddAssign<T> for SmartString<N> {
1210    #[inline]
1211    fn add_assign(&mut self, rhs: T) {
1212        self.push_str(rhs.deref());
1213    }
1214}
1215
1216// -- Tests ----------------------------------------------------------------------------------------
1217
1218#[cfg(test)]
1219mod tests {
1220    use std::mem;
1221
1222    use super::*;
1223
1224    #[test]
1225    fn test_size() {
1226        // Default stack capacity is 30 bytes, corresponding to 32 bytes of the enum.
1227        assert_eq!(mem::size_of::<SmartString>(), 32);
1228
1229        // NOTE: the enum layout for very small capacities depends on rustc version.
1230        // Newer compilers can represent these as 24 bytes on 64-bit platforms, while older compilers
1231        // (including our MSRV) may use 32 bytes. Both are acceptable; the important property is that
1232        // the default type stays small (32 bytes) and larger capacities grow in pointer-sized steps.
1233        let small_sizes = [
1234            mem::size_of::<SmartString<0>>(),
1235            mem::size_of::<SmartString<1>>(),
1236            mem::size_of::<SmartString<15>>(),
1237        ];
1238        for size in small_sizes {
1239            assert!(
1240                size == 24 || size == 32,
1241                "unexpected SmartString small size: {size}"
1242            );
1243        }
1244
1245        // It is unclear why the size of the enum grows to 32 bytes
1246        // starting from 17 bytes of size for the stack variant.
1247        assert_eq!(mem::size_of::<SmartString<16>>(), 32);
1248        assert_eq!(mem::size_of::<SmartString<22>>(), 32);
1249
1250        // The size of the enum is expected to be 32 bytes for the following capacities.
1251        assert_eq!(mem::size_of::<SmartString<23>>(), 32);
1252        assert_eq!(mem::size_of::<SmartString<30>>(), 32);
1253
1254        // Additional bytes of capacity increases the size of the enum by size of a pointer
1255        // (8 bytes on 64-bit platforms) by steps of size of a pointer.
1256        assert_eq!(mem::size_of::<SmartString<31>>(), 40);
1257        assert_eq!(mem::size_of::<SmartString<38>>(), 40);
1258
1259        assert_eq!(mem::size_of::<SmartString<39>>(), 48);
1260        assert_eq!(mem::size_of::<SmartString<46>>(), 48);
1261    }
1262
1263    #[test]
1264    fn test_from_str_picks_stack_or_heap() {
1265        let s = SmartString::<4>::from("abcd");
1266        assert!(s.is_stack());
1267
1268        let s = SmartString::<4>::from("abcde");
1269        assert!(s.is_heap());
1270    }
1271
1272    #[test]
1273    fn test_push_str_transitions_stack_to_heap() {
1274        let mut s = SmartString::<4>::new();
1275        assert!(s.is_stack());
1276
1277        s.push_str("ab");
1278        assert!(s.is_stack());
1279        assert_eq!(s.as_str(), "ab");
1280
1281        s.push_str("cd");
1282        assert!(s.is_stack());
1283        assert_eq!(s.as_str(), "abcd");
1284
1285        // Overflow stack capacity => move to heap.
1286        s.push_str("e");
1287        assert!(s.is_heap());
1288        assert_eq!(s.as_str(), "abcde");
1289    }
1290
1291    #[test]
1292    fn test_push_char_and_unicode_boundaries() {
1293        let mut s = SmartString::<4>::new();
1294        s.push('€'); // 3 bytes
1295        assert!(s.is_stack());
1296        assert_eq!(s.as_str(), "€");
1297
1298        s.push('a'); // +1 byte => exactly 4
1299        assert!(s.is_stack());
1300        assert_eq!(s.as_str(), "€a");
1301
1302        // +1 byte => overflow => heap
1303        s.push('b');
1304        assert!(s.is_heap());
1305        assert_eq!(s.as_str(), "€ab");
1306
1307        // Truncate on UTF-8 boundary should work for both stack and heap variants.
1308        s.truncate(3);
1309        assert_eq!(s.as_str(), "€");
1310        assert_eq!(s.pop(), Some('€'));
1311        assert_eq!(s.as_str(), "");
1312        assert_eq!(s.pop(), None);
1313    }
1314
1315    #[test]
1316    fn test_reserve_transitions_stack_to_heap() {
1317        let mut s = SmartString::<4>::from("ab");
1318        assert!(s.is_stack());
1319
1320        // Fits within remaining stack capacity.
1321        s.reserve(2);
1322        assert!(s.is_stack());
1323
1324        // Requires more than remaining capacity => transition to heap.
1325        s.reserve(3);
1326        assert!(s.is_heap());
1327        assert_eq!(s.as_str(), "ab");
1328    }
1329
1330    #[test]
1331    fn test_try_into_stack_converts_short_heap_string() {
1332        let s = SmartString::<4>::from(String::from("abc"));
1333        assert!(s.is_heap());
1334
1335        let s = s.try_into_stack();
1336        assert!(s.is_stack());
1337        assert_eq!(s.as_str(), "abc");
1338    }
1339
1340    #[test]
1341    fn test_into_heap_always_returns_heap_variant() {
1342        let s = SmartString::<4>::from("abc");
1343        assert!(s.is_stack());
1344
1345        let s = s.into_heap();
1346        assert!(s.is_heap());
1347        assert_eq!(s.as_str(), "abc");
1348    }
1349
1350    #[test]
1351    fn test_truncate_does_not_demote_heap_to_stack() {
1352        let mut s = SmartString::<4>::from("abcde");
1353        assert!(s.is_heap());
1354
1355        s.truncate(2);
1356        assert_eq!(s.as_str(), "ab");
1357        assert!(s.is_heap());
1358
1359        let s = s.try_into_stack();
1360        assert_eq!(s.as_str(), "ab");
1361        assert!(s.is_stack());
1362    }
1363
1364    #[test]
1365    fn test_try_reserve_transitions_stack_to_heap() {
1366        let mut s = SmartString::<4>::from("ab");
1367        assert!(s.is_stack());
1368
1369        // Fits within remaining stack capacity.
1370        s.try_reserve(2).unwrap();
1371        assert!(s.is_stack());
1372
1373        // Exceeds remaining stack capacity => transition to heap.
1374        s.try_reserve(3).unwrap();
1375        assert!(s.is_heap());
1376        assert_eq!(s.as_str(), "ab");
1377    }
1378
1379    #[test]
1380    fn test_try_reserve_exact_transitions_stack_to_heap() {
1381        let mut s = SmartString::<4>::from("ab");
1382        assert!(s.is_stack());
1383
1384        // Exceeds remaining stack capacity => transition to heap.
1385        s.try_reserve_exact(3).unwrap();
1386        assert!(s.is_heap());
1387        assert_eq!(s.as_str(), "ab");
1388    }
1389
1390    #[test]
1391    fn test_extend_str_transitions_stack_to_heap() {
1392        let mut s = SmartString::<4>::new();
1393        s.extend(["ab", "cd"]);
1394        assert!(s.is_stack());
1395        assert_eq!(s.as_str(), "abcd");
1396
1397        s.extend(["e"]);
1398        assert!(s.is_heap());
1399        assert_eq!(s.as_str(), "abcde");
1400    }
1401
1402    #[test]
1403    fn test_extend_char_unicode_boundaries() {
1404        let mut s = SmartString::<4>::new();
1405        s.extend(['€', 'a']); // 3 + 1 bytes
1406        assert!(s.is_stack());
1407        assert_eq!(s.as_str(), "€a");
1408
1409        s.extend(['b']);
1410        assert!(s.is_heap());
1411        assert_eq!(s.as_str(), "€ab");
1412    }
1413
1414    #[test]
1415    fn test_add_assign() {
1416        let mut s = SmartString::<4>::from("a");
1417        s += "bcd";
1418        assert!(s.is_stack());
1419        assert_eq!(s.as_str(), "abcd");
1420
1421        s += "e";
1422        assert!(s.is_heap());
1423        assert_eq!(s.as_str(), "abcde");
1424    }
1425
1426    #[test]
1427    fn test_insert_and_remove_promotes_to_heap() {
1428        let mut s = SmartString::<8>::from("ab");
1429        assert!(s.is_stack());
1430
1431        s.insert(1, '€');
1432        assert!(s.is_stack());
1433        assert_eq!(s.as_str(), "a€b");
1434
1435        let removed = s.remove(1);
1436        assert_eq!(removed, '€');
1437        assert_eq!(s.as_str(), "ab");
1438    }
1439
1440    #[test]
1441    fn test_insert_promotes_to_heap_when_overflow() {
1442        let mut s = SmartString::<4>::from("ab");
1443        assert!(s.is_stack());
1444
1445        // inserting "€" (3 bytes) into "ab" (2 bytes) => 5 bytes, overflows stack cap => heap
1446        s.insert(1, '€');
1447        assert!(s.is_heap());
1448        assert_eq!(s.as_str(), "a€b");
1449    }
1450
1451    #[test]
1452    fn test_insert_str_truncated_on_stack() {
1453        let mut s = SmartString::<4>::from("ab");
1454        assert!(s.is_stack());
1455
1456        // insert at idx=1: only 2 bytes available, so only "cd" fits (not "cde").
1457        let rem = s.insert_str_truncated(1, "cde");
1458        assert_eq!(s.as_str(), "acdb");
1459        assert_eq!(rem, "e");
1460        assert!(s.is_stack());
1461    }
1462
1463    #[test]
1464    fn test_split_off_returns_stack_when_possible() {
1465        let mut s = SmartString::<8>::from("hello!");
1466        assert!(s.is_stack());
1467
1468        let other = s.split_off(5);
1469        assert_eq!(s.as_str(), "hello");
1470        assert_eq!(other.as_str(), "!");
1471        assert!(other.is_stack());
1472        assert!(s.is_stack());
1473    }
1474
1475    #[test]
1476    fn test_split_off_on_heap_avoids_alloc_when_tail_fits_stack() {
1477        let mut s = SmartString::<4>::from("abcde"); // heap (len 5 > 4)
1478        assert!(s.is_heap());
1479
1480        let other = s.split_off(4); // tail is "e" (fits stack)
1481        assert_eq!(s.as_str(), "abcd");
1482        assert!(s.is_heap()); // no implicit demotion
1483        assert_eq!(other.as_str(), "e");
1484        assert!(other.is_stack());
1485    }
1486
1487    #[test]
1488    fn test_retain_keeps_stack_when_possible() {
1489        let mut s = SmartString::<8>::from("a1b2c3");
1490        assert!(s.is_stack());
1491        s.retain(|ch| ch.is_ascii_alphabetic());
1492        assert_eq!(s.as_str(), "abc");
1493        assert!(s.is_stack());
1494    }
1495
1496    #[test]
1497    fn test_replace_range() {
1498        let mut s = SmartString::<8>::from("ab");
1499        s.replace_range(1..1, "cd");
1500        assert_eq!(s.as_str(), "acdb");
1501        assert!(s.is_stack());
1502    }
1503
1504    #[test]
1505    fn test_len_and_is_empty() {
1506        let s = SmartString::<4>::new();
1507        assert!(s.is_empty());
1508        assert_eq!(s.len(), 0);
1509
1510        let s = SmartString::<4>::from("ab");
1511        assert!(!s.is_empty());
1512        assert_eq!(s.len(), 2);
1513    }
1514
1515    #[test]
1516    fn test_from_string_refs_and_smart_extend_refs() {
1517        let base = String::from("ab");
1518        let s = SmartString::<4>::from(&base);
1519        assert!(s.is_stack());
1520        assert_eq!(s.as_str(), "ab");
1521
1522        let mut s = SmartString::<4>::new();
1523        let euro = '€';
1524        let a = 'a';
1525        s.extend([&euro, &a]);
1526        assert!(s.is_stack());
1527        assert_eq!(s.as_str(), "€a");
1528
1529        let b = String::from("b");
1530        s.extend([&b]);
1531        assert!(s.is_heap());
1532        assert_eq!(s.as_str(), "€ab");
1533    }
1534
1535    #[test]
1536    fn test_into_boxed_str() {
1537        let boxed = SmartString::<4>::from("ab").into_boxed_str();
1538        assert_eq!(&*boxed, "ab");
1539    }
1540
1541    #[rustversion::since(1.72)]
1542    #[test]
1543    fn test_leak() {
1544        let leaked: &'static mut str = SmartString::<4>::from("ab").leak();
1545        leaked.make_ascii_uppercase();
1546        assert_eq!(leaked, "AB");
1547    }
1548
1549    #[test]
1550    fn test_from_utf8_lossy() {
1551        let s = SmartString::<4>::from_utf8_lossy(&[0x66, 0x6f, 0x6f]);
1552        assert_eq!(s, "foo");
1553
1554        let s = SmartString::<4>::from_utf8_lossy(&[0xff]);
1555        assert!(matches!(s, Cow::Owned(_)));
1556    }
1557
1558    #[test]
1559    fn test_from_utf8_lossy_owned_picks_stack_when_possible() {
1560        let s = SmartString::<4>::from_utf8_lossy_owned(b"ab".to_vec());
1561        assert!(s.is_stack());
1562        assert_eq!(s.as_str(), "ab");
1563
1564        // U+FFFD is 3 bytes, so it fits into capacity=4.
1565        let s = SmartString::<4>::from_utf8_lossy_owned(vec![0xff]);
1566        assert!(s.is_stack());
1567        assert_eq!(s.as_str(), "�");
1568    }
1569
1570    #[test]
1571    fn test_try_with_capacity_picks_stack_or_heap() {
1572        let s = SmartString::<4>::try_with_capacity(3).unwrap();
1573        assert!(s.is_stack());
1574        assert_eq!(s.capacity(), 4);
1575
1576        let s = SmartString::<4>::try_with_capacity(10).unwrap();
1577        assert!(s.is_heap());
1578        assert!(s.capacity() >= 10);
1579    }
1580
1581    #[test]
1582    fn test_from_char_picks_stack_or_heap() {
1583        let s = SmartString::<4>::from('€'); // 3 bytes
1584        assert!(s.is_stack());
1585        assert_eq!(s.as_str(), "€");
1586
1587        let s = SmartString::<2>::from('€'); // won't fit (3 bytes)
1588        assert!(s.is_heap());
1589        assert_eq!(s.as_str(), "€");
1590    }
1591
1592    #[test]
1593    fn test_from_ref_str_containers_and_into_box_str() {
1594        let b: Box<str> = "ab".into();
1595        let r: Rc<str> = Rc::from("ab");
1596        let a: Arc<str> = Arc::from("ab");
1597
1598        assert_eq!(SmartString::<4>::from(&b).as_str(), "ab");
1599        assert_eq!(SmartString::<4>::from(&r).as_str(), "ab");
1600        assert_eq!(SmartString::<4>::from(&a).as_str(), "ab");
1601
1602        let boxed: Box<str> = SmartString::<4>::from("ab").into();
1603        assert_eq!(&*boxed, "ab");
1604    }
1605
1606    #[test]
1607    fn test_from_cow_ref() {
1608        let borrowed: Cow<'_, str> = Cow::Borrowed("ab");
1609        let owned: Cow<'_, str> = Cow::Owned(String::from("ab"));
1610        assert_eq!(SmartString::<4>::from(&borrowed).as_str(), "ab");
1611        assert_eq!(SmartString::<4>::from(&owned).as_str(), "ab");
1612    }
1613
1614    #[test]
1615    fn test_into_vec_u8_rc_arc_str() {
1616        let bytes: Vec<u8> = SmartString::<4>::from("ab").into();
1617        assert_eq!(bytes, b"ab");
1618
1619        let rc: Rc<str> = SmartString::<4>::from("ab").into();
1620        assert_eq!(&*rc, "ab");
1621
1622        let arc: Arc<str> = SmartString::<4>::from("ab").into();
1623        assert_eq!(&*arc, "ab");
1624    }
1625
1626    // -- from_utf16be / from_utf16le tests --------------------------------------------------------
1627
1628    #[test]
1629    fn test_utf16be_ascii_hi_lands_on_stack() {
1630        // "Hi" in UTF-16BE: U+0048, U+0069
1631        let bytes = [0x00u8, 0x48, 0x00, 0x69];
1632        let s = SmartString::<30>::from_utf16be(&bytes).unwrap();
1633        assert_eq!(s.as_str(), "Hi");
1634        assert!(s.is_stack());
1635    }
1636
1637    #[test]
1638    fn test_utf16be_long_string_lands_on_heap() {
1639        // 20 BMP characters encoded as UTF-16BE = 40 bytes; each is 1–3 UTF-8 bytes.
1640        // Use ASCII codepoints so the total UTF-8 size equals 20 bytes, which still fits on the
1641        // default 30-byte stack.  Instead use non-ASCII multi-byte chars to force heap:
1642        // U+4F60 ("你") = 3 UTF-8 bytes.  11 copies → 33 UTF-8 bytes > 30-byte stack.
1643        let code_unit: u16 = 0x4F60; // 你
1644        let mut bytes = Vec::new();
1645        for _ in 0..11 {
1646            bytes.extend_from_slice(&code_unit.to_be_bytes());
1647        }
1648        let s = SmartString::<30>::from_utf16be(&bytes).unwrap();
1649        assert_eq!(s.as_str(), "你你你你你你你你你你你");
1650        assert!(s.is_heap());
1651    }
1652
1653    #[test]
1654    fn test_utf16be_surrogate_pair_musical_symbol() {
1655        // U+1D11E MUSICAL SYMBOL G CLEF encoded as UTF-16BE surrogate pair: D834 DD1E
1656        let bytes = [0xD8u8, 0x34, 0xDD, 0x1E];
1657        let s = SmartString::<30>::from_utf16be(&bytes).unwrap();
1658        assert_eq!(s.as_str(), "\u{1D11E}");
1659    }
1660
1661    #[test]
1662    fn test_utf16be_unpaired_high_surrogate_is_err() {
1663        // High surrogate D800 with no following low surrogate.
1664        let bytes = [0xD8u8, 0x00, 0x00, 0x41]; // D800, then 'A'
1665        assert!(SmartString::<30>::from_utf16be(&bytes).is_err());
1666    }
1667
1668    #[test]
1669    fn test_utf16be_unpaired_low_surrogate_is_err() {
1670        // Low surrogate DC00 with no preceding high surrogate.
1671        let bytes = [0xDCu8, 0x00, 0x00, 0x41]; // DC00, then 'A'
1672        assert!(SmartString::<30>::from_utf16be(&bytes).is_err());
1673    }
1674
1675    #[test]
1676    fn test_utf16be_empty_input() {
1677        let s = SmartString::<30>::from_utf16be(&[]).unwrap();
1678        assert_eq!(s.as_str(), "");
1679        assert!(s.is_stack());
1680    }
1681
1682    #[test]
1683    fn test_utf16be_odd_byte_count_is_err() {
1684        let bytes = [0x00u8, 0x48, 0x00]; // "H" + one trailing byte
1685        assert!(SmartString::<30>::from_utf16be(&bytes).is_err());
1686    }
1687
1688    #[test]
1689    fn test_utf16be_bmp_chinese() {
1690        // "你好" in UTF-16BE: U+4F60, U+597D
1691        let bytes = [0x4Fu8, 0x60, 0x59, 0x7D];
1692        let s = SmartString::<30>::from_utf16be(&bytes).unwrap();
1693        assert_eq!(s.as_str(), "你好");
1694    }
1695
1696    // -- from_utf16be_lossy -----------------------------------------------------------------------
1697
1698    #[test]
1699    fn test_utf16be_lossy_valid_input_unchanged() {
1700        let bytes = [0x00u8, 0x48, 0x00, 0x69]; // "Hi"
1701        let s = SmartString::<30>::from_utf16be_lossy(&bytes);
1702        assert_eq!(s.as_str(), "Hi");
1703    }
1704
1705    #[test]
1706    fn test_utf16be_lossy_unpaired_high_surrogate_replaced() {
1707        // High surrogate D800 followed by non-surrogate 'A'.
1708        let bytes = [0xD8u8, 0x00, 0x00, 0x41];
1709        let s = SmartString::<30>::from_utf16be_lossy(&bytes);
1710        assert_eq!(s.as_str(), "\u{FFFD}A");
1711    }
1712
1713    #[test]
1714    fn test_utf16be_lossy_unpaired_low_surrogate_replaced() {
1715        let bytes = [0xDCu8, 0x00, 0x00, 0x41]; // DC00, 'A'
1716        let s = SmartString::<30>::from_utf16be_lossy(&bytes);
1717        assert_eq!(s.as_str(), "\u{FFFD}A");
1718    }
1719
1720    #[test]
1721    fn test_utf16be_lossy_odd_trailing_byte_replaced() {
1722        let bytes = [0x00u8, 0x48, 0xFF]; // 'H' + one orphan byte
1723        let s = SmartString::<30>::from_utf16be_lossy(&bytes);
1724        assert_eq!(s.as_str(), "H\u{FFFD}");
1725    }
1726
1727    // -- from_utf16le / from_utf16le_lossy -------------------------------------------------------
1728
1729    #[test]
1730    fn test_utf16le_ascii_hi_lands_on_stack() {
1731        // "Hi" in UTF-16LE: U+0048, U+0069 → bytes reversed per code unit
1732        let bytes = [0x48u8, 0x00, 0x69, 0x00];
1733        let s = SmartString::<30>::from_utf16le(&bytes).unwrap();
1734        assert_eq!(s.as_str(), "Hi");
1735        assert!(s.is_stack());
1736    }
1737
1738    #[test]
1739    fn test_utf16le_long_string_lands_on_heap() {
1740        // Same heap test as BE, but with LE byte order.
1741        let code_unit: u16 = 0x4F60; // 你
1742        let mut bytes = Vec::new();
1743        for _ in 0..11 {
1744            bytes.extend_from_slice(&code_unit.to_le_bytes());
1745        }
1746        let s = SmartString::<30>::from_utf16le(&bytes).unwrap();
1747        assert_eq!(s.as_str(), "你你你你你你你你你你你");
1748        assert!(s.is_heap());
1749    }
1750
1751    #[test]
1752    fn test_utf16le_surrogate_pair_musical_symbol() {
1753        // U+1D11E in UTF-16LE: 34D8 1EDD
1754        let bytes = [0x34u8, 0xD8, 0x1E, 0xDD];
1755        let s = SmartString::<30>::from_utf16le(&bytes).unwrap();
1756        assert_eq!(s.as_str(), "\u{1D11E}");
1757    }
1758
1759    #[test]
1760    fn test_utf16le_unpaired_high_surrogate_is_err() {
1761        let bytes = [0x00u8, 0xD8, 0x41, 0x00]; // D800 LE, then 'A' LE
1762        assert!(SmartString::<30>::from_utf16le(&bytes).is_err());
1763    }
1764
1765    #[test]
1766    fn test_utf16le_unpaired_low_surrogate_is_err() {
1767        let bytes = [0x00u8, 0xDC, 0x41, 0x00]; // DC00 LE, then 'A' LE
1768        assert!(SmartString::<30>::from_utf16le(&bytes).is_err());
1769    }
1770
1771    #[test]
1772    fn test_utf16le_empty_input() {
1773        let s = SmartString::<30>::from_utf16le(&[]).unwrap();
1774        assert_eq!(s.as_str(), "");
1775    }
1776
1777    #[test]
1778    fn test_utf16le_odd_byte_count_is_err() {
1779        let bytes = [0x48u8, 0x00, 0x00]; // 'H' LE + one trailing byte
1780        assert!(SmartString::<30>::from_utf16le(&bytes).is_err());
1781    }
1782
1783    #[test]
1784    fn test_utf16le_bmp_chinese() {
1785        // "你好" in UTF-16LE
1786        let bytes = [0x60u8, 0x4F, 0x7D, 0x59];
1787        let s = SmartString::<30>::from_utf16le(&bytes).unwrap();
1788        assert_eq!(s.as_str(), "你好");
1789    }
1790
1791    #[test]
1792    fn test_utf16le_lossy_unpaired_surrogate_replaced() {
1793        let bytes = [0x00u8, 0xD8, 0x41, 0x00]; // D800 LE + 'A' LE
1794        let s = SmartString::<30>::from_utf16le_lossy(&bytes);
1795        assert_eq!(s.as_str(), "\u{FFFD}A");
1796    }
1797
1798    #[test]
1799    fn test_utf16le_lossy_odd_trailing_byte_replaced() {
1800        let bytes = [0x48u8, 0x00, 0xFF]; // 'H' LE + orphan
1801        let s = SmartString::<30>::from_utf16le_lossy(&bytes);
1802        assert_eq!(s.as_str(), "H\u{FFFD}");
1803    }
1804
1805    #[test]
1806    fn test_utf16_decode_error_display() {
1807        let err = Utf16DecodeError::new();
1808        assert_eq!(err.to_string(), "invalid utf-16: lone surrogate found");
1809    }
1810
1811    #[test]
1812    fn test_utf16be_stack_vs_heap_awareness() {
1813        // 2 chars × 2 bytes/char = 4 bytes input → 2 UTF-8 bytes → fits on a 4-byte stack.
1814        let bytes = [0x00u8, 0x48, 0x00, 0x69]; // "Hi"
1815        let stack = SmartString::<4>::from_utf16be(&bytes).unwrap();
1816        assert!(stack.is_stack());
1817
1818        // 3 × U+4F60 = 9 UTF-8 bytes > 4-byte stack → heap.
1819        let code_unit: u16 = 0x4F60;
1820        let mut long_bytes = Vec::new();
1821        for _ in 0..3 {
1822            long_bytes.extend_from_slice(&code_unit.to_be_bytes());
1823        }
1824        let heap = SmartString::<4>::from_utf16be(&long_bytes).unwrap();
1825        assert!(heap.is_heap());
1826    }
1827
1828    // -- extend_from_within tests -------------------------------------------------------------------
1829
1830    #[test]
1831    fn test_extend_from_within_stack() {
1832        let mut s = SmartString::<10>::from("abc");
1833        s.extend_from_within(0..2);
1834        assert_eq!(s.as_str(), "abcab");
1835        assert!(s.is_stack());
1836    }
1837
1838    #[test]
1839    fn test_extend_from_within_promotes_to_heap() {
1840        let mut s = SmartString::<4>::from("abc");
1841        s.extend_from_within(..); // "abcabc" = 6 bytes > 4
1842        assert_eq!(s.as_str(), "abcabc");
1843        assert!(s.is_heap());
1844    }
1845
1846    #[test]
1847    fn test_extend_from_within_heap() {
1848        let mut s = SmartString::<2>::from("hello");
1849        assert!(s.is_heap());
1850        s.extend_from_within(1..3);
1851        assert_eq!(s.as_str(), "helloel");
1852    }
1853
1854    #[test]
1855    fn test_extend_from_within_empty_range() {
1856        let mut s = SmartString::<10>::from("abc");
1857        s.extend_from_within(1..1);
1858        assert_eq!(s.as_str(), "abc");
1859    }
1860
1861    #[test]
1862    fn test_extend_from_within_full_range() {
1863        let mut s = SmartString::<10>::from("ab");
1864        s.extend_from_within(..);
1865        assert_eq!(s.as_str(), "abab");
1866    }
1867
1868    #[test]
1869    #[should_panic]
1870    fn test_extend_from_within_non_char_boundary() {
1871        let mut s = SmartString::<10>::from("a€b"); // € is 3 bytes
1872        s.extend_from_within(1..3); // mid-€ boundaries
1873    }
1874
1875    #[test]
1876    #[should_panic]
1877    fn test_extend_from_within_out_of_bounds() {
1878        let mut s = SmartString::<10>::from("abc");
1879        s.extend_from_within(0..10);
1880    }
1881
1882    // -- remove_matches tests -----------------------------------------------------------------------
1883
1884    #[test]
1885    fn test_remove_matches_stack_single() {
1886        let mut s = SmartString::<10>::from("abcabc");
1887        s.remove_matches("b");
1888        assert_eq!(s.as_str(), "acac");
1889    }
1890
1891    #[test]
1892    fn test_remove_matches_stack_multiple() {
1893        let mut s = SmartString::<20>::from("aXbXcX");
1894        s.remove_matches("X");
1895        assert_eq!(s.as_str(), "abc");
1896    }
1897
1898    #[test]
1899    fn test_remove_matches_no_match() {
1900        let mut s = SmartString::<10>::from("hello");
1901        s.remove_matches("xyz");
1902        assert_eq!(s.as_str(), "hello");
1903    }
1904
1905    #[test]
1906    fn test_remove_matches_empty_pattern() {
1907        let mut s = SmartString::<10>::from("hello");
1908        s.remove_matches("");
1909        assert_eq!(s.as_str(), "hello");
1910    }
1911
1912    #[test]
1913    fn test_remove_matches_entire_string() {
1914        let mut s = SmartString::<10>::from("aaa");
1915        s.remove_matches("aaa");
1916        assert_eq!(s.as_str(), "");
1917    }
1918
1919    #[test]
1920    fn test_remove_matches_heap() {
1921        let mut s = SmartString::<2>::from("hello world");
1922        assert!(s.is_heap());
1923        s.remove_matches("o");
1924        assert_eq!(s.as_str(), "hell wrld");
1925    }
1926
1927    #[test]
1928    fn test_remove_matches_multibyte() {
1929        let mut s = SmartString::<20>::from("a€b€c");
1930        s.remove_matches("€");
1931        assert_eq!(s.as_str(), "abc");
1932    }
1933
1934    #[test]
1935    fn test_remove_matches_char_variant() {
1936        let mut s = SmartString::<10>::from("abcabc");
1937        s.remove_matches_char('b');
1938        assert_eq!(s.as_str(), "acac");
1939    }
1940
1941    // -- replace_first / replace_last tests ---------------------------------------------------------
1942
1943    #[test]
1944    fn test_replace_first_same_length() {
1945        let mut s = SmartString::<10>::from("abcabc");
1946        s.replace_first("b", "X");
1947        assert_eq!(s.as_str(), "aXcabc");
1948        assert!(s.is_stack());
1949    }
1950
1951    #[test]
1952    fn test_replace_first_shorter() {
1953        let mut s = SmartString::<10>::from("abcabc");
1954        s.replace_first("bc", "Z");
1955        assert_eq!(s.as_str(), "aZabc");
1956    }
1957
1958    #[test]
1959    fn test_replace_first_longer_fits_stack() {
1960        let mut s = SmartString::<20>::from("abcabc");
1961        s.replace_first("b", "XYZ");
1962        assert_eq!(s.as_str(), "aXYZcabc");
1963        assert!(s.is_stack());
1964    }
1965
1966    #[test]
1967    fn test_replace_first_longer_promotes_to_heap() {
1968        let mut s = SmartString::<4>::from("abc");
1969        s.replace_first("b", "XXXX"); // "aXXXXc" = 6 > 4
1970        assert_eq!(s.as_str(), "aXXXXc");
1971        assert!(s.is_heap());
1972    }
1973
1974    #[test]
1975    fn test_replace_first_no_match() {
1976        let mut s = SmartString::<10>::from("hello");
1977        s.replace_first("xyz", "!");
1978        assert_eq!(s.as_str(), "hello");
1979    }
1980
1981    #[test]
1982    fn test_replace_last_gets_last() {
1983        let mut s = SmartString::<10>::from("abcabc");
1984        s.replace_last("b", "X");
1985        assert_eq!(s.as_str(), "abcaXc");
1986    }
1987
1988    #[test]
1989    fn test_replace_first_vs_last() {
1990        let mut s1 = SmartString::<20>::from("aXbXcXd");
1991        let mut s2 = s1.clone();
1992        s1.replace_first("X", ".");
1993        s2.replace_last("X", ".");
1994        assert_eq!(s1.as_str(), "a.bXcXd");
1995        assert_eq!(s2.as_str(), "aXbXc.d");
1996    }
1997
1998    #[test]
1999    fn test_replace_first_at_boundaries() {
2000        let mut s = SmartString::<10>::from("abc");
2001        s.replace_first("a", "X");
2002        assert_eq!(s.as_str(), "Xbc");
2003
2004        let mut s = SmartString::<10>::from("abc");
2005        s.replace_first("c", "X");
2006        assert_eq!(s.as_str(), "abX");
2007    }
2008
2009    #[test]
2010    fn test_replace_first_char_variant() {
2011        let mut s = SmartString::<10>::from("a€b€c");
2012        s.replace_first_char('€', "X");
2013        assert_eq!(s.as_str(), "aXb€c");
2014    }
2015
2016    #[test]
2017    fn test_replace_last_char_variant() {
2018        let mut s = SmartString::<10>::from("a€b€c");
2019        s.replace_last_char('€', "X");
2020        assert_eq!(s.as_str(), "a€bXc");
2021    }
2022
2023    #[test]
2024    fn test_from_utf16_stack_aware() {
2025        // "Hi" = 2 bytes UTF-8, should land on stack with capacity 4
2026        let s = SmartString::<4>::from_utf16(&[0x48u16, 0x69]).unwrap();
2027        assert!(s.is_stack());
2028        assert_eq!(s.as_str(), "Hi");
2029    }
2030
2031    #[test]
2032    fn test_from_utf16_lossy_stack_aware() {
2033        let s = SmartString::<4>::from_utf16_lossy(&[0x48u16, 0x69]);
2034        assert!(s.is_stack());
2035        assert_eq!(s.as_str(), "Hi");
2036    }
2037
2038    // -- proptest: UTF-16 decode properties -----------------------------------------------------------
2039
2040    mod proptest_utf16 {
2041        use proptest::prelude::*;
2042
2043        use super::*;
2044
2045        /// Encode a string as UTF-16 big-endian bytes.
2046        fn encode_utf16be(s: &str) -> Vec<u8> {
2047            let mut bytes = Vec::new();
2048            for u in s.encode_utf16() {
2049                bytes.extend_from_slice(&u.to_be_bytes());
2050            }
2051            bytes
2052        }
2053
2054        /// Encode a string as UTF-16 little-endian bytes.
2055        fn encode_utf16le(s: &str) -> Vec<u8> {
2056            let mut bytes = Vec::new();
2057            for u in s.encode_utf16() {
2058                bytes.extend_from_slice(&u.to_le_bytes());
2059            }
2060            bytes
2061        }
2062
2063        /// Reference decode: convert &[u8] (BE) to &[u16], then use String::from_utf16.
2064        fn reference_decode_be(bytes: &[u8]) -> Result<String, ()> {
2065            if bytes.len() % 2 != 0 {
2066                return Err(());
2067            }
2068            let u16s: Vec<u16> = bytes
2069                .chunks_exact(2)
2070                .map(|c| u16::from_be_bytes([c[0], c[1]]))
2071                .collect();
2072            String::from_utf16(&u16s).map_err(|_| ())
2073        }
2074
2075        /// Reference decode: convert &[u8] (LE) to &[u16], then use String::from_utf16.
2076        fn reference_decode_le(bytes: &[u8]) -> Result<String, ()> {
2077            if bytes.len() % 2 != 0 {
2078                return Err(());
2079            }
2080            let u16s: Vec<u16> = bytes
2081                .chunks_exact(2)
2082                .map(|c| u16::from_le_bytes([c[0], c[1]]))
2083                .collect();
2084            String::from_utf16(&u16s).map_err(|_| ())
2085        }
2086
2087        proptest! {
2088            // Round-trip: encode valid string → decode → same string
2089            #[test]
2090            fn roundtrip_be(s in "\\PC{0,100}") {
2091                let bytes = encode_utf16be(&s);
2092                let decoded = SmartString::<30>::from_utf16be(&bytes).unwrap();
2093                prop_assert_eq!(decoded.as_str(), s.as_str());
2094            }
2095
2096            #[test]
2097            fn roundtrip_le(s in "\\PC{0,100}") {
2098                let bytes = encode_utf16le(&s);
2099                let decoded = SmartString::<30>::from_utf16le(&bytes).unwrap();
2100                prop_assert_eq!(decoded.as_str(), s.as_str());
2101            }
2102
2103            // Match reference: for arbitrary even-length bytes, our BE decode agrees with std
2104            #[test]
2105            fn matches_reference_be(bytes in proptest::collection::vec(any::<u8>(), 0..200)) {
2106                let ref_result = reference_decode_be(&bytes);
2107                let our_result = SmartString::<30>::from_utf16be(&bytes);
2108                match (ref_result, our_result) {
2109                    (Ok(ref_s), Ok(our_s)) => prop_assert_eq!(ref_s.as_str(), our_s.as_str()),
2110                    (Err(_), Err(_)) => {} // both reject — fine
2111                    (Ok(_), Err(_)) => {
2112                        // Odd-length bytes: reference skips them, we reject. That's OK
2113                        // since reference_decode_be also returns Err on odd length.
2114                        prop_assert!(false, "reference accepted but we rejected");
2115                    }
2116                    (Err(_), Ok(_)) => {
2117                        prop_assert!(false, "we accepted but reference rejected");
2118                    }
2119                }
2120            }
2121
2122            #[test]
2123            fn matches_reference_le(bytes in proptest::collection::vec(any::<u8>(), 0..200)) {
2124                let ref_result = reference_decode_le(&bytes);
2125                let our_result = SmartString::<30>::from_utf16le(&bytes);
2126                match (ref_result, our_result) {
2127                    (Ok(ref_s), Ok(our_s)) => prop_assert_eq!(ref_s.as_str(), our_s.as_str()),
2128                    (Err(_), Err(_)) => {}
2129                    (Ok(_), Err(_)) => prop_assert!(false, "reference accepted but we rejected"),
2130                    (Err(_), Ok(_)) => prop_assert!(false, "we accepted but reference rejected"),
2131                }
2132            }
2133
2134            // Lossy always produces valid UTF-8, never panics
2135            #[test]
2136            fn lossy_be_never_panics(bytes in proptest::collection::vec(any::<u8>(), 0..200)) {
2137                let result = SmartString::<30>::from_utf16be_lossy(&bytes);
2138                // If it didn't panic, the string is valid UTF-8 by construction (it's a SmartString).
2139                prop_assert!(result.len() <= result.as_str().len() + 1); // just access it
2140            }
2141
2142            #[test]
2143            fn lossy_le_never_panics(bytes in proptest::collection::vec(any::<u8>(), 0..200)) {
2144                let result = SmartString::<30>::from_utf16le_lossy(&bytes);
2145                prop_assert!(result.len() <= result.as_str().len() + 1);
2146            }
2147
2148            // Stack-awareness: short decoded strings land on stack
2149            #[test]
2150            fn short_strings_on_stack_be(s in "[a-z]{0,10}") {
2151                let bytes = encode_utf16be(&s);
2152                let decoded = SmartString::<30>::from_utf16be(&bytes).unwrap();
2153                prop_assert!(decoded.is_stack(), "expected stack for {:?} (len {})", s, s.len());
2154            }
2155
2156            #[test]
2157            fn short_strings_on_stack_le(s in "[a-z]{0,10}") {
2158                let bytes = encode_utf16le(&s);
2159                let decoded = SmartString::<30>::from_utf16le(&bytes).unwrap();
2160                prop_assert!(decoded.is_stack(), "expected stack for {:?} (len {})", s, s.len());
2161            }
2162        }
2163    }
2164
2165    // -- drain tests ---------------------------------------------------------------------------------
2166
2167    #[test]
2168    fn test_drain_full_range() {
2169        let mut s = SmartString::<10>::from("hello");
2170        let drained: String = s.drain(..).collect();
2171        assert_eq!(drained, "hello");
2172        assert_eq!(s.as_str(), "");
2173    }
2174
2175    #[test]
2176    fn test_drain_partial_range() {
2177        let mut s = SmartString::<10>::from("abcdef");
2178        let drained: String = s.drain(1..4).collect();
2179        assert_eq!(drained, "bcd");
2180        assert_eq!(s.as_str(), "aef");
2181    }
2182
2183    #[test]
2184    fn test_drain_promotes_stack_to_heap() {
2185        let mut s = SmartString::<10>::from("abc");
2186        assert!(s.is_stack());
2187        // drain forces heap promotion
2188        let drained: String = s.drain(1..2).collect();
2189        assert_eq!(drained, "b");
2190        assert_eq!(s.as_str(), "ac");
2191        assert!(s.is_heap()); // promoted by ensure_heap_mut
2192    }
2193
2194    #[test]
2195    fn test_drain_empty_range() {
2196        let mut s = SmartString::<10>::from("hello");
2197        let drained: String = s.drain(2..2).collect();
2198        assert_eq!(drained, "");
2199        assert_eq!(s.as_str(), "hello");
2200    }
2201
2202    #[test]
2203    fn test_drain_multibyte() {
2204        let mut s = SmartString::<20>::from("a€b");
2205        let drained: String = s.drain(1..4).collect(); // drain the 3-byte '€'
2206        assert_eq!(drained, "€");
2207        assert_eq!(s.as_str(), "ab");
2208    }
2209
2210    // -- into_bytes / into_string / from_utf8 tests --------------------------------------------------
2211
2212    #[test]
2213    fn test_into_bytes_stack() {
2214        let s = SmartString::<10>::from("abc");
2215        assert!(s.is_stack());
2216        assert_eq!(s.into_bytes(), b"abc".to_vec());
2217    }
2218
2219    #[test]
2220    fn test_into_bytes_heap() {
2221        let s = SmartString::<2>::from("hello");
2222        assert!(s.is_heap());
2223        assert_eq!(s.into_bytes(), b"hello".to_vec());
2224    }
2225
2226    #[test]
2227    fn test_into_string_stack() {
2228        let s = SmartString::<10>::from("abc");
2229        assert_eq!(s.into_string(), "abc");
2230    }
2231
2232    #[test]
2233    fn test_into_string_heap() {
2234        let s = SmartString::<2>::from("hello");
2235        assert_eq!(s.into_string(), "hello");
2236    }
2237
2238    #[test]
2239    fn test_from_utf8_valid() {
2240        let s = SmartString::<10>::from_utf8(b"hello".to_vec()).unwrap();
2241        assert_eq!(s.as_str(), "hello");
2242        assert!(s.is_heap()); // from_utf8 takes Vec, always heap
2243    }
2244
2245    #[test]
2246    fn test_from_utf8_invalid() {
2247        let result = SmartString::<10>::from_utf8(vec![0xff, 0xfe]);
2248        assert!(result.is_err());
2249    }
2250
2251    #[test]
2252    fn test_from_utf8_empty() {
2253        let s = SmartString::<10>::from_utf8(Vec::new()).unwrap();
2254        assert_eq!(s.as_str(), "");
2255    }
2256
2257    // -- shrink_to / shrink_to_fit tests -------------------------------------------------------------
2258
2259    #[test]
2260    fn test_shrink_to_fit_heap() {
2261        let mut s = SmartString::<4>::from("hello world, this is a long string");
2262        assert!(s.is_heap());
2263        let cap_before = s.capacity();
2264        s.shrink_to_fit();
2265        assert!(s.capacity() <= cap_before);
2266        assert_eq!(s.as_str(), "hello world, this is a long string");
2267    }
2268
2269    #[test]
2270    fn test_shrink_to_fit_stack_noop() {
2271        let mut s = SmartString::<10>::from("ab");
2272        assert!(s.is_stack());
2273        s.shrink_to_fit(); // should not panic or change anything
2274        assert_eq!(s.as_str(), "ab");
2275        assert!(s.is_stack());
2276    }
2277
2278    #[test]
2279    fn test_shrink_to_heap() {
2280        let mut s = SmartString::<4>::with_capacity(100);
2281        s.push_str("hello");
2282        assert!(s.is_heap());
2283        let cap_before = s.capacity();
2284        s.shrink_to(10);
2285        assert!(s.capacity() <= cap_before);
2286        assert_eq!(s.as_str(), "hello");
2287    }
2288
2289    #[test]
2290    fn test_shrink_to_stack_noop() {
2291        let mut s = SmartString::<10>::from("ab");
2292        assert!(s.is_stack());
2293        s.shrink_to(0);
2294        assert_eq!(s.as_str(), "ab");
2295        assert!(s.is_stack());
2296    }
2297}