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