1use alloc::{
9 string::{String, ToString},
10 vec::Vec,
11};
12
13use crate::props::basic::ColorU;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[repr(C)]
37#[derive(Default)]
38#[allow(clippy::pub_underscore_fields)]
40pub struct EmptyStruct {
41 pub _reserved: u8,
44}
45
46impl EmptyStruct {
47 #[must_use]
49 pub const fn new() -> Self {
50 Self { _reserved: 0 }
51 }
52}
53
54impl From<()> for EmptyStruct {
55 fn from((): ()) -> Self {
56 Self::default()
57 }
58}
59
60impl From<EmptyStruct> for () {
61 fn from(_: EmptyStruct) -> Self {}
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
70#[repr(C)]
71#[derive(Default)]
72pub enum LayoutDebugMessageType {
73 #[default]
74 Info,
75 Warning,
76 Error,
77 BoxProps,
79 CssGetter,
80 BfcLayout,
82 IfcLayout,
84 TableLayout,
85 DisplayType,
86 PositionCalculation,
87}
88
89#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd)]
91#[repr(C)]
92pub struct LayoutDebugMessage {
93 pub message_type: LayoutDebugMessageType,
94 pub message: AzString,
95 pub location: AzString,
96}
97
98impl LayoutDebugMessage {
99 #[track_caller]
101 pub fn new(message_type: LayoutDebugMessageType, message: impl Into<String>) -> Self {
102 let location = core::panic::Location::caller();
103 Self {
104 message_type,
105 message: AzString::from_string(message.into()),
106 location: AzString::from_string(format!(
107 "{}:{}:{}",
108 location.file(),
109 location.line(),
110 location.column()
111 )),
112 }
113 }
114
115 #[track_caller]
117 pub fn info(message: impl Into<String>) -> Self {
118 Self::new(LayoutDebugMessageType::Info, message)
119 }
120
121 #[track_caller]
123 pub fn warning(message: impl Into<String>) -> Self {
124 Self::new(LayoutDebugMessageType::Warning, message)
125 }
126
127 #[track_caller]
129 pub fn error(message: impl Into<String>) -> Self {
130 Self::new(LayoutDebugMessageType::Error, message)
131 }
132
133 #[track_caller]
135 pub fn box_props(message: impl Into<String>) -> Self {
136 Self::new(LayoutDebugMessageType::BoxProps, message)
137 }
138
139 #[track_caller]
141 pub fn css_getter(message: impl Into<String>) -> Self {
142 Self::new(LayoutDebugMessageType::CssGetter, message)
143 }
144
145 #[track_caller]
147 pub fn bfc_layout(message: impl Into<String>) -> Self {
148 Self::new(LayoutDebugMessageType::BfcLayout, message)
149 }
150
151 #[track_caller]
153 pub fn ifc_layout(message: impl Into<String>) -> Self {
154 Self::new(LayoutDebugMessageType::IfcLayout, message)
155 }
156
157 #[track_caller]
159 pub fn table_layout(message: impl Into<String>) -> Self {
160 Self::new(LayoutDebugMessageType::TableLayout, message)
161 }
162
163 #[track_caller]
165 pub fn display_type(message: impl Into<String>) -> Self {
166 Self::new(LayoutDebugMessageType::DisplayType, message)
167 }
168}
169
170#[repr(C)]
175pub struct AzString {
176 pub vec: U8Vec,
177}
178
179impl_option!(
180 AzString,
181 OptionString,
182 copy = false,
183 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
184);
185
186static DEFAULT_STR: &str = "";
187
188impl Default for AzString {
189 fn default() -> Self {
190 DEFAULT_STR.into()
191 }
192}
193
194impl<'a> From<&'a str> for AzString {
195 fn from(s: &'a str) -> Self {
196 s.to_string().into()
197 }
198}
199
200impl AsRef<str> for AzString {
201 fn as_ref(&self) -> &str {
202 self.as_str()
203 }
204}
205
206impl core::fmt::Debug for AzString {
207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208 self.as_str().fmt(f)
209 }
210}
211
212impl core::fmt::Display for AzString {
213 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
214 self.as_str().fmt(f)
215 }
216}
217
218impl AzString {
219 #[inline]
220 #[must_use]
221 pub const fn from_const_str(s: &'static str) -> Self {
222 Self {
223 vec: U8Vec::from_const_slice(s.as_bytes()),
224 }
225 }
226
227 #[inline]
242 #[must_use]
243 pub unsafe fn from_c_str(ptr: *const i8) -> Self {
244 unsafe {
245 if ptr.is_null() {
246 return Self::default();
247 }
248 let c_str = core::ffi::CStr::from_ptr(ptr as *const core::ffi::c_char);
249 let bytes = c_str.to_bytes();
250 Self::copy_from_bytes(bytes.as_ptr(), 0, bytes.len())
251 }
252 }
253
254 #[inline]
267 #[must_use]
268 pub fn copy_from_bytes(ptr: *const u8, start: usize, len: usize) -> Self {
269 let raw = U8Vec::copy_from_bytes(ptr, start, len);
270 if core::str::from_utf8(raw.as_ref()).is_ok() {
277 return Self { vec: raw };
278 }
279 let s = String::from_utf8_lossy(raw.as_ref()).into_owned();
280 Self::from_string(s)
281 }
282
283 #[inline] #[must_use]
285 pub const fn from_string(s: String) -> Self {
286 Self {
287 vec: U8Vec::from_vec(s.into_bytes()),
288 }
289 }
290
291 #[inline]
292 #[must_use]
293 pub fn as_str(&self) -> &str {
294 unsafe { core::str::from_utf8_unchecked(self.vec.as_ref()) }
295 }
296
297 #[inline]
300 #[must_use]
301 pub fn clone_self(&self) -> Self {
302 Self {
303 vec: self.vec.clone_self(),
304 }
305 }
306
307 #[inline]
308 #[must_use]
309 pub fn into_library_owned_string(self) -> String {
310 match self.vec.destructor {
311 U8VecDestructor::NoDestructor
312 | U8VecDestructor::External(_)
313 | U8VecDestructor::AlreadyDestroyed => self.as_str().to_string(),
314 U8VecDestructor::DefaultRust => {
315 let m = core::mem::ManuallyDrop::new(self);
316 unsafe { String::from_raw_parts(m.vec.ptr.cast_mut(), m.vec.len, m.vec.cap) }
317 }
318 }
319 }
320
321 #[inline]
322 #[must_use]
323 pub fn as_bytes(&self) -> &[u8] {
324 self.vec.as_ref()
325 }
326
327 #[inline]
328 #[must_use]
329 pub fn into_bytes(self) -> U8Vec {
330 let m = core::mem::ManuallyDrop::new(self);
331 U8Vec {
332 ptr: m.vec.ptr,
333 len: m.vec.len,
334 cap: m.vec.cap,
335 destructor: m.vec.destructor,
336 }
337 }
338
339 #[inline]
341 #[must_use]
342 pub const fn len(&self) -> usize {
343 self.vec.len
344 }
345
346 #[inline]
348 #[must_use]
349 pub const fn is_empty(&self) -> bool {
350 self.vec.len == 0
351 }
352
353 #[inline]
359 #[must_use]
360 pub fn to_c_str(&self) -> U8Vec {
361 let bytes = self.as_bytes();
362 let mut result = Vec::with_capacity(bytes.len() + 1);
363 result.extend_from_slice(bytes);
364 result.push(0); U8Vec::from_vec(result)
366 }
367
368 unsafe fn from_utf16_with_byte_order(
374 ptr: *const u8,
375 len: usize,
376 from_bytes: fn([u8; 2]) -> u16,
377 ) -> Self {
378 unsafe {
379 if ptr.is_null() || len == 0 {
380 return Self::default();
381 }
382
383 if !len.is_multiple_of(2) {
385 return Self::default();
386 }
387
388 let byte_slice = core::slice::from_raw_parts(ptr, len);
389 let code_units: Vec<u16> = byte_slice
390 .chunks_exact(2)
391 .map(|chunk| from_bytes([chunk[0], chunk[1]]))
392 .collect();
393
394 String::from_utf16(&code_units).map_or_else(|_| Self::default(), Self::from_string)
395 }
396 }
397
398 #[inline]
409 pub unsafe fn from_utf16_le(ptr: *const u8, len: usize) -> Self {
410 unsafe { Self::from_utf16_with_byte_order(ptr, len, u16::from_le_bytes) }
411 }
412
413 #[inline]
424 pub unsafe fn from_utf16_be(ptr: *const u8, len: usize) -> Self {
425 unsafe { Self::from_utf16_with_byte_order(ptr, len, u16::from_be_bytes) }
426 }
427
428 #[inline]
434 #[must_use]
435 pub unsafe fn from_utf8_lossy(ptr: *const u8, len: usize) -> Self {
436 unsafe {
437 if ptr.is_null() || len == 0 {
438 return Self::default();
439 }
440
441 let byte_slice = core::slice::from_raw_parts(ptr, len);
442 let s = String::from_utf8_lossy(byte_slice).into_owned();
443 Self::from_string(s)
444 }
445 }
446
447 #[inline]
453 #[must_use]
454 pub unsafe fn from_utf8(ptr: *const u8, len: usize) -> Self {
455 unsafe {
456 if ptr.is_null() || len == 0 {
457 return Self::default();
458 }
459
460 let byte_slice = core::slice::from_raw_parts(ptr, len);
461 core::str::from_utf8(byte_slice)
462 .map_or_else(|_| Self::default(), |s| Self::from_string(s.to_string()))
463 }
464 }
465}
466
467impl From<String> for AzString {
468 fn from(input: String) -> Self {
469 Self::from_string(input)
470 }
471}
472
473impl PartialOrd for AzString {
474 fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
475 self.as_str().partial_cmp(rhs.as_str())
476 }
477}
478
479impl Ord for AzString {
480 fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
481 self.as_str().cmp(rhs.as_str())
482 }
483}
484
485impl Clone for AzString {
486 fn clone(&self) -> Self {
487 self.clone_self()
488 }
489}
490
491impl PartialEq for AzString {
492 fn eq(&self, rhs: &Self) -> bool {
493 self.as_str().eq(rhs.as_str())
494 }
495}
496
497impl Eq for AzString {}
498
499impl core::hash::Hash for AzString {
500 fn hash<H>(&self, state: &mut H)
501 where
502 H: core::hash::Hasher,
503 {
504 self.as_str().hash(state);
505 }
506}
507
508impl core::ops::Deref for AzString {
509 type Target = str;
510
511 fn deref(&self) -> &str {
512 self.as_str()
513 }
514}
515
516impl_option!(
517 u8,
518 OptionU8,
519 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
520);
521
522impl_vec!(
523 u8,
524 U8Vec,
525 U8VecDestructor,
526 U8VecDestructorType,
527 U8VecSlice,
528 OptionU8
529);
530impl_vec_mut!(u8, U8Vec);
531impl_vec_debug!(u8, U8Vec);
532impl_vec_partialord!(u8, U8Vec);
533impl_vec_ord!(u8, U8Vec);
534impl_vec_clone!(u8, U8Vec, U8VecDestructor);
535impl_vec_partialeq!(u8, U8Vec);
536impl_vec_eq!(u8, U8Vec);
537impl_vec_hash!(u8, U8Vec);
538
539impl U8Vec {
540 #[inline] #[allow(clippy::not_unsafe_ptr_arg_deref)]
548 #[must_use]
550 pub fn copy_from_bytes(ptr: *const u8, start: usize, len: usize) -> Self {
551 if ptr.is_null() || len == 0 {
552 return Self::new();
553 }
554 debug_assert!(
555 start.checked_add(len).is_some(),
556 "U8Vec::copy_from_bytes: start + len overflows"
557 );
558 let slice = unsafe { core::slice::from_raw_parts(ptr.add(start), len) };
559 Self::from_vec(slice.to_vec())
560 }
561}
562
563impl_option!(
564 U8Vec,
565 OptionU8Vec,
566 copy = false,
567 [Debug, Clone, PartialEq, Ord, PartialOrd, Eq, Hash]
568);
569
570impl_vec!(
571 u16,
572 U16Vec,
573 U16VecDestructor,
574 U16VecDestructorType,
575 U16VecSlice,
576 OptionU16
577);
578impl_vec_debug!(u16, U16Vec);
579impl_vec_partialord!(u16, U16Vec);
580impl_vec_ord!(u16, U16Vec);
581impl_vec_clone!(u16, U16Vec, U16VecDestructor);
582impl_vec_partialeq!(u16, U16Vec);
583impl_vec_eq!(u16, U16Vec);
584impl_vec_hash!(u16, U16Vec);
585
586impl_vec!(
587 f32,
588 F32Vec,
589 F32VecDestructor,
590 F32VecDestructorType,
591 F32VecSlice,
592 OptionF32
593);
594impl_vec_debug!(f32, F32Vec);
595impl_vec_partialord!(f32, F32Vec);
596impl_vec_clone!(f32, F32Vec, F32VecDestructor);
597impl_vec_partialeq!(f32, F32Vec);
598
599impl_vec!(
601 u32,
602 U32Vec,
603 U32VecDestructor,
604 U32VecDestructorType,
605 U32VecSlice,
606 OptionU32
607);
608impl_vec_mut!(u32, U32Vec);
609impl_option!(
610 U32Vec,
611 OptionU32Vec,
612 copy = false,
613 [Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
614);
615impl_vec_debug!(u32, U32Vec);
616impl_vec_partialord!(u32, U32Vec);
617impl_vec_ord!(u32, U32Vec);
618impl_vec_clone!(u32, U32Vec, U32VecDestructor);
619impl_vec_partialeq!(u32, U32Vec);
620impl_vec_eq!(u32, U32Vec);
621impl_vec_hash!(u32, U32Vec);
622
623impl_vec!(
624 AzString,
625 StringVec,
626 StringVecDestructor,
627 StringVecDestructorType,
628 StringVecSlice,
629 OptionString
630);
631impl_vec_debug!(AzString, StringVec);
632impl_vec_partialord!(AzString, StringVec);
633impl_vec_ord!(AzString, StringVec);
634impl_vec_clone!(AzString, StringVec, StringVecDestructor);
635impl_vec_partialeq!(AzString, StringVec);
636impl_vec_eq!(AzString, StringVec);
637impl_vec_hash!(AzString, StringVec);
638
639impl From<Vec<String>> for StringVec {
640 fn from(v: Vec<String>) -> Self {
641 let new_v: Vec<AzString> = v.into_iter().map(Into::into).collect();
642 new_v.into()
643 }
644}
645
646impl_option!(
647 StringVec,
648 OptionStringVec,
649 copy = false,
650 [Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash]
651);
652
653impl_option!(
654 u16,
655 OptionU16,
656 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
657);
658impl_option!(
659 u32,
660 OptionU32,
661 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
662);
663impl_option!(
664 u64,
665 OptionU64,
666 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
667);
668impl_option!(
669 usize,
670 OptionUsize,
671 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
672);
673impl_option!(
674 i16,
675 OptionI16,
676 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
677);
678impl_option!(
679 i32,
680 OptionI32,
681 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
682);
683impl_option!(
684 bool,
685 OptionBool,
686 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
687);
688impl_option!(f32, OptionF32, [Debug, Copy, Clone, PartialEq]);
689impl_option!(f64, OptionF64, [Debug, Copy, Clone, PartialEq, PartialOrd]);
690
691impl core::hash::Hash for OptionF32 {
693 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
694 match self {
695 Self::None => 0u8.hash(state),
696 Self::Some(v) => {
697 1u8.hash(state);
698 v.to_bits().hash(state);
699 }
700 }
701 }
702}
703
704impl Eq for OptionF32 {}
705
706impl PartialOrd for OptionF32 {
709 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
710 Some(self.cmp(other))
711 }
712}
713
714impl Ord for OptionF32 {
715 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
716 match (self, other) {
717 (Self::None, Self::None) => core::cmp::Ordering::Equal,
718 (Self::None, Self::Some(_)) => core::cmp::Ordering::Less,
719 (Self::Some(_), Self::None) => core::cmp::Ordering::Greater,
720 (Self::Some(a), Self::Some(b)) => {
721 a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
722 }
723 }
724 }
725}
726
727use alloc::sync::Arc;
740use core::cell::UnsafeCell;
741
742struct StringArenaInner {
746 chunks: UnsafeCell<Vec<Vec<u8>>>,
749 current_remaining: UnsafeCell<usize>,
752}
753
754unsafe impl Send for StringArenaInner {}
763unsafe impl Sync for StringArenaInner {}
764
765pub struct StringArena {
776 inner: Arc<StringArenaInner>,
777}
778
779impl core::fmt::Debug for StringArena {
780 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
782 f.debug_struct("StringArena").finish_non_exhaustive()
783 }
784}
785
786impl StringArena {
787 pub const CHUNK_SIZE: usize = 64 * 1024;
791
792 #[must_use]
793 pub fn new() -> Self {
794 Self {
795 inner: Arc::new(StringArenaInner {
796 chunks: UnsafeCell::new(Vec::new()),
797 current_remaining: UnsafeCell::new(0),
798 }),
799 }
800 }
801
802 #[must_use]
804 pub fn metrics(&self) -> (usize, usize) {
805 unsafe {
808 let chunks = &*self.inner.chunks.get();
809 let total: usize = chunks.iter().map(Vec::len).sum();
810 (chunks.len(), total)
811 }
812 }
813
814 pub fn intern(&mut self, s: &str) -> AzString {
825 let bytes = s.as_bytes();
826 let len = bytes.len();
827
828 let ptr: *const u8 = if len == 0 {
829 core::ptr::NonNull::<u8>::dangling().as_ptr()
832 } else {
833 unsafe {
835 let chunks: &mut Vec<Vec<u8>> = &mut *self.inner.chunks.get();
836 let remaining: &mut usize = &mut *self.inner.current_remaining.get();
837
838 if len > Self::CHUNK_SIZE / 2 {
841 let mut v = Vec::with_capacity(len);
842 v.extend_from_slice(bytes);
843 let p = v.as_ptr();
844 chunks.push(v);
845 *remaining = 0;
851 p
852 } else {
853 if *remaining < len {
854 chunks.push(Vec::with_capacity(Self::CHUNK_SIZE));
855 *remaining = Self::CHUNK_SIZE;
856 }
857 let chunk = chunks.last_mut().unwrap();
860 let offset = chunk.len();
861 chunk.extend_from_slice(bytes);
862 *remaining -= len;
863 chunk.as_ptr().add(offset)
864 }
865 }
866 };
867
868 let arc_raw = Arc::into_raw(Arc::clone(&self.inner));
871
872 AzString {
873 vec: U8Vec {
874 ptr,
875 len,
876 cap: arc_raw as usize,
881 destructor: U8VecDestructor::External(arena_string_destructor),
882 },
883 }
884 }
885}
886
887impl Default for StringArena {
888 fn default() -> Self {
889 Self::new()
890 }
891}
892
893extern "C" fn arena_string_destructor(vec: *mut U8Vec) {
897 unsafe {
900 let v = &mut *vec;
901 let arc_raw = v.cap as *const StringArenaInner;
902 if !arc_raw.is_null() {
903 drop(Arc::from_raw(arc_raw));
904 v.cap = 0;
907 }
908 }
909}
910
911#[cfg(test)]
912mod string_arena_tests {
913 use super::*;
914
915 #[test]
916 fn intern_round_trip() {
917 let mut arena = StringArena::new();
918 let a = arena.intern("hello");
919 let b = arena.intern("world");
920 let c = arena.intern("");
921 assert_eq!(a.as_str(), "hello");
922 assert_eq!(b.as_str(), "world");
923 assert_eq!(c.as_str(), "");
924 }
925
926 #[test]
927 fn strings_outlive_arena_handle() {
928 let a = {
929 let mut arena = StringArena::new();
930 arena.intern("survives drop of arena handle")
931 };
932 assert_eq!(a.as_str(), "survives drop of arena handle");
933 }
934
935 #[test]
936 fn oversized_string_gets_dedicated_chunk() {
937 let mut arena = StringArena::new();
938 let big = "x".repeat(StringArena::CHUNK_SIZE);
939 let s = arena.intern(&big);
940 assert_eq!(s.len(), big.len());
941 assert_eq!(s.as_str(), big.as_str());
942 }
943
944 #[test]
945 fn many_small_strings_share_chunk() {
946 let mut arena = StringArena::new();
947 let mut strings = Vec::new();
948 for i in 0..100 {
949 strings.push(arena.intern(&format!("s{i}")));
950 }
951 let (chunks, _bytes) = arena.metrics();
952 assert!(
953 chunks <= 2,
954 "expected ≤2 chunks for 100 small strings, got {chunks}"
955 );
956 for (i, s) in strings.iter().enumerate() {
957 assert_eq!(s.as_str(), format!("s{i}"));
958 }
959 }
960
961 #[test]
962 fn clone_deep_copies_and_is_independent() {
963 let clone = {
966 let mut arena = StringArena::new();
967
968 arena.intern("deep-copy test")
969 };
970 assert_eq!(clone.as_str(), "deep-copy test");
971 }
972}
973
974#[cfg(test)]
975#[allow(clippy::all, clippy::pedantic, clippy::nursery)]
976mod autotest_generated {
977 use super::*;
978
979 struct Fnv(u64);
986
987 impl core::hash::Hasher for Fnv {
988 fn finish(&self) -> u64 {
989 self.0
990 }
991 fn write(&mut self, bytes: &[u8]) {
992 for b in bytes {
993 self.0 ^= u64::from(*b);
994 self.0 = self.0.wrapping_mul(0x0100_0000_01b3);
995 }
996 }
997 }
998
999 fn hash_of<T: core::hash::Hash>(t: &T) -> u64 {
1000 use core::hash::{Hash, Hasher};
1001 let mut h = Fnv(0xcbf2_9ce4_8422_2325);
1002 Hash::hash(t, &mut h);
1003 h.finish()
1004 }
1005
1006 fn utf16_bytes(s: &str, little_endian: bool) -> Vec<u8> {
1008 s.encode_utf16()
1009 .flat_map(|u| {
1010 let b = if little_endian {
1011 u.to_le_bytes()
1012 } else {
1013 u.to_be_bytes()
1014 };
1015 [b[0], b[1]]
1016 })
1017 .collect()
1018 }
1019
1020 #[test]
1025 fn empty_struct_new_invariants() {
1026 let e = EmptyStruct::new();
1027 assert_eq!(e._reserved, 0, "_reserved must always be initialized to 0");
1028 assert_eq!(e, EmptyStruct::default(), "new() must equal default()");
1029 }
1030
1031 #[test]
1032 fn empty_struct_is_ffi_safe_non_zero_size() {
1033 assert_eq!(size_of::<EmptyStruct>(), 1);
1035 assert_eq!(align_of::<EmptyStruct>(), 1);
1036 }
1037
1038 #[test]
1039 fn empty_struct_unit_conversions_round_trip() {
1040 let from_unit = EmptyStruct::from(());
1041 assert_eq!(from_unit, EmptyStruct::new());
1042 let back: () = EmptyStruct::new().into();
1043 assert_eq!(back, ());
1044 }
1045
1046 #[test]
1047 fn empty_struct_total_order_is_trivial() {
1048 let a = EmptyStruct::new();
1050 let b = EmptyStruct::default();
1051 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1052 assert_eq!(hash_of(&a), hash_of(&b));
1053 }
1054
1055 #[test]
1060 fn debug_message_new_records_fields_and_caller_location() {
1061 let m = LayoutDebugMessage::new(LayoutDebugMessageType::Warning, "disk on fire");
1062 assert_eq!(m.message_type, LayoutDebugMessageType::Warning);
1063 assert_eq!(m.message.as_str(), "disk on fire");
1064 assert!(
1065 m.location.as_str().contains("corety.rs"),
1066 "#[track_caller] must record THIS file, got {:?}",
1067 m.location.as_str()
1068 );
1069
1070 let parts: Vec<&str> = m.location.as_str().rsplitn(3, ':').collect();
1072 assert_eq!(parts.len(), 3, "location must be file:line:column");
1073 assert!(parts[0].parse::<u32>().is_ok(), "column must parse as u32");
1074 assert!(parts[1].parse::<u32>().is_ok(), "line must parse as u32");
1075 }
1076
1077 #[test]
1078 fn debug_message_track_caller_propagates_through_helpers() {
1079 let a = LayoutDebugMessage::info("a");
1082 let b = LayoutDebugMessage::info("b");
1083 assert_ne!(
1084 a.location.as_str(),
1085 b.location.as_str(),
1086 "two call sites on different lines must record different locations"
1087 );
1088 assert!(a.location.as_str().contains("corety.rs"));
1089 }
1090
1091 #[test]
1092 fn debug_message_helpers_set_the_right_type() {
1093 assert_eq!(
1094 LayoutDebugMessage::info("x").message_type,
1095 LayoutDebugMessageType::Info
1096 );
1097 assert_eq!(
1098 LayoutDebugMessage::warning("x").message_type,
1099 LayoutDebugMessageType::Warning
1100 );
1101 assert_eq!(
1102 LayoutDebugMessage::error("x").message_type,
1103 LayoutDebugMessageType::Error
1104 );
1105 assert_eq!(
1106 LayoutDebugMessage::box_props("x").message_type,
1107 LayoutDebugMessageType::BoxProps
1108 );
1109 assert_eq!(
1110 LayoutDebugMessage::css_getter("x").message_type,
1111 LayoutDebugMessageType::CssGetter
1112 );
1113 assert_eq!(
1114 LayoutDebugMessage::bfc_layout("x").message_type,
1115 LayoutDebugMessageType::BfcLayout
1116 );
1117 assert_eq!(
1118 LayoutDebugMessage::ifc_layout("x").message_type,
1119 LayoutDebugMessageType::IfcLayout
1120 );
1121 assert_eq!(
1122 LayoutDebugMessage::table_layout("x").message_type,
1123 LayoutDebugMessageType::TableLayout
1124 );
1125 assert_eq!(
1126 LayoutDebugMessage::display_type("x").message_type,
1127 LayoutDebugMessageType::DisplayType
1128 );
1129 }
1130
1131 #[test]
1132 fn debug_message_helpers_preserve_the_message_verbatim() {
1133 for m in [
1136 LayoutDebugMessage::info(""),
1137 LayoutDebugMessage::warning(""),
1138 LayoutDebugMessage::error(""),
1139 LayoutDebugMessage::box_props(""),
1140 LayoutDebugMessage::css_getter(""),
1141 LayoutDebugMessage::bfc_layout(""),
1142 LayoutDebugMessage::ifc_layout(""),
1143 LayoutDebugMessage::table_layout(""),
1144 LayoutDebugMessage::display_type(""),
1145 ] {
1146 assert!(m.message.is_empty());
1147 assert!(!m.location.is_empty(), "location is always filled in");
1148 }
1149
1150 let weird = "ünïcødé \u{1F600}\n\t\"quoted\" \u{0}nul";
1151 assert_eq!(LayoutDebugMessage::error(weird).message.as_str(), weird);
1152 }
1153
1154 #[test]
1155 fn debug_message_handles_huge_message_without_panicking() {
1156 let huge = "m".repeat(1_000_000);
1157 let m = LayoutDebugMessage::new(LayoutDebugMessageType::PositionCalculation, huge.clone());
1158 assert_eq!(m.message.len(), 1_000_000);
1159 assert_eq!(m.message.as_str(), huge.as_str());
1160 assert_eq!(
1161 m.message_type,
1162 LayoutDebugMessageType::PositionCalculation,
1163 "the variant with no helper must still be constructible via new()"
1164 );
1165 }
1166
1167 #[test]
1168 fn debug_message_default_is_empty_info() {
1169 let m = LayoutDebugMessage::default();
1170 assert_eq!(m.message_type, LayoutDebugMessageType::Info);
1171 assert!(m.message.is_empty());
1172 assert!(m.location.is_empty(), "default() does not track a caller");
1173 assert_eq!(
1174 LayoutDebugMessageType::default(),
1175 LayoutDebugMessageType::Info
1176 );
1177 }
1178
1179 #[test]
1180 fn debug_message_accepts_string_and_str_via_into() {
1181 let from_str = LayoutDebugMessage::info("borrowed");
1183 let from_string = LayoutDebugMessage::info(String::from("owned"));
1184 assert_eq!(from_str.message.as_str(), "borrowed");
1185 assert_eq!(from_string.message.as_str(), "owned");
1186 }
1187
1188 #[test]
1189 fn debug_message_clone_is_a_deep_equal_copy() {
1190 let m = LayoutDebugMessage::error("clone me \u{1F600}");
1191 let c = m.clone();
1192 assert_eq!(c, m);
1193 assert_ne!(
1194 c.message.as_bytes().as_ptr(),
1195 m.message.as_bytes().as_ptr(),
1196 "clone must deep-copy the library-owned message bytes"
1197 );
1198 }
1199
1200 #[test]
1205 fn azstring_default_is_empty_and_readable() {
1206 let s = AzString::default();
1207 assert_eq!(s.as_str(), "");
1208 assert_eq!(s.len(), 0);
1209 assert!(s.is_empty());
1210 assert_eq!(s.as_bytes(), b"");
1211 }
1212
1213 #[test]
1214 fn azstring_from_const_str_borrows_the_static_and_never_frees_it() {
1215 const TEXT: &str = "static text";
1218 let s = AzString::from_const_str(TEXT);
1219 assert_eq!(s.as_str(), TEXT);
1220 assert_eq!(s.len(), 11);
1221 assert!(
1222 matches!(s.vec.destructor, U8VecDestructor::NoDestructor),
1223 "a &'static str must not get a freeing destructor"
1224 );
1225 assert_eq!(
1226 s.vec.ptr,
1227 TEXT.as_bytes().as_ptr(),
1228 "from_const_str must alias the static, not copy it"
1229 );
1230 }
1231
1232 #[test]
1233 fn azstring_from_const_str_empty_and_unicode() {
1234 let empty = AzString::from_const_str("");
1235 assert!(empty.is_empty());
1236 assert_eq!(empty.as_str(), "");
1237 assert_eq!(empty.len(), 0);
1238
1239 let uni = AzString::from_const_str("héllo \u{1F600}");
1240 assert_eq!(uni.as_str(), "héllo \u{1F600}");
1241 assert_eq!(uni.len(), "héllo \u{1F600}".len());
1243 assert_ne!(
1244 uni.len(),
1245 uni.as_str().chars().count(),
1246 "len() must be a byte length, not a char count"
1247 );
1248 }
1249
1250 #[test]
1251 fn azstring_from_string_round_trips_edge_values() {
1252 for input in [
1253 String::new(),
1254 String::from(" "),
1255 String::from("\t\n\r"),
1256 String::from("0"),
1257 String::from("-0"),
1258 String::from("9223372036854775807"), String::from("NaN"),
1260 String::from("inf"),
1261 String::from(" valid "),
1262 String::from("valid;garbage"),
1263 String::from("\u{1F600}\u{0301}\u{0}"), "{".repeat(10_000), ] {
1266 let s = AzString::from_string(input.clone());
1267 assert_eq!(s.as_str(), input.as_str(), "from_string must be verbatim");
1268 assert_eq!(s.len(), input.len());
1269 assert_eq!(s.is_empty(), input.is_empty());
1270 assert_eq!(s.into_library_owned_string(), input);
1272 }
1273 }
1274
1275 #[test]
1276 fn azstring_from_string_handles_a_megabyte() {
1277 let huge = "x".repeat(1_000_000);
1278 let s = AzString::from_string(huge.clone());
1279 assert_eq!(s.len(), 1_000_000);
1280 assert_eq!(s.as_str().len(), huge.len());
1281 assert!(s.as_str().bytes().all(|b| b == b'x'));
1282 }
1283
1284 #[test]
1285 fn azstring_from_string_preserves_the_original_capacity() {
1286 let mut owned = String::with_capacity(4096);
1289 owned.push_str("hi");
1290 let s = AzString::from_string(owned);
1291 assert!(matches!(s.vec.destructor, U8VecDestructor::DefaultRust));
1292 let back = s.into_library_owned_string();
1293 assert_eq!(back, "hi");
1294 assert!(
1295 back.capacity() >= 4096,
1296 "capacity must survive the AzString round-trip, got {}",
1297 back.capacity()
1298 );
1299 }
1300
1301 #[test]
1306 fn azstring_copy_from_bytes_zero_len_is_empty() {
1307 let buf = b"hello";
1308 let s = AzString::copy_from_bytes(buf.as_ptr(), 0, 0);
1309 assert!(s.is_empty());
1310 assert_eq!(s.as_str(), "");
1311 }
1312
1313 #[test]
1314 fn azstring_copy_from_bytes_null_ptr_is_empty() {
1315 let s = AzString::copy_from_bytes(core::ptr::null(), 0, 16);
1316 assert!(s.is_empty());
1317 assert_eq!(s.as_str(), "");
1318 }
1319
1320 #[test]
1321 fn azstring_copy_from_bytes_honours_the_start_offset() {
1322 let buf = b"0123456789";
1323 let s = AzString::copy_from_bytes(buf.as_ptr(), 3, 4);
1324 assert_eq!(s.as_str(), "3456");
1325 assert_eq!(s.len(), 4);
1326 }
1327
1328 #[test]
1329 fn azstring_copy_from_bytes_start_at_end_with_zero_len_is_empty() {
1330 let buf = b"abc";
1333 let s = AzString::copy_from_bytes(buf.as_ptr(), buf.len(), 0);
1334 assert!(s.is_empty());
1335 }
1336
1337 #[test]
1338 fn azstring_copy_from_bytes_zero_len_wins_over_start_overflow() {
1339 let buf = b"abc";
1342 let s = AzString::copy_from_bytes(buf.as_ptr(), usize::MAX, 0);
1343 assert!(s.is_empty());
1344 }
1345
1346 #[test]
1347 fn azstring_copy_from_bytes_null_wins_over_max_len() {
1348 let s = AzString::copy_from_bytes(core::ptr::null(), usize::MAX, usize::MAX);
1350 assert!(s.is_empty());
1351 assert_eq!(s.as_str(), "");
1352 }
1353
1354 #[test]
1355 fn azstring_copy_from_bytes_replaces_invalid_utf8_lossily() {
1356 let buf = "héllo".as_bytes();
1359 assert_eq!(buf[1], 0xC3);
1360 assert_eq!(buf[2], 0xA9);
1361 let s = AzString::copy_from_bytes(buf.as_ptr(), 2, 2);
1362 assert_eq!(s.as_str(), "\u{FFFD}l");
1363 assert!(core::str::from_utf8(s.as_bytes()).is_ok());
1365 }
1366
1367 #[test]
1368 fn azstring_copy_from_bytes_keeps_valid_utf8_byte_for_byte() {
1369 let buf = "héllo \u{1F600}".as_bytes();
1370 let s = AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len());
1371 assert_eq!(s.as_str(), "héllo \u{1F600}");
1372 assert_eq!(s.as_bytes(), buf);
1373 }
1374
1375 #[test]
1376 fn azstring_copy_from_bytes_preserves_interior_nul() {
1377 let buf = b"a\0b";
1378 let s = AzString::copy_from_bytes(buf.as_ptr(), 0, 3);
1379 assert_eq!(s.len(), 3, "an interior NUL is data, not a terminator");
1380 assert_eq!(s.as_bytes(), b"a\0b");
1381 }
1382
1383 #[test]
1388 fn u8vec_copy_from_bytes_zero_len_is_empty() {
1389 let buf = b"hello";
1390 let v = U8Vec::copy_from_bytes(buf.as_ptr(), 0, 0);
1391 assert!(v.is_empty());
1392 assert_eq!(v.as_ref(), b"");
1393 }
1394
1395 #[test]
1396 fn u8vec_copy_from_bytes_null_ptr_is_empty() {
1397 let v = U8Vec::copy_from_bytes(core::ptr::null(), 0, 8);
1398 assert!(v.is_empty());
1399 assert_eq!(v.len(), 0);
1400 }
1401
1402 #[test]
1403 fn u8vec_copy_from_bytes_null_wins_over_max_start_and_len() {
1404 let v = U8Vec::copy_from_bytes(core::ptr::null(), usize::MAX, usize::MAX);
1406 assert!(v.is_empty());
1407 }
1408
1409 #[test]
1410 fn u8vec_copy_from_bytes_zero_len_wins_over_start_overflow() {
1411 let buf = b"abc";
1413 let v = U8Vec::copy_from_bytes(buf.as_ptr(), usize::MAX, 0);
1414 assert!(v.is_empty());
1415 }
1416
1417 #[test]
1418 fn u8vec_copy_from_bytes_copies_the_requested_window() {
1419 let buf: Vec<u8> = (0u8..=255).collect();
1420 let v = U8Vec::copy_from_bytes(buf.as_ptr(), 250, 6);
1421 assert_eq!(v.as_ref(), &[250, 251, 252, 253, 254, 255]);
1422 assert_eq!(v.len(), 6);
1423 }
1424
1425 #[test]
1426 fn u8vec_copy_from_bytes_owns_its_copy() {
1427 let v = {
1429 let buf = vec![1u8, 2, 3, 4];
1430 U8Vec::copy_from_bytes(buf.as_ptr(), 1, 2)
1431 };
1432 assert_eq!(v.as_ref(), &[2, 3]);
1433 assert!(matches!(v.destructor, U8VecDestructor::DefaultRust));
1434 }
1435
1436 #[test]
1437 fn u8vec_copy_from_bytes_accepts_all_byte_values() {
1438 let buf: Vec<u8> = (0u8..=255).collect();
1441 let v = U8Vec::copy_from_bytes(buf.as_ptr(), 0, buf.len());
1442 assert_eq!(v.as_ref(), buf.as_slice());
1443 }
1444
1445 #[test]
1450 fn azstring_from_c_str_null_is_empty() {
1451 let s = unsafe { AzString::from_c_str(core::ptr::null()) };
1452 assert!(s.is_empty());
1453 assert_eq!(s.as_str(), "");
1454 }
1455
1456 #[test]
1457 fn azstring_from_c_str_reads_up_to_the_terminator() {
1458 let c = b"hello\0trailing garbage\0";
1459 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1460 assert_eq!(s.as_str(), "hello");
1461 assert_eq!(s.len(), 5, "the NUL terminator is not part of the string");
1462 }
1463
1464 #[test]
1465 fn azstring_from_c_str_empty_c_string_is_empty() {
1466 let c = b"\0";
1467 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1468 assert!(s.is_empty());
1469 }
1470
1471 #[test]
1472 fn azstring_from_c_str_replaces_non_utf8_bytes() {
1473 let c = b"caf\xE9\0";
1476 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1477 assert_eq!(s.as_str(), "caf\u{FFFD}");
1478 assert!(core::str::from_utf8(s.as_bytes()).is_ok());
1479 }
1480
1481 #[test]
1482 fn azstring_from_c_str_handles_a_long_c_string() {
1483 let mut c = "z".repeat(100_000).into_bytes();
1484 c.push(0);
1485 let s = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1486 assert_eq!(s.len(), 100_000);
1487 }
1488
1489 #[test]
1494 fn azstring_to_c_str_appends_exactly_one_nul() {
1495 let s = AzString::from_const_str("abc");
1496 let c = s.to_c_str();
1497 assert_eq!(c.as_ref(), b"abc\0");
1498 assert_eq!(c.len(), s.len() + 1);
1499 }
1500
1501 #[test]
1502 fn azstring_to_c_str_of_empty_is_just_the_terminator() {
1503 let c = AzString::default().to_c_str();
1504 assert_eq!(c.as_ref(), b"\0");
1505 assert_eq!(c.len(), 1);
1506 }
1507
1508 #[test]
1509 fn azstring_c_str_round_trip() {
1510 for original in ["", "abc", "héllo \u{1F600}", " spaced "] {
1511 let s = AzString::from_const_str(original);
1512 let c = s.to_c_str();
1513 let back = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1514 assert_eq!(back.as_str(), original, "C round-trip must be lossless");
1515 assert_eq!(back, s);
1516 }
1517 }
1518
1519 #[test]
1520 fn azstring_c_str_round_trip_truncates_at_an_interior_nul() {
1521 let s = AzString::from_string(String::from("a\0b"));
1525 let c = s.to_c_str();
1526 assert_eq!(c.as_ref(), b"a\0b\0", "to_c_str keeps the interior NUL");
1527 let back = unsafe { AzString::from_c_str(c.as_ptr().cast::<i8>()) };
1528 assert_eq!(back.as_str(), "a", "from_c_str stops at the first NUL");
1529 }
1530
1531 #[test]
1532 fn azstring_to_c_str_is_an_independent_allocation() {
1533 let s = AzString::from_const_str("shared?");
1534 let c = s.to_c_str();
1535 assert!(matches!(c.destructor, U8VecDestructor::DefaultRust));
1536 assert_ne!(
1537 c.as_ptr(),
1538 s.as_bytes().as_ptr(),
1539 "to_c_str must copy, not alias the source"
1540 );
1541 assert_eq!(s.as_str(), "shared?", "source must be untouched");
1542 }
1543
1544 #[test]
1549 fn azstring_from_utf8_null_or_zero_len_is_empty() {
1550 let buf = b"abc";
1551 assert!(unsafe { AzString::from_utf8(core::ptr::null(), 3) }.is_empty());
1552 assert!(unsafe { AzString::from_utf8(buf.as_ptr(), 0) }.is_empty());
1553 assert!(unsafe { AzString::from_utf8_lossy(core::ptr::null(), 3) }.is_empty());
1554 assert!(unsafe { AzString::from_utf8_lossy(buf.as_ptr(), 0) }.is_empty());
1555 }
1556
1557 #[test]
1558 fn azstring_from_utf8_accepts_valid_multibyte() {
1559 let buf = "héllo \u{1F600}".as_bytes();
1560 let s = unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) };
1561 assert_eq!(s.as_str(), "héllo \u{1F600}");
1562 assert_eq!(s.len(), buf.len());
1563 }
1564
1565 #[test]
1566 fn azstring_from_utf8_rejects_invalid_but_lossy_replaces_it() {
1567 let buf = b"caf\xC3";
1569 let strict = unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) };
1570 assert!(
1571 strict.is_empty(),
1572 "from_utf8 must return an EMPTY string for invalid UTF-8, got {:?}",
1573 strict.as_str()
1574 );
1575 let lossy = unsafe { AzString::from_utf8_lossy(buf.as_ptr(), buf.len()) };
1576 assert_eq!(lossy.as_str(), "caf\u{FFFD}");
1577 }
1578
1579 #[test]
1580 fn azstring_from_utf8_rejects_overlong_and_stray_continuations() {
1581 for bad in [
1582 &b"\xC0\xAF"[..], &b"\xED\xA0\x80"[..], &b"\xF8\x88\x80\x80"[..], &b"\x80"[..], &b"\xFF\xFE"[..], ] {
1588 let strict = unsafe { AzString::from_utf8(bad.as_ptr(), bad.len()) };
1589 assert!(strict.is_empty(), "from_utf8 must reject {bad:?}");
1590
1591 let lossy = unsafe { AzString::from_utf8_lossy(bad.as_ptr(), bad.len()) };
1592 assert!(
1593 lossy.as_str().contains('\u{FFFD}'),
1594 "from_utf8_lossy must substitute U+FFFD for {bad:?}"
1595 );
1596 assert!(core::str::from_utf8(lossy.as_bytes()).is_ok());
1598 }
1599 }
1600
1601 #[test]
1602 fn azstring_from_utf8_keeps_interior_nul() {
1603 let buf = b"a\0b";
1604 let s = unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) };
1605 assert_eq!(s.len(), 3);
1606 assert_eq!(s.as_bytes(), b"a\0b");
1607 }
1608
1609 #[test]
1610 fn azstring_from_utf8_handles_a_megabyte() {
1611 let buf = "y".repeat(1_000_000);
1612 let s = unsafe { AzString::from_utf8(buf.as_bytes().as_ptr(), buf.len()) };
1613 assert_eq!(s.len(), 1_000_000);
1614 }
1615
1616 #[test]
1621 fn azstring_from_utf16_le_decodes_bmp_and_surrogate_pairs() {
1622 let text = "héllo \u{1F600}"; let bytes = utf16_bytes(text, true);
1624 let s = unsafe { AzString::from_utf16_le(bytes.as_ptr(), bytes.len()) };
1625 assert_eq!(s.as_str(), text);
1626 }
1627
1628 #[test]
1629 fn azstring_from_utf16_be_decodes_bmp_and_surrogate_pairs() {
1630 let text = "héllo \u{1F600}";
1631 let bytes = utf16_bytes(text, false);
1632 let s = unsafe { AzString::from_utf16_be(bytes.as_ptr(), bytes.len()) };
1633 assert_eq!(s.as_str(), text);
1634 }
1635
1636 #[test]
1637 fn azstring_from_utf16_byte_order_actually_matters() {
1638 let le = utf16_bytes("AB", true);
1640 assert_eq!(le.as_slice(), &[0x41, 0x00, 0x42, 0x00]);
1641 let as_be = unsafe { AzString::from_utf16_be(le.as_ptr(), le.len()) };
1642 assert_eq!(
1643 as_be.as_str(),
1644 "\u{4100}\u{4200}",
1645 "BE decode of LE bytes must byte-swap, not guess"
1646 );
1647 assert_ne!(as_be.as_str(), "AB");
1648 }
1649
1650 #[test]
1651 fn azstring_from_utf16_odd_length_is_empty() {
1652 let bytes = utf16_bytes("hello", true);
1653 let odd = bytes.len() - 1;
1654 assert_eq!(odd % 2, 1);
1655 assert!(unsafe { AzString::from_utf16_le(bytes.as_ptr(), odd) }.is_empty());
1658 assert!(unsafe { AzString::from_utf16_be(bytes.as_ptr(), odd) }.is_empty());
1659 assert!(unsafe { AzString::from_utf16_le(bytes.as_ptr(), 1) }.is_empty());
1661 }
1662
1663 #[test]
1664 fn azstring_from_utf16_null_or_zero_len_is_empty() {
1665 let bytes = utf16_bytes("hi", true);
1666 assert!(unsafe { AzString::from_utf16_le(core::ptr::null(), 4) }.is_empty());
1667 assert!(unsafe { AzString::from_utf16_be(core::ptr::null(), 4) }.is_empty());
1668 assert!(unsafe { AzString::from_utf16_le(bytes.as_ptr(), 0) }.is_empty());
1669 assert!(unsafe { AzString::from_utf16_be(bytes.as_ptr(), 0) }.is_empty());
1670 }
1671
1672 #[test]
1673 fn azstring_from_utf16_unpaired_surrogate_is_empty() {
1674 let lone_high: [u8; 2] = 0xD83C_u16.to_le_bytes();
1676 assert!(unsafe { AzString::from_utf16_le(lone_high.as_ptr(), 2) }.is_empty());
1677
1678 let lone_low: [u8; 2] = 0xDF89_u16.to_le_bytes();
1680 assert!(unsafe { AzString::from_utf16_le(lone_low.as_ptr(), 2) }.is_empty());
1681
1682 let reversed: Vec<u8> = [0xDF89_u16, 0xD83C_u16]
1683 .iter()
1684 .flat_map(|u| u.to_le_bytes())
1685 .collect();
1686 assert!(unsafe { AzString::from_utf16_le(reversed.as_ptr(), reversed.len()) }.is_empty());
1687 }
1688
1689 #[test]
1690 fn azstring_from_utf16_decodes_noncharacters_and_nul() {
1691 let units: Vec<u8> = [0x0041_u16, 0x0000, 0xFFFE]
1694 .iter()
1695 .flat_map(|u| u.to_le_bytes())
1696 .collect();
1697 let s = unsafe { AzString::from_utf16_le(units.as_ptr(), units.len()) };
1698 assert_eq!(s.as_str(), "A\u{0}\u{FFFE}");
1699 assert_eq!(s.len(), 1 + 1 + 3);
1700 }
1701
1702 #[test]
1703 fn azstring_from_utf16_handles_100k_code_units() {
1704 let text = "ab".repeat(50_000);
1705 let bytes = utf16_bytes(&text, true);
1706 assert_eq!(bytes.len(), 200_000);
1707 let s = unsafe { AzString::from_utf16_le(bytes.as_ptr(), bytes.len()) };
1708 assert_eq!(s.len(), 100_000);
1709 }
1710
1711 #[test]
1712 fn azstring_from_utf16_with_byte_order_honours_the_supplied_fn() {
1713 fn swap_halves(b: [u8; 2]) -> u16 {
1715 u16::from_be_bytes(b)
1716 }
1717 let le = utf16_bytes("Az", true);
1718 let via_shared = unsafe {
1719 AzString::from_utf16_with_byte_order(le.as_ptr(), le.len(), u16::from_le_bytes)
1720 };
1721 assert_eq!(via_shared.as_str(), "Az");
1722
1723 let swapped =
1724 unsafe { AzString::from_utf16_with_byte_order(le.as_ptr(), le.len(), swap_halves) };
1725 assert_eq!(swapped.as_str(), "\u{4100}\u{7A00}");
1726
1727 assert!(unsafe {
1729 AzString::from_utf16_with_byte_order(le.as_ptr(), 3, u16::from_le_bytes)
1730 }
1731 .is_empty());
1732 assert!(unsafe {
1733 AzString::from_utf16_with_byte_order(core::ptr::null(), 2, u16::from_le_bytes)
1734 }
1735 .is_empty());
1736 }
1737
1738 #[test]
1743 fn azstring_as_str_and_as_bytes_agree_for_every_constructor() {
1744 let buf = "mixed \u{1F600}".as_bytes();
1745 let mut arena = StringArena::new();
1746 let strings = [
1747 AzString::default(),
1748 AzString::from_const_str("mixed \u{1F600}"),
1749 AzString::from_string(String::from("mixed \u{1F600}")),
1750 AzString::from("mixed \u{1F600}"),
1751 AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len()),
1752 unsafe { AzString::from_utf8(buf.as_ptr(), buf.len()) },
1753 arena.intern("mixed \u{1F600}"),
1754 ];
1755 for s in &strings {
1756 assert_eq!(
1757 s.as_bytes(),
1758 s.as_str().as_bytes(),
1759 "as_bytes() and as_str() must view the same memory"
1760 );
1761 assert_eq!(s.len(), s.as_bytes().len());
1762 assert_eq!(s.is_empty(), s.len() == 0);
1763 let via_as_ref: &str = s.as_ref();
1764 assert_eq!(via_as_ref, s.as_str(), "AsRef must match as_str");
1765 assert_eq!(&**s, s.as_str(), "Deref must match as_str");
1766 }
1767 }
1768
1769 #[test]
1770 fn azstring_is_empty_only_for_zero_bytes() {
1771 assert!(AzString::default().is_empty());
1772 assert!(AzString::from_const_str("").is_empty());
1773 assert!(AzString::from_string(String::new()).is_empty());
1774 assert!(!AzString::from_const_str(" ").is_empty());
1776 assert!(!AzString::from_const_str("\t\n").is_empty());
1777 assert!(!AzString::from_string(String::from("\0")).is_empty());
1778 assert_eq!(AzString::from_string(String::from("\0")).len(), 1);
1779 }
1780
1781 #[test]
1782 fn azstring_len_counts_bytes_not_chars() {
1783 assert_eq!(AzString::from_const_str("é").len(), 2);
1784 assert_eq!(AzString::from_const_str("\u{1F600}").len(), 4);
1785 assert_eq!(AzString::from_const_str("e\u{0301}").len(), 3); assert_eq!(
1787 AzString::from_const_str("\u{1F600}")
1788 .as_str()
1789 .chars()
1790 .count(),
1791 1
1792 );
1793 }
1794
1795 #[test]
1796 fn azstring_into_bytes_moves_without_copying_or_double_freeing() {
1797 let s = AzString::from_string(String::from("payload"));
1798 let ptr = s.as_bytes().as_ptr();
1799 let (len, cap) = (s.vec.len, s.vec.cap);
1800 let v = s.into_bytes();
1801 assert_eq!(v.as_ref(), b"payload");
1802 assert_eq!(v.as_ptr(), ptr, "into_bytes must move, not copy");
1803 assert_eq!(v.len(), len);
1804 assert_eq!(v.capacity(), cap);
1805 assert!(matches!(v.destructor, U8VecDestructor::DefaultRust));
1806 }
1809
1810 #[test]
1811 fn azstring_into_bytes_preserves_a_non_owning_destructor() {
1812 let v = AzString::from_const_str("static").into_bytes();
1813 assert_eq!(v.as_ref(), b"static");
1814 assert!(
1815 matches!(v.destructor, U8VecDestructor::NoDestructor),
1816 "a &'static-backed AzString must not gain a freeing destructor"
1817 );
1818 }
1819
1820 #[test]
1821 fn azstring_into_bytes_of_empty_is_empty() {
1822 let v = AzString::default().into_bytes();
1823 assert!(v.is_empty());
1824 assert_eq!(v.as_ref(), b"");
1825 }
1826
1827 #[test]
1828 fn azstring_into_library_owned_string_works_for_all_destructors() {
1829 assert_eq!(
1831 AzString::from_string(String::from("owned \u{1F600}")).into_library_owned_string(),
1832 "owned \u{1F600}"
1833 );
1834 assert_eq!(
1836 AzString::from_const_str("static").into_library_owned_string(),
1837 "static"
1838 );
1839 let owned = {
1841 let mut arena = StringArena::new();
1842 let s = arena.intern("interned");
1843 s.into_library_owned_string()
1844 };
1845 assert_eq!(
1846 owned, "interned",
1847 "must outlive the arena it was copied from"
1848 );
1849 assert_eq!(AzString::default().into_library_owned_string(), "");
1851 }
1852
1853 #[test]
1854 fn azstring_into_library_owned_string_copies_static_memory() {
1855 let mut owned = AzString::from_const_str("static").into_library_owned_string();
1856 owned.push_str(" + mutable");
1859 assert_eq!(owned, "static + mutable");
1860 }
1861
1862 #[test]
1867 fn azstring_clone_self_deep_copies_library_owned_memory() {
1868 let s = AzString::from_string(String::from("deep"));
1869 let c = s.clone_self();
1870 assert_eq!(c, s);
1871 assert_ne!(
1872 c.as_bytes().as_ptr(),
1873 s.as_bytes().as_ptr(),
1874 "a DefaultRust clone must own a fresh allocation"
1875 );
1876 assert!(matches!(c.vec.destructor, U8VecDestructor::DefaultRust));
1877 }
1878
1879 #[test]
1880 fn azstring_clone_self_shares_static_memory() {
1881 let s = AzString::from_const_str("static");
1882 let c = s.clone_self();
1883 assert_eq!(c, s);
1884 assert_eq!(
1885 c.as_bytes().as_ptr(),
1886 s.as_bytes().as_ptr(),
1887 "cloning a &'static-backed string should alias, not allocate"
1888 );
1889 assert!(matches!(c.vec.destructor, U8VecDestructor::NoDestructor));
1890 }
1891
1892 #[test]
1893 fn azstring_clone_self_of_empty_and_unicode() {
1894 for s in [
1895 AzString::default(),
1896 AzString::from_const_str(""),
1897 AzString::from_string(String::from("\u{1F600}\u{0}\u{0301}")),
1898 ] {
1899 let c = s.clone_self();
1900 assert_eq!(c.as_str(), s.as_str());
1901 assert_eq!(c.len(), s.len());
1902 }
1903 }
1904
1905 #[test]
1906 fn azstring_clone_trait_matches_clone_self() {
1907 let s = AzString::from_string(String::from("via trait"));
1908 assert_eq!(s.clone(), s.clone_self());
1909 }
1910
1911 #[test]
1916 fn azstring_display_round_trips_through_from() {
1917 for original in [
1918 "",
1919 " ",
1920 "plain",
1921 "héllo \u{1F600}",
1922 "with \"quotes\" and \\ backslash",
1923 "line\nbreak\ttab",
1924 "e\u{0301} combining",
1925 ] {
1926 let s = AzString::from(original);
1927 let rendered = format!("{s}");
1928 assert_eq!(rendered, original, "Display must emit the string verbatim");
1929 let reparsed = AzString::from(rendered.as_str());
1930 assert_eq!(reparsed, s, "parse(serialize(x)) == x");
1931 assert_eq!(format!("{reparsed}"), rendered);
1933 }
1934 }
1935
1936 #[test]
1937 fn azstring_debug_matches_str_debug_and_escapes() {
1938 let s = AzString::from("a\"b\\c\nd");
1939 let expected = format!("{:?}", "a\"b\\c\nd");
1940 assert_eq!(
1941 format!("{s:?}"),
1942 expected,
1943 "Debug must delegate to str::fmt"
1944 );
1945 assert!(
1946 format!("{s:?}").starts_with('"'),
1947 "Debug output must be quoted"
1948 );
1949 assert!(
1950 !format!("{s:?}").contains('\n'),
1951 "Debug must escape newlines"
1952 );
1953 }
1954
1955 #[test]
1956 fn azstring_debug_and_display_of_empty_do_not_panic() {
1957 assert_eq!(format!("{:?}", AzString::default()), "\"\"");
1958 assert_eq!(format!("{}", AzString::default()), "");
1959 assert_eq!(format!("{:?}", AzString::from_const_str("")), "\"\"");
1960 }
1961
1962 #[test]
1963 fn azstring_display_of_a_megabyte_is_lossless() {
1964 let huge = "q".repeat(1_000_000);
1965 let s = AzString::from_string(huge.clone());
1966 assert_eq!(format!("{s}").len(), huge.len());
1967 }
1968
1969 #[test]
1970 fn azstring_debug_is_stable_across_constructors() {
1971 let buf = "same".as_bytes();
1973 let a = AzString::from_const_str("same");
1974 let b = AzString::from_string(String::from("same"));
1975 let c = AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len());
1976 assert_eq!(format!("{a:?}"), format!("{b:?}"));
1977 assert_eq!(format!("{b:?}"), format!("{c:?}"));
1978 assert_eq!(format!("{a}"), format!("{c}"));
1979 }
1980
1981 #[test]
1986 fn azstring_eq_and_hash_ignore_memory_ownership() {
1987 let buf = "key".as_bytes();
1988 let mut arena = StringArena::new();
1989 let variants = [
1990 AzString::from_const_str("key"),
1991 AzString::from_string(String::from("key")),
1992 AzString::copy_from_bytes(buf.as_ptr(), 0, buf.len()),
1993 arena.intern("key"),
1994 ];
1995 for v in &variants {
1996 assert_eq!(
1997 *v, variants[0],
1998 "equality must compare CONTENT, not pointers"
1999 );
2000 assert_eq!(
2001 hash_of(v),
2002 hash_of(&variants[0]),
2003 "Hash must agree with Eq across destructor kinds"
2004 );
2005 assert_eq!(
2006 hash_of(v),
2007 hash_of(&"key"),
2008 "AzString must hash like the &str it wraps"
2009 );
2010 }
2011 }
2012
2013 #[test]
2014 fn azstring_ord_matches_str_ord() {
2015 let mut v = [
2016 AzString::from("b"),
2017 AzString::from(""),
2018 AzString::from("\u{1F600}"),
2019 AzString::from("a"),
2020 AzString::from("ab"),
2021 ];
2022 v.sort();
2023 let sorted: Vec<&str> = v.iter().map(AzString::as_str).collect();
2024 assert_eq!(sorted, ["", "a", "ab", "b", "\u{1F600}"]);
2025 assert_eq!(
2026 AzString::from("a").partial_cmp(&AzString::from("b")),
2027 Some(core::cmp::Ordering::Less)
2028 );
2029 assert_eq!(
2030 AzString::from("x").cmp(&AzString::from("x")),
2031 core::cmp::Ordering::Equal
2032 );
2033 }
2034
2035 #[test]
2040 fn arena_new_starts_empty() {
2041 let arena = StringArena::new();
2042 assert_eq!(arena.metrics(), (0, 0), "a fresh arena allocates nothing");
2043 assert_eq!(StringArena::default().metrics(), (0, 0));
2044 }
2045
2046 #[test]
2047 fn arena_metrics_track_chunks_and_bytes() {
2048 let mut arena = StringArena::new();
2049 let _a = arena.intern("abc");
2050 let (chunks, bytes) = arena.metrics();
2051 assert_eq!(chunks, 1);
2052 assert_eq!(bytes, 3);
2053 let _b = arena.intern("de");
2054 let (chunks, bytes) = arena.metrics();
2055 assert_eq!(chunks, 1, "a second small string reuses the open chunk");
2056 assert_eq!(bytes, 5);
2057 }
2058
2059 #[test]
2060 fn arena_empty_string_allocates_nothing_and_is_readable() {
2061 let mut arena = StringArena::new();
2062 let e = arena.intern("");
2063 assert!(e.is_empty());
2064 assert_eq!(e.as_str(), "");
2065 assert_eq!(arena.metrics(), (0, 0), "empty strings need no storage");
2066 assert!(
2067 !e.vec.ptr.is_null(),
2068 "the dangling ptr must still be non-null"
2069 );
2070 }
2071
2072 #[test]
2073 fn arena_string_is_external_and_stashes_an_arc_in_cap() {
2074 let mut arena = StringArena::new();
2075 let s = arena.intern("hi");
2076 assert!(matches!(s.vec.destructor, U8VecDestructor::External(_)));
2077 assert_ne!(s.vec.cap, 0, "cap holds the Arc pointer, not a capacity");
2078 assert_eq!(s.as_str(), "hi");
2079 }
2080
2081 #[test]
2082 fn arena_intern_refcounts_each_string() {
2083 let mut arena = StringArena::new();
2084 assert_eq!(Arc::strong_count(&arena.inner), 1);
2085 let a = arena.intern("one");
2086 let b = arena.intern("two");
2087 assert_eq!(
2088 Arc::strong_count(&arena.inner),
2089 3,
2090 "each interned string must hold its own Arc reference"
2091 );
2092 drop(a);
2093 assert_eq!(Arc::strong_count(&arena.inner), 2);
2094 drop(b);
2095 assert_eq!(Arc::strong_count(&arena.inner), 1);
2096 }
2097
2098 #[test]
2099 fn arena_clone_deep_copies_and_does_not_bump_the_refcount() {
2100 let mut arena = StringArena::new();
2101 let s = arena.intern("interned");
2102 let c = s.clone_self();
2103 assert_eq!(
2104 Arc::strong_count(&arena.inner),
2105 2,
2106 "cloning an External string deep-copies; it must NOT retain the arena"
2107 );
2108 assert!(matches!(c.vec.destructor, U8VecDestructor::DefaultRust));
2109 assert_eq!(c.as_str(), "interned");
2110 assert_ne!(c.vec.ptr, s.vec.ptr);
2111 }
2112
2113 #[test]
2114 fn arena_clone_outlives_the_arena_and_the_original() {
2115 let clone = {
2116 let mut arena = StringArena::new();
2117 let s = arena.intern("deep-copied out of the arena");
2118 let c = s.clone_self();
2119 drop(s);
2120 drop(arena);
2121 c
2122 };
2123 assert_eq!(clone.as_str(), "deep-copied out of the arena");
2124 }
2125
2126 #[test]
2127 fn arena_exact_half_chunk_boundary_fills_one_chunk_exactly() {
2128 let mut arena = StringArena::new();
2132 let half = "h".repeat(StringArena::CHUNK_SIZE / 2);
2133 let a = arena.intern(&half);
2134 let b = arena.intern(&half);
2135 assert_eq!(arena.metrics().0, 1, "two half-chunks must share one chunk");
2136 let c = arena.intern(&half);
2137 assert_eq!(arena.metrics().0, 2, "the third must open a new chunk");
2138
2139 assert_eq!(a.as_str(), half);
2141 assert_eq!(b.as_str(), half);
2142 assert_eq!(c.as_str(), half);
2143 }
2144
2145 #[test]
2146 fn arena_oversized_string_is_readable_and_gets_its_own_chunk() {
2147 let mut arena = StringArena::new();
2148 let big = "b".repeat(StringArena::CHUNK_SIZE + 1);
2149 let s = arena.intern(&big);
2150 assert_eq!(s.len(), big.len());
2151 assert_eq!(s.as_str(), big.as_str());
2152 assert_eq!(arena.metrics(), (1, big.len()));
2153 }
2154
2155 #[test]
2156 fn arena_many_interleaved_sizes_all_read_back_correctly() {
2157 let mut arena = StringArena::new();
2158 let mut kept = Vec::new();
2159 for i in 0..200 {
2160 let s = format!("s{i}-{}", "p".repeat(i % 17));
2161 kept.push((arena.intern(&s), s));
2162 }
2163 for (interned, expected) in &kept {
2164 assert_eq!(interned.as_str(), expected.as_str());
2165 }
2166 }
2167
2168 #[test]
2169 fn arena_interns_unicode_and_nul_bytes_verbatim() {
2170 let mut arena = StringArena::new();
2171 let weird = "héllo \u{1F600}\u{0}\u{0301}";
2172 let s = arena.intern(weird);
2173 assert_eq!(s.as_str(), weird);
2174 assert_eq!(s.len(), weird.len());
2175 }
2176
2177 #[test]
2178 fn arena_strings_outlive_the_handle_even_when_interleaved() {
2179 let (a, b) = {
2180 let mut arena = StringArena::new();
2181 let a = arena.intern("first");
2182 let big = "z".repeat(StringArena::CHUNK_SIZE * 2);
2183 let _dropped = arena.intern(&big);
2184 let b = arena.intern("second");
2185 (a, b)
2186 };
2187 assert_eq!(a.as_str(), "first");
2188 assert_eq!(b.as_str(), "second");
2189 }
2190
2191 #[test]
2205 fn arena_small_after_oversized_must_not_grow_the_full_dedicated_chunk() {
2206 let mut arena = StringArena::new();
2207
2208 let _small = arena.intern("a");
2210
2211 let big = "x".repeat(StringArena::CHUNK_SIZE);
2213 let big_len = big.len();
2214 let interned_big = arena.intern(&big);
2215 assert_eq!(
2216 interned_big.as_str(),
2217 big.as_str(),
2218 "valid before the next intern"
2219 );
2220
2221 let _small2 = arena.intern("y");
2223
2224 let grew = unsafe {
2227 let chunks = &*arena.inner.chunks.get();
2228 chunks.iter().any(|c| c.len() > big_len)
2229 };
2230 assert!(
2231 !grew,
2232 "intern() appended a small string into the FULL dedicated chunk of an oversized \
2233 string (len == cap), which reallocates that Vec and leaves every AzString pointing \
2234 into it dangling — a use-after-free. Root cause: the oversized branch pushes a chunk \
2235 without resetting `current_remaining`, so the next small string takes the \
2236 `chunks.last_mut()` fast path onto the wrong chunk."
2237 );
2238 }
2239
2240 #[test]
2245 fn arena_destructor_drops_one_arc_ref_and_is_idempotent() {
2246 let inner = Arc::new(StringArenaInner {
2247 chunks: UnsafeCell::new(Vec::new()),
2248 current_remaining: UnsafeCell::new(0),
2249 });
2250 let raw = Arc::into_raw(Arc::clone(&inner));
2251 assert_eq!(Arc::strong_count(&inner), 2);
2252
2253 let mut v = U8Vec {
2254 ptr: core::ptr::NonNull::<u8>::dangling().as_ptr().cast_const(),
2255 len: 0,
2256 cap: raw as usize,
2257 destructor: U8VecDestructor::External(arena_string_destructor),
2258 };
2259
2260 arena_string_destructor(&mut v);
2261 assert_eq!(
2262 Arc::strong_count(&inner),
2263 1,
2264 "the destructor must release exactly one Arc reference"
2265 );
2266 assert_eq!(
2267 v.cap, 0,
2268 "cap must be zeroed to guard against a double drop"
2269 );
2270
2271 arena_string_destructor(&mut v);
2273 assert_eq!(Arc::strong_count(&inner), 1);
2274 assert_eq!(v.cap, 0);
2275
2276 drop(v);
2279 assert_eq!(Arc::strong_count(&inner), 1);
2280 }
2281
2282 #[test]
2283 fn arena_destructor_tolerates_a_null_arc_pointer() {
2284 let mut v = U8Vec {
2286 ptr: core::ptr::null(),
2287 len: 0,
2288 cap: 0,
2289 destructor: U8VecDestructor::NoDestructor,
2290 };
2291 arena_string_destructor(&mut v);
2292 assert_eq!(v.cap, 0);
2293 }
2294
2295 #[test]
2296 fn arena_last_reference_frees_the_chunks() {
2297 let inner_ptr;
2300 let s = {
2301 let mut arena = StringArena::new();
2302 let s = arena.intern("outlives the handle");
2303 inner_ptr = Arc::as_ptr(&arena.inner);
2304 assert_eq!(Arc::strong_count(&arena.inner), 2);
2305 s
2306 };
2307 assert_eq!(s.as_str(), "outlives the handle");
2309 assert_eq!(s.vec.cap as *const StringArenaInner, inner_ptr);
2310 drop(s); }
2312}