1#[cfg(feature = "alloc")]
4use alloc::alloc::{Layout, alloc, dealloc};
5#[cfg(feature = "alloc")]
6use alloc::string::String;
7use core::borrow::Borrow;
8use core::cmp::Ordering;
9use core::fmt::{self, Debug, Formatter};
10use core::hash::{Hash, Hasher};
11use core::mem;
12use core::ops::Deref;
13use core::ptr;
14use static_assertions::const_assert;
15use static_assertions::const_assert_eq;
16
17use crate::value::{TypeTag, Value};
18
19const SAFE_FLAG: usize = 1usize << (usize::BITS - 1);
22
23#[repr(C, align(8))]
25struct StringHeader {
26 len: usize,
29 }
31
32impl StringHeader {
33 #[inline]
35 fn actual_len(&self) -> usize {
36 self.len & !SAFE_FLAG
37 }
38
39 #[inline]
41 fn is_safe(&self) -> bool {
42 self.len & SAFE_FLAG != 0
43 }
44}
45
46#[repr(transparent)]
51#[derive(Clone)]
52pub struct VString(pub(crate) Value);
53
54impl VString {
55 const INLINE_WORD_BYTES: usize = mem::size_of::<usize>();
56 const INLINE_DATA_OFFSET: usize = 1;
57 const INLINE_CAP_BYTES: usize = Self::INLINE_WORD_BYTES - Self::INLINE_DATA_OFFSET;
58 pub(crate) const INLINE_LEN_MAX: usize = {
59 const LEN_MASK: usize = (1 << (8 - 3)) - 1;
60 let cap = mem::size_of::<usize>() - 1;
61 if cap < LEN_MASK { cap } else { LEN_MASK }
62 };
63 const INLINE_LEN_SHIFT: u8 = 3;
64
65 fn layout(len: usize) -> Layout {
66 Layout::new::<StringHeader>()
67 .extend(Layout::array::<u8>(len).unwrap())
68 .unwrap()
69 .0
70 .pad_to_align()
71 }
72
73 #[cfg(feature = "alloc")]
74 fn alloc(s: &str) -> *mut StringHeader {
75 unsafe {
76 let layout = Self::layout(s.len());
77 let ptr = alloc(layout).cast::<StringHeader>();
78 (*ptr).len = s.len();
79
80 let data_ptr = ptr.add(1).cast::<u8>();
82 ptr::copy_nonoverlapping(s.as_ptr(), data_ptr, s.len());
83
84 ptr
85 }
86 }
87
88 #[cfg(feature = "alloc")]
89 fn dealloc_ptr(ptr: *mut StringHeader) {
90 unsafe {
91 let len = (*ptr).actual_len();
92 let layout = Self::layout(len);
93 dealloc(ptr.cast::<u8>(), layout);
94 }
95 }
96
97 fn header(&self) -> &StringHeader {
98 debug_assert!(!self.is_inline());
99 unsafe { &*(self.0.heap_ptr() as *const StringHeader) }
100 }
101
102 fn data_ptr(&self) -> *const u8 {
103 debug_assert!(!self.is_inline());
104 unsafe { (self.0.heap_ptr() as *const StringHeader).add(1).cast() }
107 }
108
109 #[cfg(feature = "alloc")]
111 #[must_use]
112 pub fn new(s: &str) -> Self {
113 if Self::can_inline(s.len()) {
114 return Self::new_inline(s);
115 }
116 unsafe {
117 let ptr = Self::alloc(s);
118 VString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
119 }
120 }
121
122 #[cfg(feature = "alloc")]
124 #[must_use]
125 pub fn empty() -> Self {
126 Self::new_inline("")
127 }
128
129 #[must_use]
131 pub fn len(&self) -> usize {
132 if self.is_inline() {
133 self.inline_len()
134 } else {
135 self.header().actual_len()
136 }
137 }
138
139 #[must_use]
141 pub fn is_empty(&self) -> bool {
142 self.len() == 0
143 }
144
145 #[must_use]
147 pub fn as_str(&self) -> &str {
148 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
149 }
150
151 #[must_use]
153 pub fn as_bytes(&self) -> &[u8] {
154 if self.is_inline() {
155 unsafe { core::slice::from_raw_parts(self.inline_data_ptr(), self.inline_len()) }
156 } else {
157 unsafe { core::slice::from_raw_parts(self.data_ptr(), self.len()) }
158 }
159 }
160
161 pub(crate) fn clone_impl(&self) -> Value {
162 if self.is_safe() {
163 VSafeString::new(self.as_str()).0
165 } else {
166 VString::new(self.as_str()).0
167 }
168 }
169
170 pub(crate) fn drop_impl(&mut self) {
171 if self.is_inline() {
172 return;
173 }
174 unsafe {
175 Self::dealloc_ptr(self.0.heap_ptr_mut().cast());
176 }
177 }
178
179 #[inline]
180 fn is_inline(&self) -> bool {
181 self.0.is_inline_string()
182 }
183
184 #[inline]
185 fn can_inline(len: usize) -> bool {
186 len <= Self::INLINE_LEN_MAX && len <= Self::INLINE_CAP_BYTES
187 }
188
189 #[inline]
190 fn inline_meta_ptr(&self) -> *const u8 {
191 self as *const VString as *const u8
192 }
193
194 #[inline]
195 fn inline_data_ptr(&self) -> *const u8 {
196 unsafe { self.inline_meta_ptr().add(Self::INLINE_DATA_OFFSET) }
197 }
198
199 #[inline]
200 fn inline_len(&self) -> usize {
201 debug_assert!(self.is_inline());
202 unsafe { (*self.inline_meta_ptr() >> Self::INLINE_LEN_SHIFT) as usize }
203 }
204
205 #[cfg(feature = "alloc")]
206 fn new_inline(s: &str) -> Self {
207 debug_assert!(Self::can_inline(s.len()));
208 let mut storage = [0u8; Self::INLINE_WORD_BYTES];
209 storage[0] = ((s.len() as u8) << Self::INLINE_LEN_SHIFT) | (TypeTag::InlineString as u8);
210 storage[Self::INLINE_DATA_OFFSET..Self::INLINE_DATA_OFFSET + s.len()]
211 .copy_from_slice(s.as_bytes());
212 let bits = usize::from_ne_bytes(storage);
213 VString(unsafe { Value::from_bits(bits) })
214 }
215
216 #[cfg(feature = "alloc")]
218 fn alloc_safe(s: &str) -> *mut StringHeader {
219 unsafe {
220 let layout = Self::layout(s.len());
221 let ptr = alloc(layout).cast::<StringHeader>();
222 (*ptr).len = s.len() | SAFE_FLAG;
223
224 let data_ptr = ptr.add(1).cast::<u8>();
226 ptr::copy_nonoverlapping(s.as_ptr(), data_ptr, s.len());
227
228 ptr
229 }
230 }
231
232 #[must_use]
236 pub fn is_safe(&self) -> bool {
237 if self.is_inline() {
238 false
239 } else {
240 self.header().is_safe()
241 }
242 }
243
244 #[cfg(feature = "alloc")]
250 #[must_use]
251 pub fn into_safe(self) -> VSafeString {
252 if self.is_safe() {
253 return VSafeString(self.0);
255 }
256 let s = self.as_str();
258 unsafe {
259 let ptr = Self::alloc_safe(s);
260 VSafeString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
261 }
262 }
263}
264
265const _: () = {
266 const_assert_eq!(VString::INLINE_DATA_OFFSET, 1);
267 const_assert!(
268 VString::INLINE_CAP_BYTES <= VString::INLINE_WORD_BYTES - VString::INLINE_DATA_OFFSET
269 );
270 const_assert!(VString::INLINE_LEN_MAX <= VString::INLINE_CAP_BYTES);
271};
272
273#[repr(transparent)]
290#[derive(Clone)]
291pub struct VSafeString(pub(crate) Value);
292
293impl VSafeString {
294 #[cfg(feature = "alloc")]
299 #[must_use]
300 pub fn new(s: &str) -> Self {
301 unsafe {
302 let ptr = VString::alloc_safe(s);
303 VSafeString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
304 }
305 }
306
307 #[must_use]
309 pub fn len(&self) -> usize {
310 self.header().actual_len()
312 }
313
314 #[must_use]
316 pub fn is_empty(&self) -> bool {
317 self.len() == 0
318 }
319
320 #[must_use]
322 pub fn as_str(&self) -> &str {
323 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
324 }
325
326 #[must_use]
328 pub fn as_bytes(&self) -> &[u8] {
329 unsafe { core::slice::from_raw_parts(self.data_ptr(), self.len()) }
330 }
331
332 fn header(&self) -> &StringHeader {
333 unsafe { &*(self.0.heap_ptr() as *const StringHeader) }
334 }
335
336 fn data_ptr(&self) -> *const u8 {
337 unsafe { (self.0.heap_ptr() as *const StringHeader).add(1).cast() }
338 }
339}
340
341impl Deref for VSafeString {
342 type Target = str;
343
344 fn deref(&self) -> &str {
345 self.as_str()
346 }
347}
348
349impl Borrow<str> for VSafeString {
350 fn borrow(&self) -> &str {
351 self.as_str()
352 }
353}
354
355impl AsRef<str> for VSafeString {
356 fn as_ref(&self) -> &str {
357 self.as_str()
358 }
359}
360
361impl AsRef<[u8]> for VSafeString {
362 fn as_ref(&self) -> &[u8] {
363 self.as_bytes()
364 }
365}
366
367impl PartialEq for VSafeString {
368 fn eq(&self, other: &Self) -> bool {
369 self.as_str() == other.as_str()
370 }
371}
372
373impl Eq for VSafeString {}
374
375impl PartialOrd for VSafeString {
376 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
377 Some(self.cmp(other))
378 }
379}
380
381impl Ord for VSafeString {
382 fn cmp(&self, other: &Self) -> Ordering {
383 self.as_str().cmp(other.as_str())
384 }
385}
386
387impl Hash for VSafeString {
388 fn hash<H: Hasher>(&self, state: &mut H) {
389 self.as_str().hash(state);
390 }
391}
392
393impl Debug for VSafeString {
394 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
395 f.debug_tuple("SafeString").field(&self.as_str()).finish()
396 }
397}
398
399impl fmt::Display for VSafeString {
400 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
401 fmt::Display::fmt(self.as_str(), f)
402 }
403}
404
405impl PartialEq<str> for VSafeString {
408 fn eq(&self, other: &str) -> bool {
409 self.as_str() == other
410 }
411}
412
413impl PartialEq<VSafeString> for str {
414 fn eq(&self, other: &VSafeString) -> bool {
415 self == other.as_str()
416 }
417}
418
419impl PartialEq<&str> for VSafeString {
420 fn eq(&self, other: &&str) -> bool {
421 self.as_str() == *other
422 }
423}
424
425#[cfg(feature = "alloc")]
426impl PartialEq<String> for VSafeString {
427 fn eq(&self, other: &String) -> bool {
428 self.as_str() == other.as_str()
429 }
430}
431
432#[cfg(feature = "alloc")]
433impl PartialEq<VString> for VSafeString {
434 fn eq(&self, other: &VString) -> bool {
435 self.as_str() == other.as_str()
436 }
437}
438
439#[cfg(feature = "alloc")]
440impl PartialEq<VSafeString> for VString {
441 fn eq(&self, other: &VSafeString) -> bool {
442 self.as_str() == other.as_str()
443 }
444}
445
446#[cfg(feature = "alloc")]
449impl From<&str> for VSafeString {
450 fn from(s: &str) -> Self {
451 Self::new(s)
452 }
453}
454
455#[cfg(feature = "alloc")]
456impl From<String> for VSafeString {
457 fn from(s: String) -> Self {
458 Self::new(&s)
459 }
460}
461
462#[cfg(feature = "alloc")]
463impl From<&String> for VSafeString {
464 fn from(s: &String) -> Self {
465 Self::new(s)
466 }
467}
468
469#[cfg(feature = "alloc")]
470impl From<VSafeString> for String {
471 fn from(s: VSafeString) -> Self {
472 s.as_str().into()
473 }
474}
475
476impl From<VSafeString> for VString {
478 fn from(s: VSafeString) -> Self {
479 VString(s.0)
480 }
481}
482
483impl AsRef<Value> for VSafeString {
486 fn as_ref(&self) -> &Value {
487 &self.0
488 }
489}
490
491impl AsMut<Value> for VSafeString {
492 fn as_mut(&mut self) -> &mut Value {
493 &mut self.0
494 }
495}
496
497impl From<VSafeString> for Value {
498 fn from(s: VSafeString) -> Self {
499 s.0
500 }
501}
502
503impl VSafeString {
504 #[inline]
506 pub fn into_value(self) -> Value {
507 self.0
508 }
509
510 #[inline]
513 pub fn into_string(self) -> VString {
514 VString(self.0)
515 }
516}
517
518impl Deref for VString {
519 type Target = str;
520
521 fn deref(&self) -> &str {
522 self.as_str()
523 }
524}
525
526impl Borrow<str> for VString {
527 fn borrow(&self) -> &str {
528 self.as_str()
529 }
530}
531
532impl AsRef<str> for VString {
533 fn as_ref(&self) -> &str {
534 self.as_str()
535 }
536}
537
538impl AsRef<[u8]> for VString {
539 fn as_ref(&self) -> &[u8] {
540 self.as_bytes()
541 }
542}
543
544impl PartialEq for VString {
545 fn eq(&self, other: &Self) -> bool {
546 self.as_str() == other.as_str()
547 }
548}
549
550impl Eq for VString {}
551
552impl PartialOrd for VString {
553 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
554 Some(self.cmp(other))
555 }
556}
557
558impl Ord for VString {
559 fn cmp(&self, other: &Self) -> Ordering {
560 self.as_str().cmp(other.as_str())
561 }
562}
563
564impl Hash for VString {
565 fn hash<H: Hasher>(&self, state: &mut H) {
566 self.as_str().hash(state);
567 }
568}
569
570impl Debug for VString {
571 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
572 Debug::fmt(self.as_str(), f)
573 }
574}
575
576impl fmt::Display for VString {
577 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
578 fmt::Display::fmt(self.as_str(), f)
579 }
580}
581
582impl Default for VString {
583 fn default() -> Self {
584 Self::empty()
585 }
586}
587
588impl PartialEq<str> for VString {
591 fn eq(&self, other: &str) -> bool {
592 self.as_str() == other
593 }
594}
595
596impl PartialEq<VString> for str {
597 fn eq(&self, other: &VString) -> bool {
598 self == other.as_str()
599 }
600}
601
602impl PartialEq<&str> for VString {
603 fn eq(&self, other: &&str) -> bool {
604 self.as_str() == *other
605 }
606}
607
608#[cfg(feature = "alloc")]
609impl PartialEq<String> for VString {
610 fn eq(&self, other: &String) -> bool {
611 self.as_str() == other.as_str()
612 }
613}
614
615#[cfg(feature = "alloc")]
616impl PartialEq<VString> for String {
617 fn eq(&self, other: &VString) -> bool {
618 self.as_str() == other.as_str()
619 }
620}
621
622#[cfg(feature = "alloc")]
625impl From<&str> for VString {
626 fn from(s: &str) -> Self {
627 Self::new(s)
628 }
629}
630
631#[cfg(feature = "alloc")]
632impl From<String> for VString {
633 fn from(s: String) -> Self {
634 Self::new(&s)
635 }
636}
637
638#[cfg(feature = "alloc")]
639impl From<&String> for VString {
640 fn from(s: &String) -> Self {
641 Self::new(s)
642 }
643}
644
645#[cfg(feature = "alloc")]
646impl From<VString> for String {
647 fn from(s: VString) -> Self {
648 s.as_str().into()
649 }
650}
651
652impl AsRef<Value> for VString {
655 fn as_ref(&self) -> &Value {
656 &self.0
657 }
658}
659
660impl AsMut<Value> for VString {
661 fn as_mut(&mut self) -> &mut Value {
662 &mut self.0
663 }
664}
665
666impl From<VString> for Value {
667 fn from(s: VString) -> Self {
668 s.0
669 }
670}
671
672impl VString {
673 #[inline]
675 pub fn into_value(self) -> Value {
676 self.0
677 }
678}
679
680#[cfg(feature = "alloc")]
681impl From<&str> for Value {
682 fn from(s: &str) -> Self {
683 VString::new(s).0
684 }
685}
686
687#[cfg(feature = "alloc")]
688impl From<String> for Value {
689 fn from(s: String) -> Self {
690 VString::new(&s).0
691 }
692}
693
694#[cfg(feature = "alloc")]
695impl From<&String> for Value {
696 fn from(s: &String) -> Self {
697 VString::new(s).0
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use super::*;
704 use crate::value::{TypeTag, Value};
705
706 #[test]
707 fn test_new() {
708 let s = VString::new("hello");
709 assert_eq!(s.as_str(), "hello");
710 assert_eq!(s.len(), 5);
711 assert!(!s.is_empty());
712 }
713
714 #[test]
715 fn test_empty() {
716 let s = VString::empty();
717 assert_eq!(s.as_str(), "");
718 assert_eq!(s.len(), 0);
719 assert!(s.is_empty());
720 }
721
722 #[test]
723 fn test_equality() {
724 let a = VString::new("hello");
725 let b = VString::new("hello");
726 let c = VString::new("world");
727
728 assert_eq!(a, b);
729 assert_ne!(a, c);
730 assert_eq!(a, "hello");
731 assert_eq!(a.as_str(), "hello");
732 }
733
734 #[test]
735 fn test_clone() {
736 let a = VString::new("test");
737 let b = a.clone();
738 assert_eq!(a, b);
739 }
740
741 #[test]
742 fn test_unicode() {
743 let s = VString::new("hello δΈη π");
744 assert_eq!(s.as_str(), "hello δΈη π");
745 }
746
747 #[test]
748 fn test_deref() {
749 let s = VString::new("hello");
750 assert!(s.starts_with("hel"));
751 assert!(s.ends_with("llo"));
752 }
753
754 #[test]
755 fn test_ordering() {
756 let a = VString::new("apple");
757 let b = VString::new("banana");
758 assert!(a < b);
759 }
760
761 #[test]
762 fn test_inline_representation() {
763 let s = VString::new("inline");
764 assert!(s.is_inline(), "expected inline storage");
765 assert_eq!(s.as_str(), "inline");
766 }
767
768 #[test]
769 fn test_heap_representation() {
770 let long_input = "a".repeat(VString::INLINE_LEN_MAX + 1);
771 let s = VString::new(&long_input);
772 assert!(!s.is_inline(), "expected heap storage");
773 assert_eq!(s.as_str(), long_input);
774 }
775
776 #[test]
777 fn inline_capacity_boundaries() {
778 for len in 0..=VString::INLINE_LEN_MAX {
779 let input = "x".repeat(len);
780 let s = VString::new(&input);
781 assert!(
782 s.is_inline(),
783 "expected inline storage for length {} (capacity {})",
784 len,
785 VString::INLINE_LEN_MAX
786 );
787 assert_eq!(s.len(), len);
788 assert_eq!(s.as_str(), input);
789 assert_eq!(s.as_bytes(), input.as_bytes());
790 }
791
792 let overflow = "y".repeat(VString::INLINE_LEN_MAX + 1);
793 let heap = VString::new(&overflow);
794 assert!(
795 !heap.is_inline(),
796 "length {} should force heap allocation",
797 overflow.len()
798 );
799 }
800
801 #[test]
802 fn inline_value_tag_matches() {
803 for len in 0..=VString::INLINE_LEN_MAX {
804 let input = "z".repeat(len);
805 let value = Value::from(input.as_str());
806 assert!(value.is_inline_string(), "Value should mark inline string");
807 assert_eq!(
808 value.ptr_usize() & 0b111,
809 TypeTag::InlineString as usize,
810 "low bits must store inline string tag"
811 );
812 let roundtrip = value.as_string().expect("string value");
813 assert_eq!(roundtrip.as_str(), input);
814 assert_eq!(roundtrip.as_bytes(), input.as_bytes());
815 }
816 }
817
818 #[cfg(target_pointer_width = "64")]
819 #[test]
820 fn inline_len_max_is_seven_on_64_bit() {
821 assert_eq!(VString::INLINE_LEN_MAX, 7);
822 }
823
824 #[cfg(target_pointer_width = "32")]
825 #[test]
826 fn inline_len_max_is_three_on_32_bit() {
827 assert_eq!(VString::INLINE_LEN_MAX, 3);
828 }
829
830 #[test]
833 fn test_safe_string_new() {
834 let s = VSafeString::new("hello");
835 assert_eq!(s.as_str(), "hello");
836 assert_eq!(s.len(), 5);
837 assert!(!s.is_empty());
838 }
839
840 #[test]
841 fn test_safe_string_roundtrip() {
842 let original = "<b>bold</b>";
843 let safe = VSafeString::new(original);
844 assert_eq!(safe.as_str(), original);
845 }
846
847 #[test]
848 fn test_safe_string_is_always_heap() {
849 let short = VSafeString::new("hi");
851 assert_eq!(short.len(), 2);
852 assert_eq!(short.as_str(), "hi");
853 let value: Value = short.into();
855 assert!(!value.is_inline_string());
856 assert!(value.is_string());
857 }
858
859 #[test]
860 fn test_vstring_is_safe() {
861 let normal = VString::new("hello");
862 assert!(!normal.is_safe());
863
864 let safe = VSafeString::new("hello");
865 let as_vstring: VString = safe.into();
867 assert!(as_vstring.is_safe());
868 }
869
870 #[test]
871 fn test_vstring_into_safe() {
872 let inline = VString::new("hi");
874 assert!(inline.is_inline());
875 let safe = inline.into_safe();
876 assert_eq!(safe.as_str(), "hi");
877
878 let long = "a".repeat(VString::INLINE_LEN_MAX + 10);
880 let heap = VString::new(&long);
881 assert!(!heap.is_inline());
882 let safe_heap = heap.into_safe();
883 assert_eq!(safe_heap.as_str(), long);
884 }
885
886 #[test]
887 fn test_safe_flag_preserved_through_clone() {
888 let safe = VSafeString::new("<b>bold</b>");
889 let value: Value = safe.into();
890 assert!(value.is_safe_string());
891
892 let cloned = value.clone();
893 assert!(cloned.is_safe_string());
894 assert_eq!(cloned.as_string().unwrap().as_str(), "<b>bold</b>");
895 }
896
897 #[test]
898 fn test_value_as_safe_string() {
899 let safe = VSafeString::new("safe content");
900 let value: Value = safe.into();
901
902 assert!(value.is_string());
904 assert!(value.is_safe_string());
906 assert_eq!(value.as_string().unwrap().as_str(), "safe content");
908 assert_eq!(value.as_safe_string().unwrap().as_str(), "safe content");
910 }
911
912 #[test]
913 fn test_normal_string_not_safe() {
914 let normal = VString::new("normal");
915 let value: Value = normal.into();
916
917 assert!(value.is_string());
918 assert!(!value.is_safe_string());
919 assert!(value.as_string().is_some());
920 assert!(value.as_safe_string().is_none());
921 }
922
923 #[test]
924 fn test_safe_string_equality() {
925 let a = VSafeString::new("hello");
926 let b = VSafeString::new("hello");
927 let c = VSafeString::new("world");
928
929 assert_eq!(a, b);
930 assert_ne!(a, c);
931 assert_eq!(a, "hello");
932
933 let vstring = VString::new("hello");
935 assert_eq!(a, vstring);
936 assert_eq!(vstring, a);
937 }
938
939 #[test]
940 fn test_safe_string_into_string() {
941 let safe = VSafeString::new("test");
942 let vstring = safe.into_string();
943 assert_eq!(vstring.as_str(), "test");
944 assert!(vstring.is_safe()); }
946
947 #[test]
948 fn test_safe_flag_constant() {
949 assert_eq!(SAFE_FLAG, 1usize << (usize::BITS - 1));
951 }
954
955 #[test]
956 fn test_safe_string_long() {
957 let long = "a".repeat(1000);
959 let safe = VSafeString::new(&long);
960 assert_eq!(safe.len(), 1000);
961 assert_eq!(safe.as_str(), long);
962
963 let value: Value = safe.into();
964 assert!(value.is_safe_string());
965 assert_eq!(value.as_string().unwrap().len(), 1000);
966 }
967}
968
969#[cfg(all(test, feature = "bolero-inline-tests"))]
970mod bolero_props {
971 use super::*;
972 use crate::ValueType;
973 use crate::array::VArray;
974 use alloc::string::String;
975 use alloc::vec::Vec;
976 use bolero::check;
977
978 #[test]
979 fn bolero_inline_string_round_trip() {
980 check!().with_type::<Vec<u8>>().for_each(|bytes: &Vec<u8>| {
981 if bytes.len() > VString::INLINE_LEN_MAX + 8 {
982 return;
984 }
985
986 if let Ok(s) = String::from_utf8(bytes.clone()) {
987 let value = Value::from(s.as_str());
988 let roundtrip = value.as_string().expect("expected string value");
989 assert_eq!(roundtrip.as_str(), s);
990
991 if VString::can_inline(s.len()) {
992 assert!(value.is_inline_string(), "expected inline tag for {s:?}");
993 } else {
994 assert!(!value.is_inline_string(), "unexpected inline tag for {s:?}");
995 }
996 }
997 });
998 }
999
1000 #[test]
1001 fn bolero_string_mutation_sequences() {
1002 check!().with_type::<Vec<u8>>().for_each(|bytes: &Vec<u8>| {
1003 let mut value = Value::from("");
1004 let mut expected = String::new();
1005
1006 for chunk in bytes.chunks(3).take(24) {
1007 let selector = chunk.first().copied().unwrap_or(0) % 3;
1008 match selector {
1009 0 => {
1010 let ch = (b'a' + chunk.get(1).copied().unwrap_or(0) % 26) as char;
1011 expected.push(ch);
1012 }
1013 1 => {
1014 if !expected.is_empty() {
1015 let len = chunk
1016 .get(1)
1017 .copied()
1018 .map(|n| (n as usize) % expected.len())
1019 .unwrap_or(0);
1020 expected.truncate(len);
1021 }
1022 }
1023 _ => expected.clear(),
1024 }
1025
1026 overwrite_value_string(&mut value, &expected);
1027 assert_eq!(value.as_string().unwrap().as_str(), expected);
1028 assert_eq!(
1029 value.is_inline_string(),
1030 expected.len() <= VString::INLINE_LEN_MAX,
1031 "mutation sequence should keep inline status accurate"
1032 );
1033 }
1034 });
1035 }
1036
1037 #[test]
1038 fn bolero_array_model_matches() {
1039 check!().with_type::<Vec<u8>>().for_each(|bytes: &Vec<u8>| {
1040 let mut arr = VArray::new();
1041 let mut model: Vec<String> = Vec::new();
1042
1043 for chunk in bytes.chunks(4).take(20) {
1044 match chunk.first().copied().unwrap_or(0) % 4 {
1045 0 => {
1046 let content = inline_string_from_chunk(chunk, 1);
1047 arr.push(Value::from(content.as_str()));
1048 model.push(content);
1049 }
1050 1 => {
1051 let idx = chunk.get(1).copied().unwrap_or(0) as usize;
1052 if !model.is_empty() {
1053 let idx = idx % model.len();
1054 model.remove(idx);
1055 let _ = arr.remove(idx);
1056 }
1057 }
1058 2 => {
1059 let content = inline_string_from_chunk(chunk, 2);
1060 if model.is_empty() {
1061 arr.insert(0, Value::from(content.as_str()));
1062 model.insert(0, content);
1063 } else {
1064 let len = model.len();
1065 let idx = (chunk.get(2).copied().unwrap_or(0) as usize) % (len + 1);
1066 arr.insert(idx, Value::from(content.as_str()));
1067 model.insert(idx, content);
1068 }
1069 }
1070 _ => {
1071 arr.clear();
1072 model.clear();
1073 }
1074 }
1075
1076 assert_eq!(arr.len(), model.len());
1077 for (value, expected) in arr.iter().zip(model.iter()) {
1078 assert_eq!(value.value_type(), ValueType::String);
1079 assert_eq!(value.as_string().unwrap().as_str(), expected);
1080 assert_eq!(
1081 value.is_inline_string(),
1082 expected.len() <= VString::INLINE_LEN_MAX
1083 );
1084 }
1085 }
1086 });
1087 }
1088
1089 fn overwrite_value_string(value: &mut Value, new_value: &str) {
1090 let slot = value.as_string_mut().expect("expected string value");
1091 *slot = VString::new(new_value);
1092 }
1093
1094 fn inline_string_from_chunk(chunk: &[u8], seed_idx: usize) -> String {
1095 let len_hint = chunk.get(seed_idx).copied().unwrap_or(0) as usize;
1096 let len = len_hint % (VString::INLINE_LEN_MAX.saturating_sub(1).max(1));
1097 (0..len)
1098 .map(|i| {
1099 let byte = chunk.get(i % chunk.len()).copied().unwrap_or(b'a');
1100 (b'a' + (byte % 26)) as char
1101 })
1102 .collect()
1103 }
1104}