1use crate::gc::GcState;
5use crate::gc::alloc_string;
6use crate::rt::EnumType;
7use crate::rt::Struct;
8use crate::vm::{MapPool, ObjectPool, RegisterFile, StringPool, char_byte_offset, count_chars};
9use smol_strc::SmolStr;
10use smol_strc::ToSmolStr;
11use std::hash::Hash;
12use std::hash::Hasher;
13use std::hint::cold_path;
14use std::hint::unreachable_unchecked;
15
16const NAN_BASE: u64 =
17 0b1111_1111_1111_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
18const PAYLOAD_MASK: u64 = 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111;
19const NAN_BOOL: u64 = NAN_BASE | (1 << 48);
20const NAN_STRING_SMALL: u64 = NAN_BASE | (2 << 48);
21const NAN_STRING_LARGE: u64 = NAN_BASE | (3 << 48);
22const NAN_ARRAY: u64 = NAN_BASE | (4 << 48);
23const NAN_NULL: u64 = NAN_BASE | (5 << 48);
24const NAN_INT: u64 = NAN_BASE | (6 << 48);
25const NAN_STRUCT: u64 = NAN_BASE | (7 << 48);
26const NAN_MAP: u64 = NAN_BASE | (7 << 48) | (1 << 47);
27const NAN_ENUM: u64 = NAN_BASE;
38const CANONICAL_NAN: u64 =
43 0b0111_1111_1111_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
44const SMALL_STR_HIGH_BITS: u64 = 0x0000_8080_8080_8080;
47const NAN_FN_MARK: u64 = 1 << 47;
54pub const FUNCTION_TEXT: &str = "<fn>";
57pub const NULL: Data = Data::tagged(NAN_NULL);
58pub const FALSE: Data = Data::tagged(NAN_BOOL);
59pub const TRUE: Data = Data::tagged(NAN_BOOL | 1);
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[repr(C)]
85pub struct Data {
86 boxed: u64,
89 int: i64,
92}
93
94#[derive(Default, Clone, Copy)]
95pub struct DataHash(u64);
96
97impl Hasher for DataHash {
98 fn finish(&self) -> u64 {
99 self.0
100 }
101 fn write(&mut self, _bytes: &[u8]) {
102 unsafe { unreachable_unchecked() }
103 }
104 fn write_u64(&mut self, i: u64) {
105 self.0 = i;
106 }
107}
108
109#[must_use]
114pub fn text_hash(text: &str) -> u64 {
115 text.bytes().fold(0xcbf2_9ce4_8422_2325, |hash, byte| {
116 (hash ^ u64::from(byte)).wrapping_mul(0x0000_0100_0000_01b3)
117 })
118}
119
120impl Hash for Data {
121 #[inline(always)]
128 fn hash<H: Hasher>(&self, state: &mut H) {
129 state.write_u64(self.boxed ^ (self.int as u64));
130 }
131}
132
133pub trait PoolString {
134 fn pool_as_str(&self) -> &str;
135 fn move_to_slot(self, slot: &mut String);
136 fn push_to_pool(self, str_pool: &mut StringPool);
137 fn str_len(&self) -> usize;
138}
139
140impl PoolString for String {
141 #[inline(always)]
142 fn pool_as_str(&self) -> &str {
143 self.as_str()
144 }
145 #[inline(always)]
146 fn move_to_slot(self, slot: &mut String) {
147 *slot = self;
148 }
149 #[inline(always)]
150 fn push_to_pool(self, pool: &mut StringPool) {
151 pool.push(self);
152 }
153 #[inline(always)]
154 fn str_len(&self) -> usize {
155 self.len()
156 }
157}
158impl PoolString for &str {
159 #[inline(always)]
160 fn pool_as_str(&self) -> &str {
161 self
162 }
163 #[inline(always)]
164 fn move_to_slot(self, slot: &mut String) {
165 self.clone_into(slot);
166 }
167 #[inline(always)]
168 fn push_to_pool(self, string_pool: &mut StringPool) {
169 string_pool.push(self.to_owned());
170 }
171 #[inline(always)]
172 fn str_len(&self) -> usize {
173 self.len()
174 }
175}
176
177impl Data {
178 #[inline(always)]
182 const fn tagged(boxed: u64) -> Self {
183 Self { boxed, int: 0 }
184 }
185 #[must_use]
189 #[inline(always)]
190 pub const fn into_words(self) -> (u64, i64) {
191 (self.boxed, self.int)
192 }
193 #[inline(always)]
206 pub unsafe fn load(src: *const Self) -> Self {
207 let words = src.cast::<u64>();
208 unsafe {
209 Self {
210 boxed: words.read_volatile(),
211 int: words.add(1).read_volatile() as i64,
212 }
213 }
214 }
215 #[inline(always)]
218 pub const fn is_false(self) -> bool {
219 self.boxed == NAN_BOOL
220 }
221 #[inline(always)]
223 pub const fn is_true(self) -> bool {
224 self.boxed == NAN_BOOL | 1
225 }
226 #[must_use]
228 #[inline(always)]
229 pub const fn from_words(boxed: u64, int: i64) -> Self {
230 Self { boxed, int }
231 }
232 #[inline(always)]
233 pub const fn tag(self) -> u64 {
234 self.boxed & !0xFFFF_FFFF }
236 #[inline(always)]
237 pub const fn is_null(self) -> bool {
238 self.boxed == NAN_NULL
239 }
240 #[inline(always)]
241 pub const fn bool(b: bool) -> Self {
242 Self::tagged(NAN_BOOL | b as u64)
243 }
244 #[inline(always)]
245 pub fn as_bool(self) -> bool {
246 debug_assert!(self.is_bool());
247 (self.boxed & 1) != 0
248 }
249 #[inline(always)]
250 pub const fn is_bool(self) -> bool {
251 (self.boxed & !PAYLOAD_MASK) == NAN_BOOL
252 }
253 #[inline(always)]
254 pub const fn float(n: f64) -> Self {
255 let bits = n.to_bits();
256 if (bits & NAN_BASE) == NAN_BASE {
260 Self::tagged(CANONICAL_NAN)
261 } else {
262 Self::tagged(bits)
263 }
264 }
265 #[inline(always)]
266 pub const fn as_float(self) -> f64 {
267 debug_assert!(self.is_float());
268 f64::from_bits(self.boxed)
269 }
270 #[inline(always)]
271 pub const fn is_float(self) -> bool {
272 (self.boxed & NAN_BASE) != NAN_BASE
273 }
274 #[inline(always)]
275 pub const fn int(n: i64) -> Self {
278 Self {
279 boxed: NAN_INT,
280 int: n,
281 }
282 }
283 #[inline(always)]
284 pub const fn as_int(self) -> i64 {
285 debug_assert!(self.is_int());
286 self.int
287 }
288 #[inline(always)]
289 pub const fn is_int(self) -> bool {
290 (self.boxed & !PAYLOAD_MASK) == NAN_INT
291 }
292 #[inline(always)]
293 pub const fn array(id: u32) -> Self {
294 Self::tagged(NAN_ARRAY | id as u64)
295 }
296 #[inline(always)]
297 pub const fn as_array(self) -> usize {
298 debug_assert!(self.is_array() || self.is_struct() || self.is_enum());
301 (self.boxed & 0xFFFF_FFFF) as usize
302 }
303 #[inline(always)]
304 pub const fn is_array(self) -> bool {
305 (self.boxed & !PAYLOAD_MASK) == NAN_ARRAY
306 }
307 #[inline(always)]
311 pub const fn function(id: u32) -> Self {
312 Self::tagged(NAN_ARRAY | NAN_FN_MARK | id as u64)
313 }
314 #[inline(always)]
318 pub const fn is_function(self) -> bool {
319 (self.boxed & !PAYLOAD_MASK) == NAN_ARRAY && (self.boxed & NAN_FN_MARK) != 0
320 }
321 #[inline(always)]
325 pub fn small_str(s: &str) -> Self {
326 debug_assert!(s.len() <= 6);
327 let mut payload: u64 = 0;
328 for (i, byte) in s.as_bytes().iter().enumerate() {
330 payload |= (*byte as u64) << (i * 8);
331 }
332 Self::tagged(NAN_STRING_SMALL | (payload & PAYLOAD_MASK))
333 }
334 #[inline(always)]
335 pub const fn large_str_id(id: u64) -> Self {
336 Self::tagged(NAN_STRING_LARGE | id)
337 }
338 #[inline(always)]
345 #[must_use]
346 pub fn as_map_key(self, string_pool: &StringPool) -> Self {
347 if self.is_large_str() {
348 Self {
349 boxed: self.boxed,
350 int: (text_hash(self.as_str(string_pool)) ^ self.boxed) as i64,
351 }
352 } else {
353 self
354 }
355 }
356 #[inline(always)]
358 pub fn p_str(s: &str, string_pool: &mut StringPool) -> Self {
359 Self::interned(s, string_pool).unwrap_or_else(|| {
360 let string_pool_id = string_pool.len() as u64;
361 string_pool.push(s.to_owned());
362 Self::tagged(NAN_STRING_LARGE | string_pool_id)
363 })
364 }
365 pub fn interned(s: &str, string_pool: &mut StringPool) -> Option<Self> {
368 if s.len() <= 6 {
369 return Some(Self::small_str(s));
370 }
371 let id = string_pool.iter().position(|existing| existing == s)?;
372 string_pool.shade(id);
375 Some(Self::tagged(NAN_STRING_LARGE | id as u64))
376 }
377 #[inline(always)]
382 pub fn string<S: PoolString>(
383 s: S,
384 array_pool: &mut ObjectPool,
385 map_pool: &mut MapPool,
386 string_pool: &mut StringPool,
387 registers: &RegisterFile,
388 recursion_stack: &RegisterFile,
389 gc: &mut GcState,
390 ) -> Self {
391 if s.str_len() <= 6 {
392 Self::small_str(s.pool_as_str())
393 } else {
394 let id = alloc_string(
395 s,
396 array_pool,
397 map_pool,
398 string_pool,
399 registers,
400 recursion_stack,
401 gc,
402 );
403 Self::tagged(NAN_STRING_LARGE | id)
404 }
405 }
406 #[inline(always)]
407 pub fn as_str(&self, string_pool: &StringPool) -> &str {
408 debug_assert!(self.is_string());
409 if (self.boxed & !PAYLOAD_MASK) == NAN_STRING_SMALL {
410 let len = self.small_str_len();
411 let ptr = self as *const Self as *const u8;
412 unsafe {
413 let slice = std::slice::from_raw_parts(ptr, len);
414 std::str::from_utf8_unchecked(slice)
415 }
416 } else {
417 let payload = (self.boxed & PAYLOAD_MASK) as usize;
418 unsafe { &*(string_pool[payload].as_str() as *const str) }
419 }
420 }
421 #[inline(always)]
425 pub fn str_is_ascii(&self, string_pool: &StringPool) -> bool {
426 if self.is_large_str() {
427 string_pool.is_ascii(self.get_str_pool_id())
428 } else {
429 self.boxed & SMALL_STR_HIGH_BITS == 0
430 }
431 }
432 #[inline(always)]
436 pub fn str_char_len(&self, string_pool: &StringPool) -> usize {
437 if self.is_large_str() {
438 string_pool.char_len(self.get_str_pool_id())
439 } else if self.boxed & SMALL_STR_HIGH_BITS == 0 {
440 self.small_str_len()
441 } else {
442 cold_path();
443 count_chars(self.as_str(string_pool))
444 }
445 }
446 #[inline(always)]
450 pub fn str_byte_offset(&self, char_idx: usize, string_pool: &StringPool) -> usize {
451 if self.str_is_ascii(string_pool) {
452 char_idx
453 } else if self.is_large_str() {
454 string_pool.byte_offset(self.get_str_pool_id(), char_idx)
455 } else {
456 char_byte_offset(self.as_str(string_pool), char_idx)
457 }
458 }
459 #[inline(always)]
461 const fn small_str_len(&self) -> usize {
462 let payload = self.boxed & PAYLOAD_MASK;
463 ((64 - payload.leading_zeros()) as usize + 7) >> 3
464 }
465 #[inline(always)]
466 pub const fn is_string(self) -> bool {
467 (self.boxed & !PAYLOAD_MASK).wrapping_sub(NAN_STRING_SMALL) <= const { 1u64 << 48 }
469 }
470 #[inline(always)]
472 pub const fn inc_int(&mut self) {
473 debug_assert!(self.is_int());
474 self.int = self.int.wrapping_add(1);
475 }
476 #[inline(always)]
478 pub const fn dec_int(&mut self) {
479 debug_assert!(self.is_int());
480 self.int = self.int.wrapping_sub(1);
481 }
482 #[inline(always)]
484 pub const fn inc_into(&mut self, src: Self) {
485 debug_assert!(src.is_int());
486 self.boxed = NAN_INT;
487 self.int = src.int.wrapping_add(1);
488 }
489 #[inline(always)]
491 pub const fn dec_into(&mut self, src: Self) {
492 debug_assert!(src.is_int());
493 self.boxed = NAN_INT;
494 self.int = src.int.wrapping_sub(1);
495 }
496 #[inline(always)]
497 pub const fn is_large_str(self) -> bool {
498 (self.boxed & !PAYLOAD_MASK) == NAN_STRING_LARGE
499 }
500 #[inline(always)]
501 pub const fn get_str_pool_id(self) -> usize {
502 debug_assert!(self.is_large_str());
503 (self.boxed & PAYLOAD_MASK) as usize
504 }
505 #[inline(always)]
506 pub const fn struct_instance(type_id: u16, id: u32) -> Self {
507 Self::tagged(NAN_STRUCT | ((type_id as u64) << 32) | id as u64)
508 }
509 #[inline(always)]
510 pub const fn as_struct(self) -> usize {
511 debug_assert!(self.is_struct() || self.is_enum());
515 (self.boxed & 0xFFFF_FFFF) as usize
516 }
517 #[inline(always)]
518 pub const fn enum_instance(type_id: u16, id: u32) -> Self {
519 Self::tagged(NAN_ENUM | ((type_id as u64) << 32) | id as u64)
520 }
521 #[inline(always)]
522 pub const fn as_enum(self) -> usize {
523 debug_assert!(self.is_enum());
524 (self.boxed & 0xFFFF_FFFF) as usize
525 }
526 #[inline(always)]
527 pub const fn enum_type_id(self) -> u16 {
528 debug_assert!(self.is_enum());
529 ((self.boxed >> 32) & 0xFFFF) as u16
530 }
531 #[inline(always)]
532 pub const fn is_enum(self) -> bool {
533 (self.boxed & !PAYLOAD_MASK) == NAN_ENUM
534 }
535 #[inline(always)]
536 pub const fn struct_type_id(self) -> u16 {
537 ((self.boxed >> 32) & 0xFFFF) as u16
538 }
539 #[inline(always)]
540 pub const fn is_struct(self) -> bool {
541 (self.boxed & !PAYLOAD_MASK) == NAN_STRUCT && (self.boxed & (1 << 47)) == 0
542 }
543 #[inline(always)]
544 pub const fn map(id: u32) -> Self {
545 Self::tagged(NAN_MAP | id as u64)
546 }
547 #[inline(always)]
548 pub const fn as_map(self) -> usize {
549 debug_assert!(self.is_map());
550 (self.boxed & 0xFFFF_FFFF) as usize
551 }
552 #[inline(always)]
553 pub const fn is_map(self) -> bool {
554 (self.boxed & !PAYLOAD_MASK) == NAN_STRUCT && (self.boxed & (1 << 47)) != 0
555 }
556 #[must_use]
559 pub const fn type_name(self) -> &'static str {
560 if self.is_int() {
561 "int"
562 } else if self.is_float() {
563 "float"
564 } else if self.is_bool() {
565 "bool"
566 } else if self.is_null() {
567 "null"
568 } else if self.is_string() {
569 "string"
570 } else if self.is_array() {
571 "list"
572 } else if self.is_map() {
573 "map"
574 } else if self.is_struct() {
575 "struct"
576 } else if self.is_enum() {
577 "enum"
578 } else {
579 "value"
580 }
581 }
582
583 #[allow(clippy::too_many_arguments)]
584 pub fn format(
585 self,
586 obj_pool: &ObjectPool,
587 string_pool: &StringPool,
588 map_pool: &MapPool,
589 structs: &[Struct],
590 enums: &[EnumType],
591 show_str: bool,
592 ) -> SmolStr {
593 if self.is_float() {
594 format_float(self.as_float())
595 } else if self.is_int() {
596 self.as_int().to_smolstr()
597 } else if self.is_bool() {
598 self.as_bool().to_smolstr()
599 } else if self.is_string() {
600 if show_str {
601 self.as_str(string_pool).to_smolstr()
602 } else {
603 format_args!("\"{}\"", self.as_str(string_pool)).to_smolstr()
604 }
605 } else if self.is_function() {
606 SmolStr::new_static(FUNCTION_TEXT)
607 } else if self.is_array() {
608 format_args!(
609 "[{}]",
610 obj_pool[self.as_array()]
611 .iter()
612 .map(|x| x.format(obj_pool, string_pool, map_pool, structs, enums, false))
613 .collect::<Vec<SmolStr>>()
614 .join(",")
615 )
616 .to_smolstr()
617 } else if self.is_null() {
618 SmolStr::new_static("null")
619 } else if self.is_struct() {
620 let declared = structs.get(self.struct_type_id() as usize);
621 let s_name = declared.map_or("struct", |s| s.name.as_str());
622 format_args!(
623 "{} {{{}}}",
624 s_name,
625 obj_pool[self.as_struct()]
626 .iter()
627 .enumerate()
628 .map(|(idx, x)| {
629 let value =
630 x.format(obj_pool, string_pool, map_pool, structs, enums, false);
631 match declared.and_then(|s| s.fields.get(idx)) {
636 Some((field, ..)) => format_args!("{field}:{value}").to_smolstr(),
637 None => value,
638 }
639 })
640 .collect::<Vec<SmolStr>>()
641 .join(",")
642 )
643 .to_smolstr()
644 } else if self.is_enum() {
645 let entry = &obj_pool[self.as_enum()];
646 let variant = enums
652 .get(self.enum_type_id() as usize)
653 .filter(|_| entry.first().is_some_and(|word| word.is_int()))
654 .and_then(|e| e.variants.get(entry[0].as_int() as usize));
655 let Some(variant) = variant else {
656 return SmolStr::new_static("enum");
657 };
658 if entry.len() <= 1 {
659 variant.name.clone()
660 } else {
661 format_args!(
662 "{}({})",
663 variant.name,
664 entry[1..]
665 .iter()
666 .map(|x| x.format(obj_pool, string_pool, map_pool, structs, enums, false))
667 .collect::<Vec<SmolStr>>()
668 .join(",")
669 )
670 .to_smolstr()
671 }
672 } else if self.is_map() {
673 let m = &map_pool[self.as_map()];
674 format_args!(
675 "{{{}}}",
676 m.iter()
677 .map(|(key, val)| {
678 format_args!(
679 "{}:{}",
680 key.format(obj_pool, string_pool, map_pool, structs, enums, false),
681 val.format(obj_pool, string_pool, map_pool, structs, enums, false),
682 )
683 .to_smolstr()
684 })
685 .collect::<Vec<SmolStr>>()
686 .join(",")
687 )
688 .to_smolstr()
689 } else {
690 unsafe { unreachable_unchecked() }
691 }
692 }
693}
694
695#[must_use]
702pub fn format_float(value: f64) -> SmolStr {
703 SmolStr::from(zmij::Buffer::new().format(value))
704}
705
706impl From<f64> for Data {
707 #[inline(always)]
708 fn from(value: f64) -> Self {
709 Self::float(value)
710 }
711}
712impl From<Data> for f64 {
713 #[inline(always)]
714 fn from(value: Data) -> Self {
715 value.as_float()
716 }
717}
718
719impl From<i64> for Data {
720 #[inline(always)]
721 fn from(value: i64) -> Self {
722 Self::int(value)
723 }
724}
725impl From<Data> for i64 {
726 #[inline(always)]
727 fn from(value: Data) -> Self {
728 value.as_int()
729 }
730}
731
732impl From<bool> for Data {
733 #[inline(always)]
734 fn from(value: bool) -> Self {
735 Self::tagged(NAN_BOOL | (value as u64))
736 }
737}
738impl From<Data> for bool {
739 #[inline(always)]
740 fn from(value: Data) -> Self {
741 value.as_bool()
742 }
743}
744
745#[cfg(test)]
746mod format_tests {
747 use super::Data;
748 use crate::rt::{DataType, EnumType, EnumVariant, Span, Struct};
749 use crate::vm::{MapPool, ObjectPool, Pool, StringPool};
750 use smol_strc::SmolStr;
751
752 fn one_struct() -> Vec<Struct> {
753 vec![Struct {
754 name: SmolStr::new_static("P"),
755 fields: Box::new([
756 (
757 SmolStr::new_static("x"),
758 DataType::Int,
759 Span { start: 0, end: 0 },
760 ),
761 (
762 SmolStr::new_static("y"),
763 DataType::Int,
764 Span { start: 0, end: 0 },
765 ),
766 ]),
767 id: 0,
768 name_span: Span { start: 0, end: 0 },
769 }]
770 }
771
772 #[test]
776 fn a_struct_formats_with_its_field_names() {
777 let structs = one_struct();
778 let obj_pool: ObjectPool = Pool(vec![
779 vec![Data::int(1), Data::int(2)],
780 vec![Data::struct_instance(0, 0)],
781 ]);
782 let rendered = |value: Data| {
783 value.format(
784 &obj_pool,
785 &StringPool::default(),
786 &Pool(Vec::new()),
787 &structs,
788 &[],
789 true,
790 )
791 };
792 assert_eq!(rendered(Data::struct_instance(0, 0)), "P {x:1,y:2}");
793 assert_eq!(rendered(Data::array(1)), "[P {x:1,y:2}]");
794 }
795
796 #[test]
800 fn a_struct_with_no_declaration_formats_as_its_slots() {
801 let obj_pool: ObjectPool = Pool(vec![vec![Data::int(1), Data::int(2)]]);
802 let rendered = Data::struct_instance(0, 0).format(
803 &obj_pool,
804 &StringPool::default(),
805 &Pool(Vec::new()),
806 &[],
807 &[],
808 true,
809 );
810 assert_eq!(rendered, "struct {1,2}");
811 }
812
813 #[test]
817 fn a_function_value_formats_as_the_function_marker() {
818 let obj_pool: ObjectPool = Pool(vec![
819 vec![Data::int(12)],
820 vec![Data::function(0)],
821 vec![Data::int(12)],
822 ]);
823 let rendered = |value: Data| {
824 value.format(
825 &obj_pool,
826 &StringPool::default(),
827 &Pool(Vec::new()),
828 &[],
829 &[],
830 true,
831 )
832 };
833 assert_eq!(rendered(Data::function(0)), "<fn>");
834 assert_eq!(rendered(Data::array(1)), "[<fn>]");
835 assert_eq!(rendered(Data::array(2)), "[12]");
837 }
838
839 #[test]
842 fn a_function_value_is_an_array_underneath() {
843 let value = Data::function(7);
844 assert!(value.is_array());
845 assert!(value.is_function());
846 assert_eq!(value.as_array(), 7);
847 assert!(!Data::array(7).is_function());
848 }
849
850 fn one_enum() -> Vec<EnumType> {
851 vec![EnumType {
852 name: SmolStr::new_static("Value"),
853 variants: Box::new([EnumVariant {
854 name: SmolStr::new_static("Int"),
855 payload: Box::new([DataType::Int]),
856 name_span: Span { start: 0, end: 0 },
857 }]),
858 id: 0,
859 name_span: Span { start: 0, end: 0 },
860 }]
861 }
862
863 #[test]
866 fn an_enum_inside_an_array_formats_as_its_variant() {
867 let enums = one_enum();
868 let obj_pool: ObjectPool = Pool(vec![
869 vec![Data::int(0), Data::int(3)],
870 vec![Data::enum_instance(0, 0)],
871 ]);
872 let rendered = Data::array(1).format(
873 &obj_pool,
874 &StringPool::default(),
875 &Pool(Vec::new()),
876 &[],
877 &enums,
878 true,
879 );
880 assert_eq!(rendered, "[Int(3)]");
881 }
882
883 #[test]
887 fn an_entry_with_no_readable_tag_formats_as_the_bare_type() {
888 let enums = one_enum();
889 let string_pool = StringPool::default();
890 let map_pool: MapPool = Pool(Vec::new());
891 for entry in [
892 vec![Data::enum_instance(0, 0)],
894 vec![Data::int(42)],
896 vec![Data::int(-1)],
898 Vec::new(),
900 ] {
901 let obj_pool: ObjectPool = Pool(vec![entry]);
902 let rendered = Data::enum_instance(0, 0).format(
903 &obj_pool,
904 &string_pool,
905 &map_pool,
906 &[],
907 &enums,
908 true,
909 );
910 assert_eq!(rendered, "enum");
911 }
912 }
913}
914
915#[cfg(test)]
916mod float_boxing_tests {
917 use super::Data;
918
919 #[test]
924 fn negative_quiet_nan_stays_a_float() {
925 let hardware_nan = f64::from_bits(0xFFF8_0000_0000_0000);
926 assert!(hardware_nan.is_nan());
927
928 let boxed = Data::float(hardware_nan);
929 assert!(boxed.is_float());
930 assert!(!boxed.is_enum());
931 assert!(!boxed.is_struct());
932 assert!(!boxed.is_map());
933 assert!(boxed.as_float().is_nan());
934 }
935
936 #[test]
937 fn every_nan_boxes_to_one_pattern() {
938 let signed = Data::float(f64::from_bits(0xFFF8_0000_0000_0000));
939 let unsigned = Data::float(f64::NAN);
940 let with_payload = Data::float(f64::from_bits(0xFFF8_0000_DEAD_BEEF));
941 assert_eq!(signed, unsigned);
942 assert_eq!(signed, with_payload);
943 }
944
945 #[test]
946 fn infinities_and_ordinary_floats_round_trip() {
947 for value in [
948 0.0,
949 -0.0,
950 1.5,
951 -1.5,
952 f64::MIN,
953 f64::MAX,
954 f64::INFINITY,
955 f64::NEG_INFINITY,
956 ] {
957 let boxed = Data::float(value);
958 assert!(boxed.is_float(), "{value} must box as a float");
959 assert_eq!(boxed.as_float().to_bits(), value.to_bits());
960 }
961 }
962
963 #[test]
966 fn tagged_values_are_not_floats() {
967 assert!(!Data::int(7).is_float());
968 assert!(!Data::bool(true).is_float());
969 assert!(!Data::array(0).is_float());
970 assert!(!Data::map(0).is_float());
971 assert!(!Data::enum_instance(0, 0).is_float());
972 assert!(!super::NULL.is_float());
973 }
974}