1use std::collections::BTreeMap;
36use std::fmt;
37use std::rc::Rc;
38
39use crate::error::VMError;
40use crate::intern::Symbol;
41use crate::value::{HigherOrderBuiltin, ThunkState, VMBuiltin, VMClosure, VMThunk, VMValue};
42
43const QNAN: u64 = 0x7FF8_0000_0000_0000;
45const TAG_SHIFT: u64 = 48;
47const PAYLOAD_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
49
50const TAG_NULL: u64 = QNAN | (0x0 << TAG_SHIFT);
52const TAG_FALSE: u64 = QNAN | (0x1 << TAG_SHIFT);
53const TAG_TRUE: u64 = QNAN | (0x2 << TAG_SHIFT);
54const TAG_INT: u64 = QNAN | (0x3 << TAG_SHIFT);
55const TAG_PTR: u64 = QNAN | (0x4 << TAG_SHIFT);
56
57const TAG_MASK: u64 = QNAN | (0xF << TAG_SHIFT);
59
60pub struct NanBox(u64);
65
66pub enum HeapObject {
77 String(String),
78 Path(String),
79 List(Vec<NanBox>),
80 Attrs(BTreeMap<Symbol, NanBox>),
81 Closure(VMClosure),
82 Builtin(VMBuiltin),
83 Thunk(VMThunk),
84 HigherOrderBuiltin(HigherOrderBuiltin),
85 BigInt(i64),
86}
87
88impl NanBox {
89 #[inline(always)]
93 #[must_use]
94 pub const fn null() -> Self {
95 Self(TAG_NULL)
96 }
97
98 #[inline(always)]
100 #[must_use]
101 pub const fn bool(b: bool) -> Self {
102 if b {
103 Self(TAG_TRUE)
104 } else {
105 Self(TAG_FALSE)
106 }
107 }
108
109 #[inline(always)]
121 #[must_use]
122 pub fn int(n: i64) -> Self {
123 let fits = (n << 16) >> 16 == n;
124 if fits {
125 let payload = (n as u64) & PAYLOAD_MASK;
126 Self(TAG_INT | payload)
127 } else {
128 Self::heap(HeapObject::BigInt(n))
129 }
130 }
131
132 #[inline(always)]
134 #[must_use]
135 pub fn float(f: f64) -> Self {
136 Self(f.to_bits())
137 }
138
139 #[must_use]
141 pub fn heap(obj: HeapObject) -> Self {
142 let boxed = Rc::new(obj);
143 let ptr = Rc::into_raw(boxed) as u64;
144 debug_assert!(
145 ptr & !PAYLOAD_MASK == 0,
146 "pointer exceeds 48 bits"
147 );
148 Self(TAG_PTR | (ptr & PAYLOAD_MASK))
149 }
150
151 #[must_use]
153 pub fn string(s: String) -> Self {
154 Self::heap(HeapObject::String(s))
155 }
156
157 #[must_use]
159 pub fn path(s: String) -> Self {
160 Self::heap(HeapObject::Path(s))
161 }
162
163 #[must_use]
165 pub fn list(items: Vec<NanBox>) -> Self {
166 Self::heap(HeapObject::List(items))
167 }
168
169 #[must_use]
171 pub fn attrs(map: BTreeMap<Symbol, NanBox>) -> Self {
172 Self::heap(HeapObject::Attrs(map))
173 }
174
175 #[must_use]
177 pub fn closure(c: VMClosure) -> Self {
178 Self::heap(HeapObject::Closure(c))
179 }
180
181 #[must_use]
183 pub fn builtin(b: VMBuiltin) -> Self {
184 Self::heap(HeapObject::Builtin(b))
185 }
186
187 #[must_use]
189 pub fn thunk(t: VMThunk) -> Self {
190 Self::heap(HeapObject::Thunk(t))
191 }
192
193 #[must_use]
195 pub fn higher_order_builtin(h: HigherOrderBuiltin) -> Self {
196 Self::heap(HeapObject::HigherOrderBuiltin(h))
197 }
198
199 #[inline(always)]
203 #[must_use]
204 pub fn is_float(&self) -> bool {
205 (self.0 & TAG_MASK) != TAG_INT
209 && (self.0 & TAG_MASK) != TAG_NULL
210 && (self.0 & TAG_MASK) != TAG_FALSE
211 && (self.0 & TAG_MASK) != TAG_TRUE
212 && (self.0 & TAG_MASK) != TAG_PTR
213 }
214
215 #[inline(always)]
216 #[must_use]
217 pub fn is_null(&self) -> bool {
218 self.0 == TAG_NULL
219 }
220
221 #[inline(always)]
222 #[must_use]
223 pub fn is_bool(&self) -> bool {
224 self.0 == TAG_TRUE || self.0 == TAG_FALSE
225 }
226
227 #[inline(always)]
228 #[must_use]
229 pub fn is_int(&self) -> bool {
230 if (self.0 & TAG_MASK) == TAG_INT {
231 return true;
232 }
233 matches!(self.as_heap(), Some(HeapObject::BigInt(_)))
234 }
235
236 #[inline(always)]
237 #[must_use]
238 pub fn is_ptr(&self) -> bool {
239 (self.0 & TAG_MASK) == TAG_PTR
240 }
241
242 #[inline(always)]
246 #[must_use]
247 pub fn as_bool(&self) -> Option<bool> {
248 if self.0 == TAG_TRUE {
249 Some(true)
250 } else if self.0 == TAG_FALSE {
251 Some(false)
252 } else {
253 None
254 }
255 }
256
257 #[inline(always)]
262 #[must_use]
263 pub fn as_int(&self) -> Option<i64> {
264 if (self.0 & TAG_MASK) == TAG_INT {
265 let raw = (self.0 & PAYLOAD_MASK) as i64;
267 let extended = (raw << 16) >> 16;
268 return Some(extended);
269 }
270 if let Some(HeapObject::BigInt(n)) = self.as_heap() {
271 return Some(*n);
272 }
273 None
274 }
275
276 #[inline(always)]
278 #[must_use]
279 pub fn as_float(&self) -> Option<f64> {
280 if self.is_float() {
281 Some(f64::from_bits(self.0))
282 } else {
283 None
284 }
285 }
286
287 #[must_use]
289 pub fn as_heap(&self) -> Option<&HeapObject> {
290 if (self.0 & TAG_MASK) == TAG_PTR {
291 let ptr = (self.0 & PAYLOAD_MASK) as *const HeapObject;
292 Some(unsafe { &*ptr })
295 } else {
296 None
297 }
298 }
299
300 #[must_use]
304 pub fn type_name(&self) -> &'static str {
305 if self.is_null() {
306 "null"
307 } else if self.is_bool() {
308 "bool"
309 } else if self.is_int() {
310 "int"
311 } else if self.is_float() {
312 "float"
313 } else if let Some(obj) = self.as_heap() {
314 match obj {
315 HeapObject::String(_) => "string",
316 HeapObject::Path(_) => "path",
317 HeapObject::List(_) => "list",
318 HeapObject::Attrs(_) => "set",
319 HeapObject::Closure(_) | HeapObject::Builtin(_) | HeapObject::HigherOrderBuiltin(_) => "lambda",
320 HeapObject::Thunk(_) => "thunk",
321 HeapObject::BigInt(_) => "int",
322 }
323 } else {
324 "unknown"
325 }
326 }
327
328 pub fn is_truthy(&self) -> Result<bool, VMError> {
331 if self.0 == TAG_TRUE {
332 Ok(true)
333 } else if self.0 == TAG_FALSE {
334 Ok(false)
335 } else if self.is_null() {
336 Ok(false)
337 } else {
338 Err(VMError::TypeError {
341 expected: "bool",
342 got: self.type_name(),
343 context: "condition".to_string(),
344 })
345 }
346 }
347
348 #[inline(always)]
350 #[must_use]
351 pub fn is_string(&self) -> bool {
352 if let Some(HeapObject::String(_)) = self.as_heap() { true } else { false }
353 }
354
355 #[inline(always)]
357 #[must_use]
358 pub fn is_path(&self) -> bool {
359 if let Some(HeapObject::Path(_)) = self.as_heap() { true } else { false }
360 }
361
362 #[inline(always)]
364 #[must_use]
365 pub fn is_list(&self) -> bool {
366 if let Some(HeapObject::List(_)) = self.as_heap() { true } else { false }
367 }
368
369 #[inline(always)]
371 #[must_use]
372 pub fn is_attrs(&self) -> bool {
373 if let Some(HeapObject::Attrs(_)) = self.as_heap() { true } else { false }
374 }
375
376 #[inline(always)]
378 #[must_use]
379 pub fn is_closure(&self) -> bool {
380 if let Some(HeapObject::Closure(_)) = self.as_heap() { true } else { false }
381 }
382
383 #[inline(always)]
385 #[must_use]
386 pub fn is_builtin(&self) -> bool {
387 if let Some(HeapObject::Builtin(_)) = self.as_heap() { true } else { false }
388 }
389
390 #[inline(always)]
392 #[must_use]
393 pub fn is_thunk(&self) -> bool {
394 if let Some(HeapObject::Thunk(_)) = self.as_heap() { true } else { false }
395 }
396
397 #[inline(always)]
399 #[must_use]
400 pub fn is_higher_order_builtin(&self) -> bool {
401 matches!(self.as_heap(), Some(HeapObject::HigherOrderBuiltin(_)))
402 }
403
404 #[must_use]
406 pub fn as_string(&self) -> Option<&str> {
407 if let Some(HeapObject::String(s)) = self.as_heap() {
408 Some(s.as_str())
409 } else {
410 None
411 }
412 }
413
414 #[must_use]
416 pub fn as_path(&self) -> Option<&str> {
417 if let Some(HeapObject::Path(p)) = self.as_heap() {
418 Some(p.as_str())
419 } else {
420 None
421 }
422 }
423
424 #[must_use]
426 pub fn as_list(&self) -> Option<&[NanBox]> {
427 if let Some(HeapObject::List(items)) = self.as_heap() {
428 Some(items.as_slice())
429 } else {
430 None
431 }
432 }
433
434 #[must_use]
436 pub fn as_attrs(&self) -> Option<&BTreeMap<Symbol, NanBox>> {
437 if let Some(HeapObject::Attrs(map)) = self.as_heap() {
438 Some(map)
439 } else {
440 None
441 }
442 }
443
444 #[must_use]
446 pub fn as_closure(&self) -> Option<&VMClosure> {
447 if let Some(HeapObject::Closure(c)) = self.as_heap() {
448 Some(c)
449 } else {
450 None
451 }
452 }
453
454 #[must_use]
456 pub fn as_builtin(&self) -> Option<&VMBuiltin> {
457 if let Some(HeapObject::Builtin(b)) = self.as_heap() {
458 Some(b)
459 } else {
460 None
461 }
462 }
463
464 #[must_use]
466 pub fn as_thunk(&self) -> Option<&VMThunk> {
467 if let Some(HeapObject::Thunk(t)) = self.as_heap() {
468 Some(t)
469 } else {
470 None
471 }
472 }
473
474 #[must_use]
476 pub fn as_higher_order_builtin(&self) -> Option<&HigherOrderBuiltin> {
477 if let Some(HeapObject::HigherOrderBuiltin(h)) = self.as_heap() {
478 Some(h)
479 } else {
480 None
481 }
482 }
483
484 pub fn from_vmvalue(val: &VMValue) -> Self {
488 match val {
489 VMValue::Null => Self::null(),
490 VMValue::Bool(b) => Self::bool(*b),
491 VMValue::Int(n) => Self::int(*n),
492 VMValue::Float(f) => Self::float(*f),
493 VMValue::String(s) => Self::string(s.clone()),
494 VMValue::Path(p) => Self::path(p.clone()),
495 VMValue::List(items) => {
496 let boxed: Vec<NanBox> = items.iter().map(|v| Self::from_vmvalue(v)).collect();
497 Self::list(boxed)
498 }
499 VMValue::Attrs(attrs) => {
500 let boxed: BTreeMap<Symbol, NanBox> = attrs
501 .iter()
502 .map(|(k, v)| (*k, Self::from_vmvalue(v)))
503 .collect();
504 Self::attrs(boxed)
505 }
506 VMValue::Closure(c) => Self::closure(c.clone()),
507 VMValue::Builtin(b) => Self::builtin(b.clone()),
508 VMValue::Thunk(t) => Self::thunk(t.clone()),
509 VMValue::HigherOrderBuiltin(h) => Self::higher_order_builtin(h.clone()),
510 }
511 }
512
513 pub fn to_vmvalue(&self) -> VMValue {
515 if self.is_null() {
516 VMValue::Null
517 } else if let Some(b) = self.as_bool() {
518 VMValue::Bool(b)
519 } else if let Some(n) = self.as_int() {
520 VMValue::Int(n)
521 } else if let Some(f) = self.as_float() {
522 VMValue::Float(f)
523 } else if let Some(obj) = self.as_heap() {
524 match obj {
525 HeapObject::String(s) => VMValue::String(s.clone()),
526 HeapObject::Path(p) => VMValue::Path(p.clone()),
527 HeapObject::List(items) => {
528 VMValue::List(items.iter().map(NanBox::to_vmvalue).collect())
529 }
530 HeapObject::Attrs(attrs) => {
531 let map = attrs
532 .iter()
533 .map(|(k, v)| (*k, v.to_vmvalue()))
534 .collect();
535 VMValue::Attrs(map)
536 }
537 HeapObject::Closure(c) => VMValue::Closure(c.clone()),
538 HeapObject::Builtin(b) => VMValue::Builtin(b.clone()),
539 HeapObject::Thunk(t) => {
540 let state = t.state.take();
546 match state {
547 Some(ThunkState::Done(boxed)) => {
548 t.state.set(Some(ThunkState::Done(boxed.clone())));
549 *boxed
550 }
551 other => {
552 t.state.set(other);
553 VMValue::Thunk(t.clone())
554 }
555 }
556 }
557 HeapObject::HigherOrderBuiltin(h) => VMValue::HigherOrderBuiltin(h.clone()),
558 HeapObject::BigInt(n) => VMValue::Int(*n),
559 }
560 } else {
561 VMValue::Null
563 }
564 }
565}
566
567impl Clone for HeapObject {
568 fn clone(&self) -> Self {
569 match self {
570 HeapObject::String(s) => HeapObject::String(s.clone()),
571 HeapObject::Path(p) => HeapObject::Path(p.clone()),
572 HeapObject::List(items) => HeapObject::List(items.clone()),
573 HeapObject::Attrs(attrs) => HeapObject::Attrs(attrs.clone()),
574 HeapObject::Closure(c) => HeapObject::Closure(c.clone()),
575 HeapObject::Builtin(b) => HeapObject::Builtin(b.clone()),
576 HeapObject::Thunk(t) => HeapObject::Thunk(t.clone()),
577 HeapObject::HigherOrderBuiltin(h) => HeapObject::HigherOrderBuiltin(h.clone()),
578 HeapObject::BigInt(n) => HeapObject::BigInt(*n),
579 }
580 }
581}
582
583impl PartialEq for NanBox {
584 fn eq(&self, other: &Self) -> bool {
585 if self.0 == other.0 {
587 return true;
588 }
589
590 if self.is_float() && other.is_float() {
592 return self.as_float() == other.as_float();
593 }
594
595 if self.is_int() && other.is_float() {
597 if let (Some(i), Some(f)) = (self.as_int(), other.as_float()) {
598 return (i as f64) == f;
599 }
600 }
601 if self.is_float() && other.is_int() {
602 if let (Some(f), Some(i)) = (self.as_float(), other.as_int()) {
603 return f == (i as f64);
604 }
605 }
606
607 if self.is_ptr() && other.is_ptr() {
609 if let (Some(a), Some(b)) = (self.as_heap(), other.as_heap()) {
610 return heap_eq(a, b);
611 }
612 }
613
614 false
615 }
616}
617
618impl Eq for NanBox {}
619
620fn heap_eq(a: &HeapObject, b: &HeapObject) -> bool {
622 match (a, b) {
623 (HeapObject::String(a), HeapObject::String(b)) => a == b,
624 (HeapObject::Path(a), HeapObject::Path(b)) => a == b,
625 (HeapObject::List(a), HeapObject::List(b)) => a == b,
626 (HeapObject::Attrs(a), HeapObject::Attrs(b)) => a == b,
627 _ => false,
628 }
629}
630
631impl Drop for NanBox {
633 fn drop(&mut self) {
634 if (self.0 & TAG_MASK) == TAG_PTR {
635 let ptr = (self.0 & PAYLOAD_MASK) as *const HeapObject;
636 unsafe {
639 let _ = Rc::from_raw(ptr);
640 }
641 }
642 }
643}
644
645impl Clone for NanBox {
647 fn clone(&self) -> Self {
648 if (self.0 & TAG_MASK) == TAG_PTR {
649 let ptr = (self.0 & PAYLOAD_MASK) as *const HeapObject;
650 unsafe {
652 let rc = Rc::from_raw(ptr);
653 let cloned = Rc::clone(&rc);
654 let _ = Rc::into_raw(rc); let new_ptr = Rc::into_raw(cloned);
656 Self(TAG_PTR | (new_ptr as u64 & PAYLOAD_MASK))
657 }
658 } else {
659 Self(self.0)
660 }
661 }
662}
663
664impl fmt::Debug for NanBox {
665 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666 if self.is_null() {
667 write!(f, "NanBox(null)")
668 } else if let Some(b) = self.as_bool() {
669 write!(f, "NanBox({b})")
670 } else if let Some(n) = self.as_int() {
671 write!(f, "NanBox({n})")
672 } else if let Some(fl) = self.as_float() {
673 write!(f, "NanBox({fl})")
674 } else if let Some(obj) = self.as_heap() {
675 match obj {
676 HeapObject::String(s) => write!(f, "NanBox(\"{s}\")"),
677 HeapObject::Path(p) => write!(f, "NanBox(path:{p})"),
678 HeapObject::List(items) => write!(f, "NanBox(list[{}])", items.len()),
679 HeapObject::Attrs(map) => write!(f, "NanBox(attrs[{}])", map.len()),
680 HeapObject::Closure(c) => write!(f, "NanBox({c:?})"),
681 HeapObject::Builtin(b) => write!(f, "NanBox({b:?})"),
682 HeapObject::Thunk(_) => write!(f, "NanBox(<thunk>)"),
683 HeapObject::HigherOrderBuiltin(h) => write!(f, "NanBox({h:?})"),
684 HeapObject::BigInt(n) => write!(f, "NanBox(bigint:{n})"),
685 }
686 } else {
687 write!(f, "NanBox(0x{:016x})", self.0)
688 }
689 }
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695
696 #[test]
697 fn null_roundtrip() {
698 let v = NanBox::null();
699 assert!(v.is_null());
700 assert_eq!(v.to_vmvalue(), VMValue::Null);
701 }
702
703 #[test]
704 fn bool_roundtrip() {
705 let t = NanBox::bool(true);
706 let f = NanBox::bool(false);
707 assert_eq!(t.as_bool(), Some(true));
708 assert_eq!(f.as_bool(), Some(false));
709 assert_eq!(t.to_vmvalue(), VMValue::Bool(true));
710 assert_eq!(f.to_vmvalue(), VMValue::Bool(false));
711 }
712
713 #[test]
714 fn int_roundtrip() {
715 for n in [0i64, 1, -1, 42, -42, 1000000, -1000000, i32::MAX as i64, i32::MIN as i64] {
716 let v = NanBox::int(n);
717 assert!(v.is_int(), "should be int for {n}");
718 assert_eq!(v.as_int(), Some(n), "roundtrip failed for {n}");
719 }
720 }
721
722 #[test]
723 fn int_roundtrip_full_i64_range() {
724 let boundaries: &[i64] = &[
727 i64::MAX,
728 i64::MIN,
729 i64::MAX - 1,
730 i64::MIN + 1,
731 2_i64.pow(53), -(2_i64.pow(53)),
733 2_i64.pow(47), -(2_i64.pow(47)) - 1,
735 2_i64.pow(47) - 1, -(2_i64.pow(47)),
737 9_223_372_036_854_775_806, -9_223_372_036_854_775_807,
739 ];
740 for &n in boundaries {
741 let v = NanBox::int(n);
742 assert!(v.is_int(), "is_int false for boundary {n}");
743 assert_eq!(v.as_int(), Some(n), "round-trip failed for boundary {n}");
744 assert_eq!(v.type_name(), "int", "type_name wrong for boundary {n}");
745 match v.to_vmvalue() {
747 VMValue::Int(m) => assert_eq!(m, n, "VMValue round-trip lost precision at {n}"),
748 other => panic!("VMValue round-trip produced {other:?} for {n}"),
749 }
750 }
751 }
752
753 #[test]
754 fn int_overflow_does_not_silently_demote_to_float() {
755 let big = 9_223_372_036_854_775_806_i64;
759 let v = NanBox::int(big);
760 assert!(!v.is_float(), "BigInt must not be classified as float");
761 assert_eq!(v.as_int(), Some(big));
762 assert_eq!(v.as_float(), None, "BigInt must not pose as float");
763 }
764
765 #[test]
766 fn float_roundtrip() {
767 for f in [0.0f64, 1.0, -1.0, 3.14, f64::INFINITY, f64::NEG_INFINITY] {
768 let v = NanBox::float(f);
769 assert!(v.is_float(), "should be float for {f}");
770 assert_eq!(v.as_float(), Some(f), "roundtrip failed for {f}");
771 }
772 }
773
774 #[test]
775 fn string_roundtrip() {
776 let v = NanBox::string("hello".to_string());
777 assert!(v.is_ptr());
778 match v.to_vmvalue() {
779 VMValue::String(s) => assert_eq!(s, "hello"),
780 other => panic!("expected String, got {other:?}"),
781 }
782 }
783
784 #[test]
785 fn clone_heap_value() {
786 let v1 = NanBox::string("test".to_string());
787 let v2 = v1.clone();
788 match v2.to_vmvalue() {
789 VMValue::String(s) => assert_eq!(s, "test"),
790 other => panic!("expected String, got {other:?}"),
791 }
792 match v1.to_vmvalue() {
794 VMValue::String(s) => assert_eq!(s, "test"),
795 other => panic!("expected String, got {other:?}"),
796 }
797 }
798
799 #[test]
800 fn vmvalue_roundtrip_scalars() {
801 let cases = [
802 VMValue::Null,
803 VMValue::Bool(true),
804 VMValue::Bool(false),
805 VMValue::Int(42),
806 VMValue::Int(-1),
807 VMValue::Float(3.14),
808 ];
809 for val in &cases {
810 let boxed = NanBox::from_vmvalue(val);
811 let back = boxed.to_vmvalue();
812 assert_eq!(*val, back, "roundtrip failed for {val:?}");
813 }
814 }
815
816 #[test]
817 fn vmvalue_roundtrip_string() {
818 let val = VMValue::String("hello world".to_string());
819 let boxed = NanBox::from_vmvalue(&val);
820 let back = boxed.to_vmvalue();
821 assert_eq!(val, back);
822 }
823
824 #[test]
825 fn vmvalue_roundtrip_list() {
826 let val = VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)]);
827 let boxed = NanBox::from_vmvalue(&val);
828 let back = boxed.to_vmvalue();
829 assert_eq!(val, back);
830 }
831
832 #[test]
833 fn vmvalue_roundtrip_builtin() {
834 use crate::value::VMBuiltin;
835 use std::rc::Rc;
836 let b = VMBuiltin {
837 name: "test",
838 func: Rc::new(|_| Ok(VMValue::Null)),
839 arity: 1,
840 };
841 let val = VMValue::Builtin(b);
842 let boxed = NanBox::from_vmvalue(&val);
843 assert!(boxed.is_builtin());
844 match boxed.to_vmvalue() {
845 VMValue::Builtin(b) => assert_eq!(b.name, "test"),
846 other => panic!("expected Builtin, got {other:?}"),
847 }
848 }
849
850 #[test]
851 fn vmvalue_roundtrip_thunk() {
852 use crate::chunk::Chunk;
853 use std::rc::Rc;
854 let thunk = VMThunk::new(Rc::new(Chunk::new()), Vec::new());
855 let val = VMValue::Thunk(thunk);
856 let boxed = NanBox::from_vmvalue(&val);
857 assert!(boxed.is_thunk());
858 match boxed.to_vmvalue() {
859 VMValue::Thunk(_) => {} other => panic!("expected Thunk, got {other:?}"),
861 }
862 }
863
864 #[test]
865 fn type_name_all_types() {
866 assert_eq!(NanBox::null().type_name(), "null");
867 assert_eq!(NanBox::bool(true).type_name(), "bool");
868 assert_eq!(NanBox::int(42).type_name(), "int");
869 assert_eq!(NanBox::float(3.14).type_name(), "float");
870 assert_eq!(NanBox::string("hi".to_string()).type_name(), "string");
871 assert_eq!(NanBox::path("/tmp".to_string()).type_name(), "path");
872 assert_eq!(NanBox::list(vec![]).type_name(), "list");
873 assert_eq!(NanBox::attrs(BTreeMap::new()).type_name(), "set");
874 }
875
876 #[test]
877 fn nanbox_equality() {
878 assert_eq!(NanBox::null(), NanBox::null());
879 assert_eq!(NanBox::bool(true), NanBox::bool(true));
880 assert_ne!(NanBox::bool(true), NanBox::bool(false));
881 assert_eq!(NanBox::int(42), NanBox::int(42));
882 assert_ne!(NanBox::int(1), NanBox::int(2));
883 assert_eq!(NanBox::float(3.14), NanBox::float(3.14));
884 assert_eq!(NanBox::string("a".to_string()), NanBox::string("a".to_string()));
885 assert_ne!(NanBox::string("a".to_string()), NanBox::string("b".to_string()));
886 }
887
888 #[test]
889 fn nanbox_int_float_coercion() {
890 assert_eq!(NanBox::int(1), NanBox::float(1.0));
891 assert_eq!(NanBox::float(1.0), NanBox::int(1));
892 assert_ne!(NanBox::int(1), NanBox::float(1.5));
893 }
894
895 #[test]
896 fn is_truthy_bool() {
897 assert!(NanBox::bool(true).is_truthy().unwrap());
898 assert!(!NanBox::bool(false).is_truthy().unwrap());
899 }
900
901 #[test]
902 fn is_truthy_non_bool_errors() {
903 assert!(NanBox::int(1).is_truthy().is_err());
905 assert_eq!(NanBox::null().is_truthy().unwrap(), false);
907 }
908
909 #[test]
910 fn size_is_8_bytes() {
911 assert_eq!(std::mem::size_of::<NanBox>(), 8);
912 }
913}