Skip to main content

shape_value/
value_word.rs

1//! ValueWord: 8-byte NaN-boxed value representation for the VM stack.
2//!
3//! Uses IEEE 754 quiet NaN space to pack type tags and payloads into 8 bytes.
4//! Simple types (f64, i48, bool, None, Unit, Function) are stored inline.
5//! Complex types are heap-allocated as `Arc<HeapValue>` with the raw pointer in the payload.
6//!
7//! ## NaN-boxing scheme
8//!
9//! All tagged values use sign bit = 1 with a quiet NaN exponent, giving us 51 bits
10//! for tag + payload. Normal f64 values (including NaN, which is canonicalized to a
11//! positive quiet NaN) are stored directly and never collide with our tagged range.
12//!
13//! ```text
14//! Tagged: 0xFFF[C-F]_XXXX_XXXX_XXXX
15//!   Bit 63    = 1 (sign, marks as tagged)
16//!   Bits 62-52 = 0x7FF (NaN exponent)
17//!   Bit 51    = 1 (quiet NaN bit)
18//!   Bits 50-48 = tag (3 bits)
19//!   Bits 47-0  = payload (48 bits)
20//! ```
21//!
22//! | Tag   | Meaning                                      |
23//! |-------|----------------------------------------------|
24//! | 0b000 | Heap pointer to `Arc<HeapValue>` (48-bit ptr) |
25//! | 0b001 | i48 (48-bit signed integer, sign-extended)   |
26//! | 0b010 | Bool (payload bit 0)                         |
27//! | 0b011 | None                                         |
28//! | 0b100 | Unit                                         |
29//! | 0b101 | Function(u16) (payload = function_id)        |
30//! | 0b110 | ModuleFunction(u32) (payload = index)        |
31//! | 0b111 | Ref (absolute stack slot index, 48 bits)     |
32
33use crate::content::ContentNode;
34use crate::datatable::DataTable;
35use crate::enums::EnumValue;
36use crate::heap_value::{
37    ChannelData, DequeData, HashMapData, HeapValue, NativeScalar, NativeTypeLayout, NativeViewData,
38    PriorityQueueData, ProjectedRefData, RefProjection, SetData,
39};
40use crate::slot::ValueSlot;
41use crate::value::{FilterNode, HostCallable, PrintResult, VMArray, VTable};
42use chrono::{DateTime, FixedOffset, Utc};
43use shape_ast::ast::{DataDateTimeRef, DateTimeExpr, Duration, TimeReference, TypeAnnotation};
44use shape_ast::data::Timeframe;
45use std::collections::HashMap;
46use std::sync::Arc;
47
48const REF_TARGET_MODULE_FLAG: u64 = 1 << 47;
49const REF_TARGET_INDEX_MASK: u64 = REF_TARGET_MODULE_FLAG - 1;
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum RefTarget {
53    Stack(usize),
54    ModuleBinding(usize),
55    Projected(ProjectedRefData),
56}
57
58// --- Bit layout constants (imported from shared tags module) ---
59use crate::tags::{
60    CANONICAL_NAN, I48_MAX, I48_MIN, PAYLOAD_MASK, TAG_BOOL, TAG_FUNCTION, TAG_HEAP, TAG_INT,
61    TAG_MODULE_FN, TAG_NONE, TAG_REF, TAG_UNIT, get_payload, get_tag, is_tagged, make_tagged,
62    sign_extend_i48,
63};
64
65/// Single source of truth for NanTag variants and their inline type dispatch.
66///
67/// Generates:
68/// - `NanTag` enum
69/// - `nan_tag_type_name(tag)` — type name string for an inline (non-F64, non-Heap) tag
70/// - `nan_tag_is_truthy(tag, payload)` — truthiness for an inline (non-F64, non-Heap) tag
71///
72/// F64 is handled before the tag match (via `!is_tagged()`), and Heap delegates
73/// to HeapValue. Both are kept out of the inline dispatch.
74macro_rules! define_nan_tag_types {
75    () => {
76        /// Tag discriminator for ValueWord values.
77        ///
78        /// Returned by `ValueWord::tag()` for fast type dispatch without materializing HeapValue.
79        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
80        pub enum NanTag {
81            /// Inline f64 (not tagged — uses the full 64-bit IEEE 754 representation)
82            F64,
83            /// Inline i48 (48-bit signed integer, covers [-2^47, 2^47-1])
84            I48,
85            /// Inline bool
86            Bool,
87            /// None (null)
88            None,
89            /// Unit (void return)
90            Unit,
91            /// Function reference (inline u16 function ID)
92            Function,
93            /// Module function reference (inline u32 index)
94            ModuleFunction,
95            /// Heap-allocated HeapValue (String, Array, TypedObject, Closure, etc.)
96            Heap,
97            /// Reference to a stack slot (absolute index, used for pass-by-reference)
98            Ref,
99        }
100
101        /// Map a raw tag constant to its type name string.
102        ///
103        /// Covers inline tags only; F64 and Heap are handled separately by callers.
104        #[inline]
105        fn nan_tag_type_name(tag: u64) -> &'static str {
106            match tag {
107                TAG_INT => "int",
108                TAG_BOOL => "bool",
109                TAG_NONE => "option",
110                TAG_UNIT => "unit",
111                TAG_FUNCTION => "function",
112                TAG_MODULE_FN => "module_function",
113                TAG_REF => "reference",
114                _ => "unknown",
115            }
116        }
117
118        /// Evaluate truthiness for an inline tag value.
119        ///
120        /// Covers inline tags only; F64 and Heap are handled separately by callers.
121        #[inline]
122        fn nan_tag_is_truthy(tag: u64, payload: u64) -> bool {
123            match tag {
124                TAG_INT => sign_extend_i48(payload) != 0,
125                TAG_BOOL => payload != 0,
126                TAG_NONE => false,
127                TAG_UNIT => false,
128                TAG_FUNCTION | TAG_MODULE_FN | TAG_REF => true,
129                _ => true,
130            }
131        }
132    };
133}
134
135define_nan_tag_types!();
136
137// ===== ArrayView: unified read-only view over all array variants =====
138
139/// Unified read-only view over all array variants (generic, int, float, bool, width-specific).
140///
141/// Returned by `ValueWord::as_any_array()`. Use typed fast-path methods
142/// (`as_f64_slice()`, `as_i64_slice()`) for hot paths, or `to_generic()`
143/// / `get_nb()` for cold paths that need ValueWord values.
144#[derive(Debug)]
145pub enum ArrayView<'a> {
146    Generic(&'a Arc<Vec<ValueWord>>),
147    Int(&'a Arc<crate::typed_buffer::TypedBuffer<i64>>),
148    Float(&'a Arc<crate::typed_buffer::AlignedTypedBuffer>),
149    Bool(&'a Arc<crate::typed_buffer::TypedBuffer<u8>>),
150    I8(&'a Arc<crate::typed_buffer::TypedBuffer<i8>>),
151    I16(&'a Arc<crate::typed_buffer::TypedBuffer<i16>>),
152    I32(&'a Arc<crate::typed_buffer::TypedBuffer<i32>>),
153    U8(&'a Arc<crate::typed_buffer::TypedBuffer<u8>>),
154    U16(&'a Arc<crate::typed_buffer::TypedBuffer<u16>>),
155    U32(&'a Arc<crate::typed_buffer::TypedBuffer<u32>>),
156    U64(&'a Arc<crate::typed_buffer::TypedBuffer<u64>>),
157    F32(&'a Arc<crate::typed_buffer::TypedBuffer<f32>>),
158}
159
160impl<'a> ArrayView<'a> {
161    #[inline]
162    pub fn len(&self) -> usize {
163        match self {
164            ArrayView::Generic(a) => a.len(),
165            ArrayView::Int(a) => a.len(),
166            ArrayView::Float(a) => a.len(),
167            ArrayView::Bool(a) => a.len(),
168            ArrayView::I8(a) => a.len(),
169            ArrayView::I16(a) => a.len(),
170            ArrayView::I32(a) => a.len(),
171            ArrayView::U8(a) => a.len(),
172            ArrayView::U16(a) => a.len(),
173            ArrayView::U32(a) => a.len(),
174            ArrayView::U64(a) => a.len(),
175            ArrayView::F32(a) => a.len(),
176        }
177    }
178
179    #[inline]
180    pub fn is_empty(&self) -> bool {
181        self.len() == 0
182    }
183
184    /// Get element at index as ValueWord (boxes typed elements — use for cold paths).
185    #[inline]
186    pub fn get_nb(&self, idx: usize) -> Option<ValueWord> {
187        match self {
188            ArrayView::Generic(a) => a.get(idx).cloned(),
189            ArrayView::Int(a) => a.get(idx).map(|&i| ValueWord::from_i64(i)),
190            ArrayView::Float(a) => a.get(idx).map(|&f| ValueWord::from_f64(f)),
191            ArrayView::Bool(a) => a.get(idx).map(|&b| ValueWord::from_bool(b != 0)),
192            ArrayView::I8(a) => a.get(idx).map(|&v| ValueWord::from_i64(v as i64)),
193            ArrayView::I16(a) => a.get(idx).map(|&v| ValueWord::from_i64(v as i64)),
194            ArrayView::I32(a) => a.get(idx).map(|&v| ValueWord::from_i64(v as i64)),
195            ArrayView::U8(a) => a.get(idx).map(|&v| ValueWord::from_i64(v as i64)),
196            ArrayView::U16(a) => a.get(idx).map(|&v| ValueWord::from_i64(v as i64)),
197            ArrayView::U32(a) => a.get(idx).map(|&v| ValueWord::from_i64(v as i64)),
198            ArrayView::U64(a) => a.get(idx).map(|&v| {
199                if v <= i64::MAX as u64 {
200                    ValueWord::from_i64(v as i64)
201                } else {
202                    ValueWord::from_native_u64(v)
203                }
204            }),
205            ArrayView::F32(a) => a.get(idx).map(|&v| ValueWord::from_f64(v as f64)),
206        }
207    }
208
209    #[inline]
210    pub fn first_nb(&self) -> Option<ValueWord> {
211        self.get_nb(0)
212    }
213
214    #[inline]
215    pub fn last_nb(&self) -> Option<ValueWord> {
216        if self.is_empty() {
217            None
218        } else {
219            self.get_nb(self.len() - 1)
220        }
221    }
222
223    /// Materialize into a generic ValueWord array. Cheap Arc clone for Generic variant.
224    pub fn to_generic(&self) -> Arc<Vec<ValueWord>> {
225        match self {
226            ArrayView::Generic(a) => (*a).clone(),
227            ArrayView::Int(a) => Arc::new(a.iter().map(|&i| ValueWord::from_i64(i)).collect()),
228            ArrayView::Float(a) => Arc::new(
229                a.as_slice()
230                    .iter()
231                    .map(|&f| ValueWord::from_f64(f))
232                    .collect(),
233            ),
234            ArrayView::Bool(a) => {
235                Arc::new(a.iter().map(|&b| ValueWord::from_bool(b != 0)).collect())
236            }
237            ArrayView::I8(a) => {
238                Arc::new(a.iter().map(|&v| ValueWord::from_i64(v as i64)).collect())
239            }
240            ArrayView::I16(a) => {
241                Arc::new(a.iter().map(|&v| ValueWord::from_i64(v as i64)).collect())
242            }
243            ArrayView::I32(a) => {
244                Arc::new(a.iter().map(|&v| ValueWord::from_i64(v as i64)).collect())
245            }
246            ArrayView::U8(a) => {
247                Arc::new(a.iter().map(|&v| ValueWord::from_i64(v as i64)).collect())
248            }
249            ArrayView::U16(a) => {
250                Arc::new(a.iter().map(|&v| ValueWord::from_i64(v as i64)).collect())
251            }
252            ArrayView::U32(a) => {
253                Arc::new(a.iter().map(|&v| ValueWord::from_i64(v as i64)).collect())
254            }
255            ArrayView::U64(a) => Arc::new(
256                a.iter()
257                    .map(|&v| {
258                        if v <= i64::MAX as u64 {
259                            ValueWord::from_i64(v as i64)
260                        } else {
261                            ValueWord::from_native_u64(v)
262                        }
263                    })
264                    .collect(),
265            ),
266            ArrayView::F32(a) => {
267                Arc::new(a.iter().map(|&v| ValueWord::from_f64(v as f64)).collect())
268            }
269        }
270    }
271
272    #[inline]
273    pub fn as_i64_slice(&self) -> Option<&[i64]> {
274        if let ArrayView::Int(a) = self {
275            Some(a.as_slice())
276        } else {
277            None
278        }
279    }
280
281    #[inline]
282    pub fn as_f64_slice(&self) -> Option<&[f64]> {
283        if let ArrayView::Float(a) = self {
284            Some(a.as_slice())
285        } else {
286            None
287        }
288    }
289
290    #[inline]
291    pub fn as_bool_slice(&self) -> Option<&[u8]> {
292        if let ArrayView::Bool(a) = self {
293            Some(a.as_slice())
294        } else {
295            None
296        }
297    }
298
299    #[inline]
300    pub fn as_generic(&self) -> Option<&Arc<Vec<ValueWord>>> {
301        if let ArrayView::Generic(a) = self {
302            Some(a)
303        } else {
304            None
305        }
306    }
307
308    #[inline]
309    pub fn iter_i64(&self) -> Option<std::slice::Iter<'_, i64>> {
310        if let ArrayView::Int(a) = self {
311            Some(a.iter())
312        } else {
313            None
314        }
315    }
316
317    #[inline]
318    pub fn iter_f64(&self) -> Option<std::slice::Iter<'_, f64>> {
319        if let ArrayView::Float(a) = self {
320            Some(a.as_slice().iter())
321        } else {
322            None
323        }
324    }
325}
326
327/// Mutable view over all array variants. Uses Arc::make_mut for COW semantics.
328pub enum ArrayViewMut<'a> {
329    Generic(&'a mut Arc<Vec<ValueWord>>),
330    Int(&'a mut Arc<crate::typed_buffer::TypedBuffer<i64>>),
331    Float(&'a mut Arc<crate::typed_buffer::AlignedTypedBuffer>),
332    Bool(&'a mut Arc<crate::typed_buffer::TypedBuffer<u8>>),
333}
334
335impl ArrayViewMut<'_> {
336    #[inline]
337    pub fn len(&self) -> usize {
338        match self {
339            ArrayViewMut::Generic(a) => a.len(),
340            ArrayViewMut::Int(a) => a.len(),
341            ArrayViewMut::Float(a) => a.len(),
342            ArrayViewMut::Bool(a) => a.len(),
343        }
344    }
345
346    pub fn pop_vw(&mut self) -> Option<ValueWord> {
347        match self {
348            ArrayViewMut::Generic(a) => Arc::make_mut(a).pop(),
349            ArrayViewMut::Int(a) => Arc::make_mut(a).data.pop().map(ValueWord::from_i64),
350            ArrayViewMut::Float(a) => Arc::make_mut(a).pop().map(ValueWord::from_f64),
351            ArrayViewMut::Bool(a) => Arc::make_mut(a)
352                .data
353                .pop()
354                .map(|b| ValueWord::from_bool(b != 0)),
355        }
356    }
357
358    pub fn push_vw(&mut self, val: ValueWord) -> Result<(), crate::context::VMError> {
359        match self {
360            ArrayViewMut::Generic(a) => {
361                Arc::make_mut(a).push(val);
362                Ok(())
363            }
364            ArrayViewMut::Int(a) => {
365                if let Some(i) = val.as_i64() {
366                    Arc::make_mut(a).push(i);
367                    Ok(())
368                } else {
369                    Err(crate::context::VMError::TypeError {
370                        expected: "int",
371                        got: val.type_name(),
372                    })
373                }
374            }
375            ArrayViewMut::Float(a) => {
376                if let Some(f) = val.as_number_coerce() {
377                    Arc::make_mut(a).push(f);
378                    Ok(())
379                } else {
380                    Err(crate::context::VMError::TypeError {
381                        expected: "number",
382                        got: val.type_name(),
383                    })
384                }
385            }
386            ArrayViewMut::Bool(a) => {
387                if let Some(b) = val.as_bool() {
388                    Arc::make_mut(a).push(if b { 1 } else { 0 });
389                    Ok(())
390                } else {
391                    Err(crate::context::VMError::TypeError {
392                        expected: "bool",
393                        got: val.type_name(),
394                    })
395                }
396            }
397        }
398    }
399}
400
401/// An 8-byte value word for the VM stack (NaN-boxed encoding).
402///
403/// This is NOT Copy because heap-tagged values reference an `Arc<HeapValue>`.
404/// Clone is implemented manually to bump the Arc refcount (no allocation).
405/// Drop is implemented to decrement the Arc refcount.
406#[repr(transparent)]
407pub struct ValueWord(u64);
408
409impl ValueWord {
410    // ===== Constructors =====
411
412    /// Create a ValueWord from an f64 value.
413    ///
414    /// Normal f64 values are stored directly. NaN values are canonicalized to a single
415    /// canonical NaN to avoid collisions with our tagged range.
416    #[inline]
417    pub fn from_f64(v: f64) -> Self {
418        let bits = v.to_bits();
419        if v.is_nan() {
420            // Canonicalize all NaN variants to one known NaN outside our tagged range.
421            Self(CANONICAL_NAN)
422        } else if is_tagged(bits) {
423            // Extremely rare: a valid non-NaN f64 whose bits happen to fall in our tagged range.
424            // This cannot actually happen because all values in 0x7FFC..0x7FFF range are NaN
425            // (exponent all 1s with non-zero mantissa). So this branch is dead code, but kept
426            // for safety.
427            Self(CANONICAL_NAN)
428        } else {
429            Self(bits)
430        }
431    }
432
433    /// Create a ValueWord from an i64 value.
434    ///
435    /// Values in the range [-2^47, 2^47-1] are stored inline as i48.
436    /// Values outside that range are heap-boxed as `HeapValue::BigInt`.
437    #[inline]
438    pub fn from_i64(v: i64) -> Self {
439        if v >= I48_MIN && v <= I48_MAX {
440            // Fits in 48 bits. Store as sign-extended i48.
441            // Truncate to 48 bits by masking with PAYLOAD_MASK.
442            let payload = (v as u64) & PAYLOAD_MASK;
443            Self(make_tagged(TAG_INT, payload))
444        } else {
445            // Too large for inline. Heap-box as BigInt.
446            Self::heap_box(HeapValue::BigInt(v))
447        }
448    }
449
450    /// Create a ValueWord from a width-aware native scalar.
451    #[inline]
452    pub fn from_native_scalar(value: NativeScalar) -> Self {
453        Self::heap_box(HeapValue::NativeScalar(value))
454    }
455
456    #[inline]
457    pub fn from_native_i8(v: i8) -> Self {
458        Self::from_native_scalar(NativeScalar::I8(v))
459    }
460
461    #[inline]
462    pub fn from_native_u8(v: u8) -> Self {
463        Self::from_native_scalar(NativeScalar::U8(v))
464    }
465
466    #[inline]
467    pub fn from_native_i16(v: i16) -> Self {
468        Self::from_native_scalar(NativeScalar::I16(v))
469    }
470
471    #[inline]
472    pub fn from_native_u16(v: u16) -> Self {
473        Self::from_native_scalar(NativeScalar::U16(v))
474    }
475
476    #[inline]
477    pub fn from_native_i32(v: i32) -> Self {
478        Self::from_native_scalar(NativeScalar::I32(v))
479    }
480
481    #[inline]
482    pub fn from_native_u32(v: u32) -> Self {
483        Self::from_native_scalar(NativeScalar::U32(v))
484    }
485
486    #[inline]
487    pub fn from_native_u64(v: u64) -> Self {
488        Self::from_native_scalar(NativeScalar::U64(v))
489    }
490
491    #[inline]
492    pub fn from_native_isize(v: isize) -> Self {
493        Self::from_native_scalar(NativeScalar::Isize(v))
494    }
495
496    #[inline]
497    pub fn from_native_usize(v: usize) -> Self {
498        Self::from_native_scalar(NativeScalar::Usize(v))
499    }
500
501    #[inline]
502    pub fn from_native_ptr(v: usize) -> Self {
503        Self::from_native_scalar(NativeScalar::Ptr(v))
504    }
505
506    #[inline]
507    pub fn from_native_f32(v: f32) -> Self {
508        Self::from_native_scalar(NativeScalar::F32(v))
509    }
510
511    /// Create a pointer-backed C view.
512    #[inline]
513    pub fn from_c_view(ptr: usize, layout: Arc<NativeTypeLayout>) -> Self {
514        Self::heap_box(HeapValue::NativeView(Box::new(NativeViewData {
515            ptr,
516            layout,
517            mutable: false,
518        })))
519    }
520
521    /// Create a pointer-backed mutable C view.
522    #[inline]
523    pub fn from_c_mut(ptr: usize, layout: Arc<NativeTypeLayout>) -> Self {
524        Self::heap_box(HeapValue::NativeView(Box::new(NativeViewData {
525            ptr,
526            layout,
527            mutable: true,
528        })))
529    }
530
531    /// Create a ValueWord from a bool.
532    #[inline]
533    pub fn from_bool(v: bool) -> Self {
534        Self(make_tagged(TAG_BOOL, v as u64))
535    }
536
537    /// Create a ValueWord representing None.
538    #[inline]
539    pub fn none() -> Self {
540        Self(make_tagged(TAG_NONE, 0))
541    }
542
543    /// Create a ValueWord representing Unit.
544    #[inline]
545    pub fn unit() -> Self {
546        Self(make_tagged(TAG_UNIT, 0))
547    }
548
549    /// Construct a ValueWord from raw u64 bits, bumping the Arc refcount for
550    /// heap-tagged values. This is equivalent to `Clone::clone` but works from
551    /// raw bits (e.g. read via pointer arithmetic) instead of a `&ValueWord`.
552    /// Create a ValueWord from a function ID.
553    #[inline]
554    pub fn from_function(id: u16) -> Self {
555        Self(make_tagged(TAG_FUNCTION, id as u64))
556    }
557
558    /// Create a ValueWord from a module function index.
559    #[inline]
560    pub fn from_module_function(index: u32) -> Self {
561        Self(make_tagged(TAG_MODULE_FN, index as u64))
562    }
563
564    /// Create a ValueWord reference to an absolute stack slot.
565    #[inline]
566    pub fn from_ref(absolute_slot: usize) -> Self {
567        Self(make_tagged(TAG_REF, absolute_slot as u64))
568    }
569
570    /// Create a ValueWord reference to a module binding slot.
571    #[inline]
572    pub fn from_module_binding_ref(binding_idx: usize) -> Self {
573        Self(make_tagged(
574            TAG_REF,
575            REF_TARGET_MODULE_FLAG | (binding_idx as u64 & REF_TARGET_INDEX_MASK),
576        ))
577    }
578
579    /// Create a projected reference backed by heap metadata.
580    #[inline]
581    pub fn from_projected_ref(base: ValueWord, projection: RefProjection) -> Self {
582        Self::heap_box(HeapValue::ProjectedRef(Box::new(ProjectedRefData {
583            base,
584            projection,
585        })))
586    }
587
588    /// Heap-box a HeapValue directly.
589    ///
590    /// Under the `gc` feature, allocates via the GC heap (bump allocator, no refcount).
591    /// Without `gc`, uses `Arc<HeapValue>` with refcount bump on clone.
592    #[inline]
593    #[cfg(not(feature = "gc"))]
594    pub(crate) fn heap_box(v: HeapValue) -> Self {
595        let arc = Arc::new(v);
596        let ptr = Arc::into_raw(arc) as u64;
597        debug_assert!(
598            ptr & !PAYLOAD_MASK == 0,
599            "pointer exceeds 48 bits — platform not supported"
600        );
601        Self(make_tagged(TAG_HEAP, ptr & PAYLOAD_MASK))
602    }
603
604    /// Heap-box a HeapValue via the GC heap (bump allocation, no refcount).
605    ///
606    /// Clone is a bitwise copy (no refcount). Drop is a no-op (GC handles deallocation).
607    #[inline]
608    #[cfg(feature = "gc")]
609    pub(crate) fn heap_box(v: HeapValue) -> Self {
610        let heap = shape_gc::thread_gc_heap();
611        let ptr = heap.alloc(v) as u64;
612        debug_assert!(
613            ptr & !PAYLOAD_MASK == 0,
614            "GC pointer exceeds 48 bits — platform not supported"
615        );
616        Self(make_tagged(TAG_HEAP, ptr & PAYLOAD_MASK))
617    }
618
619    // ===== Typed constructors =====
620
621    /// Create a ValueWord from an Arc<String>.
622    #[inline]
623    pub fn from_string(s: Arc<String>) -> Self {
624        Self::heap_box(HeapValue::String(s))
625    }
626
627    /// Create a ValueWord from a char.
628    #[inline]
629    pub fn from_char(c: char) -> Self {
630        Self::heap_box(HeapValue::Char(c))
631    }
632
633    /// Extract a char if this is a HeapValue::Char.
634    #[inline]
635    pub fn as_char(&self) -> Option<char> {
636        if let Some(HeapValue::Char(c)) = self.as_heap_ref() {
637            Some(*c)
638        } else {
639            std::option::Option::None
640        }
641    }
642
643    /// Create a ValueWord from a VMArray directly (no intermediate conversion).
644    #[inline]
645    pub fn from_array(a: crate::value::VMArray) -> Self {
646        Self::heap_box(HeapValue::Array(a))
647    }
648
649    /// Create a ValueWord from Decimal directly (no intermediate conversion).
650    #[inline]
651    pub fn from_decimal(d: rust_decimal::Decimal) -> Self {
652        Self::heap_box(HeapValue::Decimal(d))
653    }
654
655    /// Create a ValueWord from any HeapValue directly (no intermediate conversion).
656    ///
657    /// BigInt that fits i48 is unwrapped to its native ValueWord inline tag instead
658    /// of being heap-allocated. All other variants are heap-boxed.
659    #[inline]
660    pub fn from_heap_value(v: HeapValue) -> Self {
661        match v {
662            HeapValue::BigInt(i) => Self::from_i64(i),
663            other => Self::heap_box(other),
664        }
665    }
666
667    // ===== DataTable family constructors =====
668
669    /// Create a ValueWord from a DataTable directly.
670    #[inline]
671    pub fn from_datatable(dt: Arc<DataTable>) -> Self {
672        Self::heap_box(HeapValue::DataTable(dt))
673    }
674
675    /// Create a ValueWord TypedTable directly.
676    #[inline]
677    pub fn from_typed_table(schema_id: u64, table: Arc<DataTable>) -> Self {
678        Self::heap_box(HeapValue::TypedTable { schema_id, table })
679    }
680
681    /// Create a ValueWord RowView directly.
682    #[inline]
683    pub fn from_row_view(schema_id: u64, table: Arc<DataTable>, row_idx: usize) -> Self {
684        Self::heap_box(HeapValue::RowView {
685            schema_id,
686            table,
687            row_idx,
688        })
689    }
690
691    /// Create a ValueWord ColumnRef directly.
692    #[inline]
693    pub fn from_column_ref(schema_id: u64, table: Arc<DataTable>, col_id: u32) -> Self {
694        Self::heap_box(HeapValue::ColumnRef {
695            schema_id,
696            table,
697            col_id,
698        })
699    }
700
701    /// Create a ValueWord IndexedTable directly.
702    #[inline]
703    pub fn from_indexed_table(schema_id: u64, table: Arc<DataTable>, index_col: u32) -> Self {
704        Self::heap_box(HeapValue::IndexedTable {
705            schema_id,
706            table,
707            index_col,
708        })
709    }
710
711    // ===== Container / wrapper constructors =====
712
713    /// Create a ValueWord Range directly.
714    #[inline]
715    pub fn from_range(start: Option<ValueWord>, end: Option<ValueWord>, inclusive: bool) -> Self {
716        Self::heap_box(HeapValue::Range {
717            start: start.map(Box::new),
718            end: end.map(Box::new),
719            inclusive,
720        })
721    }
722
723    /// Create a ValueWord Enum directly.
724    #[inline]
725    pub fn from_enum(e: EnumValue) -> Self {
726        Self::heap_box(HeapValue::Enum(Box::new(e)))
727    }
728
729    /// Create a ValueWord Some directly.
730    #[inline]
731    pub fn from_some(inner: ValueWord) -> Self {
732        Self::heap_box(HeapValue::Some(Box::new(inner)))
733    }
734
735    /// Create a ValueWord Ok directly.
736    #[inline]
737    pub fn from_ok(inner: ValueWord) -> Self {
738        Self::heap_box(HeapValue::Ok(Box::new(inner)))
739    }
740
741    /// Create a ValueWord Err directly.
742    #[inline]
743    pub fn from_err(inner: ValueWord) -> Self {
744        Self::heap_box(HeapValue::Err(Box::new(inner)))
745    }
746
747    // ===== HashMap constructors =====
748
749    /// Create a ValueWord HashMap from keys, values, and index.
750    #[inline]
751    pub fn from_hashmap(
752        keys: Vec<ValueWord>,
753        values: Vec<ValueWord>,
754        index: HashMap<u64, Vec<usize>>,
755    ) -> ValueWord {
756        ValueWord::heap_box(HeapValue::HashMap(Box::new(HashMapData {
757            keys,
758            values,
759            index,
760            shape_id: None,
761        })))
762    }
763
764    /// Create an empty ValueWord HashMap.
765    #[inline]
766    pub fn empty_hashmap() -> ValueWord {
767        ValueWord::heap_box(HeapValue::HashMap(Box::new(HashMapData {
768            keys: Vec::new(),
769            values: Vec::new(),
770            index: HashMap::new(),
771            shape_id: None,
772        })))
773    }
774
775    /// Create a ValueWord HashMap from keys and values, auto-building the bucket
776    /// index and computing a shape for O(1) property access when all keys are strings.
777    #[inline]
778    pub fn from_hashmap_pairs(keys: Vec<ValueWord>, values: Vec<ValueWord>) -> ValueWord {
779        let index = HashMapData::rebuild_index(&keys);
780        let shape_id = HashMapData::compute_shape(&keys);
781        ValueWord::heap_box(HeapValue::HashMap(Box::new(HashMapData {
782            keys,
783            values,
784            index,
785            shape_id,
786        })))
787    }
788
789    // ===== Set constructors =====
790
791    /// Create a ValueWord Set from items (deduplicating).
792    #[inline]
793    pub fn from_set(items: Vec<ValueWord>) -> ValueWord {
794        ValueWord::heap_box(HeapValue::Set(Box::new(SetData::from_items(items))))
795    }
796
797    /// Create an empty ValueWord Set.
798    #[inline]
799    pub fn empty_set() -> ValueWord {
800        ValueWord::heap_box(HeapValue::Set(Box::new(SetData {
801            items: Vec::new(),
802            index: HashMap::new(),
803        })))
804    }
805
806    // ===== Deque constructors =====
807
808    /// Create a ValueWord Deque from items.
809    #[inline]
810    pub fn from_deque(items: Vec<ValueWord>) -> ValueWord {
811        ValueWord::heap_box(HeapValue::Deque(Box::new(DequeData::from_items(items))))
812    }
813
814    /// Create an empty ValueWord Deque.
815    #[inline]
816    pub fn empty_deque() -> ValueWord {
817        ValueWord::heap_box(HeapValue::Deque(Box::new(DequeData::new())))
818    }
819
820    // ===== PriorityQueue constructors =====
821
822    /// Create a ValueWord PriorityQueue from items (heapified).
823    #[inline]
824    pub fn from_priority_queue(items: Vec<ValueWord>) -> ValueWord {
825        ValueWord::heap_box(HeapValue::PriorityQueue(Box::new(
826            PriorityQueueData::from_items(items),
827        )))
828    }
829
830    /// Create an empty ValueWord PriorityQueue.
831    #[inline]
832    pub fn empty_priority_queue() -> ValueWord {
833        ValueWord::heap_box(HeapValue::PriorityQueue(Box::new(PriorityQueueData::new())))
834    }
835
836    // ===== Content constructors =====
837
838    /// Create a ValueWord from a ContentNode directly.
839    #[inline]
840    pub fn from_content(node: ContentNode) -> ValueWord {
841        ValueWord::heap_box(HeapValue::Content(Box::new(node)))
842    }
843
844    // ===== Typed collection constructors =====
845
846    /// Create a ValueWord IntArray from an Arc<TypedBuffer<i64>>.
847    #[inline]
848    pub fn from_int_array(a: Arc<crate::typed_buffer::TypedBuffer<i64>>) -> Self {
849        Self::heap_box(HeapValue::IntArray(a))
850    }
851
852    /// Create a ValueWord FloatArray from an Arc<AlignedTypedBuffer>.
853    #[inline]
854    pub fn from_float_array(a: Arc<crate::typed_buffer::AlignedTypedBuffer>) -> Self {
855        Self::heap_box(HeapValue::FloatArray(a))
856    }
857
858    /// Create a ValueWord BoolArray from an Arc<TypedBuffer<u8>>.
859    #[inline]
860    pub fn from_bool_array(a: Arc<crate::typed_buffer::TypedBuffer<u8>>) -> Self {
861        Self::heap_box(HeapValue::BoolArray(a))
862    }
863
864    /// Create a ValueWord I8Array.
865    #[inline]
866    pub fn from_i8_array(a: Arc<crate::typed_buffer::TypedBuffer<i8>>) -> Self {
867        Self::heap_box(HeapValue::I8Array(a))
868    }
869
870    /// Create a ValueWord I16Array.
871    #[inline]
872    pub fn from_i16_array(a: Arc<crate::typed_buffer::TypedBuffer<i16>>) -> Self {
873        Self::heap_box(HeapValue::I16Array(a))
874    }
875
876    /// Create a ValueWord I32Array.
877    #[inline]
878    pub fn from_i32_array(a: Arc<crate::typed_buffer::TypedBuffer<i32>>) -> Self {
879        Self::heap_box(HeapValue::I32Array(a))
880    }
881
882    /// Create a ValueWord U8Array.
883    #[inline]
884    pub fn from_u8_array(a: Arc<crate::typed_buffer::TypedBuffer<u8>>) -> Self {
885        Self::heap_box(HeapValue::U8Array(a))
886    }
887
888    /// Create a ValueWord U16Array.
889    #[inline]
890    pub fn from_u16_array(a: Arc<crate::typed_buffer::TypedBuffer<u16>>) -> Self {
891        Self::heap_box(HeapValue::U16Array(a))
892    }
893
894    /// Create a ValueWord U32Array.
895    #[inline]
896    pub fn from_u32_array(a: Arc<crate::typed_buffer::TypedBuffer<u32>>) -> Self {
897        Self::heap_box(HeapValue::U32Array(a))
898    }
899
900    /// Create a ValueWord U64Array.
901    #[inline]
902    pub fn from_u64_array(a: Arc<crate::typed_buffer::TypedBuffer<u64>>) -> Self {
903        Self::heap_box(HeapValue::U64Array(a))
904    }
905
906    /// Create a ValueWord F32Array.
907    #[inline]
908    pub fn from_f32_array(a: Arc<crate::typed_buffer::TypedBuffer<f32>>) -> Self {
909        Self::heap_box(HeapValue::F32Array(a))
910    }
911
912    /// Create a ValueWord Matrix from MatrixData.
913    #[inline]
914    pub fn from_matrix(m: Arc<crate::heap_value::MatrixData>) -> Self {
915        Self::heap_box(HeapValue::Matrix(m))
916    }
917
918    /// Create a ValueWord FloatArraySlice — a zero-copy view into a parent matrix.
919    #[inline]
920    pub fn from_float_array_slice(
921        parent: Arc<crate::heap_value::MatrixData>,
922        offset: u32,
923        len: u32,
924    ) -> Self {
925        Self::heap_box(HeapValue::FloatArraySlice {
926            parent,
927            offset,
928            len,
929        })
930    }
931
932    /// Create a ValueWord Iterator from IteratorState.
933    #[inline]
934    pub fn from_iterator(state: Box<crate::heap_value::IteratorState>) -> Self {
935        Self::heap_box(HeapValue::Iterator(state))
936    }
937
938    /// Create a ValueWord Generator from GeneratorState.
939    #[inline]
940    pub fn from_generator(state: Box<crate::heap_value::GeneratorState>) -> Self {
941        Self::heap_box(HeapValue::Generator(state))
942    }
943
944    // ===== Async / concurrency constructors =====
945
946    /// Create a ValueWord Future directly.
947    #[inline]
948    pub fn from_future(id: u64) -> Self {
949        Self::heap_box(HeapValue::Future(id))
950    }
951
952    /// Create a ValueWord TaskGroup directly.
953    #[inline]
954    pub fn from_task_group(kind: u8, task_ids: Vec<u64>) -> Self {
955        Self::heap_box(HeapValue::TaskGroup { kind, task_ids })
956    }
957
958    /// Create a ValueWord Mutex wrapping a value.
959    #[inline]
960    pub fn from_mutex(value: ValueWord) -> Self {
961        Self::heap_box(HeapValue::Mutex(Box::new(
962            crate::heap_value::MutexData::new(value),
963        )))
964    }
965
966    /// Create a ValueWord Atomic with an initial integer value.
967    #[inline]
968    pub fn from_atomic(value: i64) -> Self {
969        Self::heap_box(HeapValue::Atomic(Box::new(
970            crate::heap_value::AtomicData::new(value),
971        )))
972    }
973
974    /// Create a ValueWord Lazy with an initializer closure.
975    #[inline]
976    pub fn from_lazy(initializer: ValueWord) -> Self {
977        Self::heap_box(HeapValue::Lazy(Box::new(crate::heap_value::LazyData::new(
978            initializer,
979        ))))
980    }
981
982    /// Create a ValueWord Channel endpoint.
983    #[inline]
984    pub fn from_channel(data: ChannelData) -> Self {
985        Self::heap_box(HeapValue::Channel(Box::new(data)))
986    }
987
988    // ===== Trait dispatch constructors =====
989
990    /// Create a ValueWord TraitObject directly.
991    #[inline]
992    pub fn from_trait_object(value: ValueWord, vtable: Arc<VTable>) -> Self {
993        Self::heap_box(HeapValue::TraitObject {
994            value: Box::new(value),
995            vtable,
996        })
997    }
998
999    // ===== SQL pushdown constructors =====
1000
1001    /// Create a ValueWord ExprProxy directly.
1002    #[inline]
1003    pub fn from_expr_proxy(col_name: Arc<String>) -> Self {
1004        Self::heap_box(HeapValue::ExprProxy(col_name))
1005    }
1006
1007    /// Create a ValueWord FilterExpr directly.
1008    #[inline]
1009    pub fn from_filter_expr(node: Arc<FilterNode>) -> Self {
1010        Self::heap_box(HeapValue::FilterExpr(node))
1011    }
1012
1013    // ===== Instant constructors =====
1014
1015    /// Create a ValueWord Instant directly.
1016    #[inline]
1017    pub fn from_instant(t: std::time::Instant) -> Self {
1018        Self::heap_box(HeapValue::Instant(Box::new(t)))
1019    }
1020
1021    // ===== IoHandle constructors =====
1022
1023    /// Create a ValueWord IoHandle.
1024    #[inline]
1025    pub fn from_io_handle(data: crate::heap_value::IoHandleData) -> Self {
1026        Self::heap_box(HeapValue::IoHandle(Box::new(data)))
1027    }
1028
1029    // ===== Time constructors =====
1030
1031    /// Create a ValueWord Time directly from a DateTime<FixedOffset>.
1032    #[inline]
1033    pub fn from_time(t: DateTime<FixedOffset>) -> Self {
1034        Self::heap_box(HeapValue::Time(t))
1035    }
1036
1037    /// Create a ValueWord Time from a DateTime<Utc> (converts to FixedOffset).
1038    #[inline]
1039    pub fn from_time_utc(t: DateTime<Utc>) -> Self {
1040        Self::heap_box(HeapValue::Time(t.fixed_offset()))
1041    }
1042
1043    /// Create a ValueWord Duration directly.
1044    #[inline]
1045    pub fn from_duration(d: Duration) -> Self {
1046        Self::heap_box(HeapValue::Duration(d))
1047    }
1048
1049    /// Create a ValueWord TimeSpan directly.
1050    #[inline]
1051    pub fn from_timespan(ts: chrono::Duration) -> Self {
1052        Self::heap_box(HeapValue::TimeSpan(ts))
1053    }
1054
1055    /// Create a ValueWord Timeframe directly.
1056    #[inline]
1057    pub fn from_timeframe(tf: Timeframe) -> Self {
1058        Self::heap_box(HeapValue::Timeframe(tf))
1059    }
1060
1061    // ===== Other constructors =====
1062
1063    /// Create a ValueWord HostClosure directly.
1064    #[inline]
1065    pub fn from_host_closure(nc: HostCallable) -> Self {
1066        Self::heap_box(HeapValue::HostClosure(nc))
1067    }
1068
1069    /// Create a ValueWord PrintResult directly.
1070    #[inline]
1071    pub fn from_print_result(pr: PrintResult) -> Self {
1072        Self::heap_box(HeapValue::PrintResult(Box::new(pr)))
1073    }
1074
1075    /// Create a ValueWord SimulationCall directly.
1076    #[inline]
1077    pub fn from_simulation_call(name: String, params: HashMap<String, ValueWord>) -> Self {
1078        Self::heap_box(HeapValue::SimulationCall(Box::new(
1079            crate::heap_value::SimulationCallData { name, params },
1080        )))
1081    }
1082
1083    /// Create a ValueWord FunctionRef directly.
1084    #[inline]
1085    pub fn from_function_ref(name: String, closure: Option<ValueWord>) -> Self {
1086        Self::heap_box(HeapValue::FunctionRef {
1087            name,
1088            closure: closure.map(Box::new),
1089        })
1090    }
1091
1092    /// Create a ValueWord DataReference directly.
1093    #[inline]
1094    pub fn from_data_reference(
1095        datetime: DateTime<FixedOffset>,
1096        id: String,
1097        timeframe: Timeframe,
1098    ) -> Self {
1099        Self::heap_box(HeapValue::DataReference(Box::new(
1100            crate::heap_value::DataReferenceData {
1101                datetime,
1102                id,
1103                timeframe,
1104            },
1105        )))
1106    }
1107
1108    /// Create a ValueWord TimeReference directly.
1109    #[inline]
1110    pub fn from_time_reference(tr: TimeReference) -> Self {
1111        Self::heap_box(HeapValue::TimeReference(Box::new(tr)))
1112    }
1113
1114    /// Create a ValueWord DateTimeExpr directly.
1115    #[inline]
1116    pub fn from_datetime_expr(de: DateTimeExpr) -> Self {
1117        Self::heap_box(HeapValue::DateTimeExpr(Box::new(de)))
1118    }
1119
1120    /// Create a ValueWord DataDateTimeRef directly.
1121    #[inline]
1122    pub fn from_data_datetime_ref(dr: DataDateTimeRef) -> Self {
1123        Self::heap_box(HeapValue::DataDateTimeRef(Box::new(dr)))
1124    }
1125
1126    /// Create a ValueWord TypeAnnotation directly.
1127    #[inline]
1128    pub fn from_type_annotation(ta: TypeAnnotation) -> Self {
1129        Self::heap_box(HeapValue::TypeAnnotation(Box::new(ta)))
1130    }
1131
1132    /// Create a ValueWord TypeAnnotatedValue directly.
1133    #[inline]
1134    pub fn from_type_annotated_value(type_name: String, value: ValueWord) -> Self {
1135        Self::heap_box(HeapValue::TypeAnnotatedValue {
1136            type_name,
1137            value: Box::new(value),
1138        })
1139    }
1140
1141    /// Create a ValueWord by "cloning" from raw bits read from a stack slot.
1142    ///
1143    /// For inline values (f64, i48, bool, none, unit, function), this simply
1144    /// wraps the bits. For heap values (without `gc`), it bumps the Arc refcount.
1145    /// With `gc`, it's a pure bitwise copy (GC handles liveness).
1146    ///
1147    /// # Safety
1148    /// `bits` must be a valid ValueWord representation (either an inline value
1149    /// or a heap-tagged value with a valid Arc<HeapValue> / GC pointer).
1150    #[inline(always)]
1151    #[cfg(not(feature = "gc"))]
1152    pub unsafe fn clone_from_bits(bits: u64) -> Self {
1153        if is_tagged(bits) && get_tag(bits) == TAG_HEAP {
1154            let ptr = get_payload(bits) as *const HeapValue;
1155            unsafe { Arc::increment_strong_count(ptr) };
1156        }
1157        Self(bits)
1158    }
1159
1160    /// Create a ValueWord by "cloning" from raw bits (GC path: pure bitwise copy).
1161    ///
1162    /// # Safety
1163    /// `bits` must be a valid ValueWord representation.
1164    #[inline(always)]
1165    #[cfg(feature = "gc")]
1166    pub unsafe fn clone_from_bits(bits: u64) -> Self {
1167        // GC path: no refcount — just copy the bits.
1168        Self(bits)
1169    }
1170
1171    // ===== Type checks =====
1172
1173    /// Returns true if this value is an inline f64 (not a tagged value).
1174    #[inline(always)]
1175    pub fn is_f64(&self) -> bool {
1176        !is_tagged(self.0)
1177    }
1178
1179    /// Returns true if this value is an inline i48 integer.
1180    #[inline(always)]
1181    pub fn is_i64(&self) -> bool {
1182        is_tagged(self.0) && get_tag(self.0) == TAG_INT
1183    }
1184
1185    /// Returns true if this value is a bool.
1186    #[inline(always)]
1187    pub fn is_bool(&self) -> bool {
1188        is_tagged(self.0) && get_tag(self.0) == TAG_BOOL
1189    }
1190
1191    /// Returns true if this value is None.
1192    #[inline(always)]
1193    pub fn is_none(&self) -> bool {
1194        is_tagged(self.0) && get_tag(self.0) == TAG_NONE
1195    }
1196
1197    /// Returns true if this value is Unit.
1198    #[inline(always)]
1199    pub fn is_unit(&self) -> bool {
1200        is_tagged(self.0) && get_tag(self.0) == TAG_UNIT
1201    }
1202
1203    /// Returns true if this value is a function reference.
1204    #[inline(always)]
1205    pub fn is_function(&self) -> bool {
1206        is_tagged(self.0) && get_tag(self.0) == TAG_FUNCTION
1207    }
1208
1209    /// Returns true if this value is a heap-boxed HeapValue.
1210    #[inline(always)]
1211    pub fn is_heap(&self) -> bool {
1212        is_tagged(self.0) && get_tag(self.0) == TAG_HEAP
1213    }
1214
1215    /// Returns true if this value is a stack reference.
1216    #[inline(always)]
1217    pub fn is_ref(&self) -> bool {
1218        if is_tagged(self.0) {
1219            return get_tag(self.0) == TAG_REF;
1220        }
1221        matches!(self.as_heap_ref(), Some(HeapValue::ProjectedRef(_)))
1222    }
1223
1224    /// Extract the reference target.
1225    #[inline]
1226    pub fn as_ref_target(&self) -> Option<RefTarget> {
1227        if is_tagged(self.0) && get_tag(self.0) == TAG_REF {
1228            let payload = get_payload(self.0);
1229            let idx = (payload & REF_TARGET_INDEX_MASK) as usize;
1230            if payload & REF_TARGET_MODULE_FLAG != 0 {
1231                return Some(RefTarget::ModuleBinding(idx));
1232            }
1233            return Some(RefTarget::Stack(idx));
1234        }
1235        if let Some(HeapValue::ProjectedRef(data)) = self.as_heap_ref() {
1236            return Some(RefTarget::Projected((**data).clone()));
1237        }
1238        None
1239    }
1240
1241    /// Extract the absolute stack slot index from a stack reference.
1242    #[inline]
1243    pub fn as_ref_slot(&self) -> Option<usize> {
1244        match self.as_ref_target() {
1245            Some(RefTarget::Stack(slot)) => Some(slot),
1246            _ => None,
1247        }
1248    }
1249
1250    // ===== Checked extractors =====
1251
1252    /// Extract as f64, returning None if this is not an inline f64.
1253    #[inline]
1254    pub fn as_f64(&self) -> Option<f64> {
1255        if self.is_f64() {
1256            Some(f64::from_bits(self.0))
1257        } else {
1258            None
1259        }
1260    }
1261
1262    /// Extract as i64, returning None if this is not an exact signed integer.
1263    ///
1264    /// Accepts inline i48 values, heap BigInt, and signed-compatible native scalars.
1265    #[inline]
1266    pub fn as_i64(&self) -> Option<i64> {
1267        if self.is_i64() {
1268            Some(sign_extend_i48(get_payload(self.0)))
1269        } else if let Some(HeapValue::BigInt(v)) = self.as_heap_ref() {
1270            Some(*v)
1271        } else if let Some(HeapValue::NativeScalar(v)) = self.as_heap_ref() {
1272            v.as_i64()
1273        } else {
1274            None
1275        }
1276    }
1277
1278    /// Extract as u64 when the value is an exact non-negative integer.
1279    #[inline]
1280    pub fn as_u64(&self) -> Option<u64> {
1281        if self.is_i64() {
1282            let v = sign_extend_i48(get_payload(self.0));
1283            return u64::try_from(v).ok();
1284        }
1285
1286        if let Some(hv) = self.as_heap_ref() {
1287            return match hv {
1288                HeapValue::BigInt(v) => u64::try_from(*v).ok(),
1289                HeapValue::NativeScalar(v) => v.as_u64(),
1290                _ => None,
1291            };
1292        }
1293
1294        None
1295    }
1296
1297    /// Extract exact integer domain as i128 (used for width-aware arithmetic/comparison).
1298    #[inline]
1299    pub fn as_i128_exact(&self) -> Option<i128> {
1300        if self.is_i64() {
1301            return Some(sign_extend_i48(get_payload(self.0)) as i128);
1302        }
1303
1304        if let Some(hv) = self.as_heap_ref() {
1305            return match hv {
1306                HeapValue::BigInt(v) => Some(*v as i128),
1307                HeapValue::NativeScalar(v) => v.as_i128(),
1308                _ => None,
1309            };
1310        }
1311
1312        None
1313    }
1314
1315    /// Extract numeric values as f64, including lossless i48→f64 coercion.
1316    #[inline]
1317    pub fn as_number_strict(&self) -> Option<f64> {
1318        if self.is_f64() {
1319            return Some(f64::from_bits(self.0));
1320        }
1321        // Inline i48 integers — lossless conversion to f64.
1322        if is_tagged(self.0) && get_tag(self.0) == TAG_INT {
1323            return Some(sign_extend_i48(get_payload(self.0)) as f64);
1324        }
1325        if let Some(hv) = self.as_heap_ref() {
1326            return match hv {
1327                HeapValue::NativeScalar(NativeScalar::F32(v)) => Some(*v as f64),
1328                _ => None,
1329            };
1330        }
1331        None
1332    }
1333
1334    /// Extract as bool, returning None if this is not a bool.
1335    #[inline]
1336    pub fn as_bool(&self) -> Option<bool> {
1337        if self.is_bool() {
1338            Some(get_payload(self.0) != 0)
1339        } else {
1340            None
1341        }
1342    }
1343
1344    /// Extract as function ID, returning None if this is not a function.
1345    #[inline]
1346    pub fn as_function(&self) -> Option<u16> {
1347        if self.is_function() {
1348            Some(get_payload(self.0) as u16)
1349        } else {
1350            None
1351        }
1352    }
1353
1354    // ===== Unchecked extractors (for hot paths where the compiler guarantees type) =====
1355
1356    /// Extract f64 without type checking.
1357    /// Safely handles inline i48 ints via lossless coercion.
1358    ///
1359    /// # Safety
1360    /// Caller must ensure the value is numeric (f64 or i48).
1361    #[inline(always)]
1362    pub unsafe fn as_f64_unchecked(&self) -> f64 {
1363        if self.is_f64() {
1364            f64::from_bits(self.0)
1365        } else if is_tagged(self.0) && get_tag(self.0) == TAG_INT {
1366            sign_extend_i48(get_payload(self.0)) as f64
1367        } else {
1368            debug_assert!(false, "as_f64_unchecked on non-numeric ValueWord");
1369            0.0
1370        }
1371    }
1372
1373    /// Extract i64 without type checking.
1374    /// Safely handles f64 values via truncation.
1375    ///
1376    /// # Safety
1377    /// Caller must ensure the value is numeric (i48 or f64).
1378    #[inline(always)]
1379    pub unsafe fn as_i64_unchecked(&self) -> i64 {
1380        if is_tagged(self.0) && get_tag(self.0) == TAG_INT {
1381            sign_extend_i48(get_payload(self.0))
1382        } else if self.is_f64() {
1383            f64::from_bits(self.0) as i64
1384        } else {
1385            debug_assert!(false, "as_i64_unchecked on non-numeric ValueWord");
1386            0
1387        }
1388    }
1389
1390    /// Extract bool without type checking.
1391    ///
1392    /// # Safety
1393    /// Caller must ensure `self.is_bool()` is true.
1394    #[inline(always)]
1395    pub unsafe fn as_bool_unchecked(&self) -> bool {
1396        debug_assert!(self.is_bool(), "as_bool_unchecked on non-bool ValueWord");
1397        get_payload(self.0) != 0
1398    }
1399
1400    /// Extract function ID without type checking.
1401    ///
1402    /// # Safety
1403    /// Caller must ensure `self.is_function()` is true.
1404    #[inline(always)]
1405    pub unsafe fn as_function_unchecked(&self) -> u16 {
1406        debug_assert!(
1407            self.is_function(),
1408            "as_function_unchecked on non-function ValueWord"
1409        );
1410        get_payload(self.0) as u16
1411    }
1412
1413    // ===== ValueWord inspection API =====
1414
1415    /// Get a reference to the heap-boxed HeapValue without cloning.
1416    /// Returns None if this is not a heap value.
1417    #[inline]
1418    pub fn as_heap_ref(&self) -> Option<&HeapValue> {
1419        if is_tagged(self.0) && get_tag(self.0) == TAG_HEAP {
1420            let ptr = get_payload(self.0) as *const HeapValue;
1421            Some(unsafe { &*ptr })
1422        } else {
1423            None
1424        }
1425    }
1426
1427    /// Get a mutable reference to the heap-boxed HeapValue, cloning if shared.
1428    /// Returns None if this is not a heap value.
1429    ///
1430    /// Without `gc`: Uses Arc::make_mut semantics (clones if refcount > 1).
1431    /// With `gc`: Direct mutable access (GC objects are not refcounted).
1432    #[inline]
1433    #[cfg(not(feature = "gc"))]
1434    pub fn as_heap_mut(&mut self) -> Option<&mut HeapValue> {
1435        if is_tagged(self.0) && get_tag(self.0) == TAG_HEAP {
1436            let ptr = get_payload(self.0) as *const HeapValue;
1437            // Reconstruct the Arc without dropping it (we'll consume it via make_mut).
1438            let mut arc = unsafe { Arc::from_raw(ptr) };
1439            // Ensure unique ownership (clones HeapValue if refcount > 1, no-op if == 1).
1440            Arc::make_mut(&mut arc);
1441            // Leak the Arc back into a raw pointer and update self.0
1442            // (make_mut may have reallocated if refcount > 1).
1443            let new_ptr = Arc::into_raw(arc) as u64;
1444            self.0 = make_tagged(TAG_HEAP, new_ptr & PAYLOAD_MASK);
1445            let final_ptr = get_payload(self.0) as *mut HeapValue;
1446            Some(unsafe { &mut *final_ptr })
1447        } else {
1448            None
1449        }
1450    }
1451
1452    /// Get a mutable reference to the heap-boxed HeapValue (GC path).
1453    ///
1454    /// With GC, objects are not refcounted so we can always get a direct
1455    /// mutable reference without copy-on-write.
1456    #[inline]
1457    #[cfg(feature = "gc")]
1458    pub fn as_heap_mut(&mut self) -> Option<&mut HeapValue> {
1459        if is_tagged(self.0) && get_tag(self.0) == TAG_HEAP {
1460            let ptr = get_payload(self.0) as *mut HeapValue;
1461            Some(unsafe { &mut *ptr })
1462        } else {
1463            None
1464        }
1465    }
1466
1467    /// Check truthiness without materializing HeapValue.
1468    #[inline]
1469    pub fn is_truthy(&self) -> bool {
1470        if !is_tagged(self.0) {
1471            // f64: truthy if non-zero and not NaN
1472            let f = f64::from_bits(self.0);
1473            return f != 0.0 && !f.is_nan();
1474        }
1475        let tag = get_tag(self.0);
1476        if tag == TAG_HEAP {
1477            let ptr = get_payload(self.0) as *const HeapValue;
1478            return unsafe { (*ptr).is_truthy() };
1479        }
1480        nan_tag_is_truthy(tag, get_payload(self.0))
1481    }
1482
1483    /// Extract as f64 using safe coercion.
1484    ///
1485    /// This accepts:
1486    /// - explicit floating-point values (`number`, `f32`)
1487    /// - inline `int` values (`i48`)
1488    ///
1489    /// It intentionally does not coerce `BigInt`, `i64`, or `u64`
1490    /// native scalars into `f64` to avoid lossy conversion.
1491    #[inline]
1492    pub fn as_number_coerce(&self) -> Option<f64> {
1493        if let Some(n) = self.as_number_strict() {
1494            Some(n)
1495        } else if is_tagged(self.0) && get_tag(self.0) == TAG_INT {
1496            Some(sign_extend_i48(get_payload(self.0)) as f64)
1497        } else {
1498            None
1499        }
1500    }
1501
1502    /// Check if this ValueWord is a module function tag.
1503    #[inline(always)]
1504    pub fn is_module_function(&self) -> bool {
1505        is_tagged(self.0) && get_tag(self.0) == TAG_MODULE_FN
1506    }
1507
1508    /// Extract module function index.
1509    #[inline]
1510    pub fn as_module_function(&self) -> Option<usize> {
1511        if self.is_module_function() {
1512            Some(get_payload(self.0) as usize)
1513        } else {
1514            None
1515        }
1516    }
1517
1518    // ===== HeapValue inspection =====
1519
1520    /// Get the HeapKind discriminator without cloning.
1521    /// Returns None if this is not a heap value.
1522    #[inline]
1523    pub fn heap_kind(&self) -> Option<crate::heap_value::HeapKind> {
1524        if let Some(hv) = self.as_heap_ref() {
1525            Some(hv.kind())
1526        } else {
1527            std::option::Option::None
1528        }
1529    }
1530
1531    // ===== Phase 2A: Extended inspection API =====
1532
1533    /// Tag discriminator for ValueWord values.
1534    /// Used for fast dispatch without materializing HeapValue.
1535    #[inline]
1536    pub fn tag(&self) -> NanTag {
1537        if !is_tagged(self.0) {
1538            return NanTag::F64;
1539        }
1540        match get_tag(self.0) {
1541            TAG_INT => NanTag::I48,
1542            TAG_BOOL => NanTag::Bool,
1543            TAG_NONE => NanTag::None,
1544            TAG_UNIT => NanTag::Unit,
1545            TAG_FUNCTION => NanTag::Function,
1546            TAG_MODULE_FN => NanTag::ModuleFunction,
1547            TAG_HEAP => NanTag::Heap,
1548            TAG_REF => NanTag::Ref,
1549            _ => unreachable!("invalid ValueWord tag"),
1550        }
1551    }
1552
1553    /// Get a reference to a heap String without cloning.
1554    /// Returns None if this is not a heap-boxed String.
1555    #[inline]
1556    pub fn as_str(&self) -> Option<&str> {
1557        if let Some(hv) = self.as_heap_ref() {
1558            match hv {
1559                HeapValue::String(s) => Some(s.as_str()),
1560                _ => std::option::Option::None,
1561            }
1562        } else {
1563            std::option::Option::None
1564        }
1565    }
1566
1567    /// Extract a Decimal from a heap-boxed Decimal value.
1568    /// Returns None if this is not a heap-boxed Decimal.
1569    #[inline]
1570    pub fn as_decimal(&self) -> Option<rust_decimal::Decimal> {
1571        if let Some(hv) = self.as_heap_ref() {
1572            match hv {
1573                HeapValue::Decimal(d) => Some(*d),
1574                _ => std::option::Option::None,
1575            }
1576        } else {
1577            std::option::Option::None
1578        }
1579    }
1580
1581    /// Extract a Decimal without type checking.
1582    ///
1583    /// # Safety
1584    /// Caller must ensure this is a heap-boxed Decimal value.
1585    #[inline(always)]
1586    pub unsafe fn as_decimal_unchecked(&self) -> rust_decimal::Decimal {
1587        debug_assert!(matches!(self.as_heap_ref(), Some(HeapValue::Decimal(_))));
1588        match unsafe { self.as_heap_ref().unwrap_unchecked() } {
1589            HeapValue::Decimal(d) => *d,
1590            _ => unsafe { std::hint::unreachable_unchecked() },
1591        }
1592    }
1593
1594    /// Get a reference to a heap Array without cloning.
1595    /// Returns None if this is not a heap-boxed Array.
1596    #[inline]
1597    #[deprecated(note = "Use as_any_array() instead for unified typed array dispatch")]
1598    pub fn as_array(&self) -> Option<&VMArray> {
1599        if let Some(hv) = self.as_heap_ref() {
1600            match hv {
1601                HeapValue::Array(arr) => Some(arr),
1602                _ => std::option::Option::None,
1603            }
1604        } else {
1605            std::option::Option::None
1606        }
1607    }
1608
1609    /// Get a unified read-only view over any array variant (Generic, Int, Float, Bool, width-specific).
1610    #[inline]
1611    pub fn as_any_array(&self) -> Option<ArrayView<'_>> {
1612        match self.as_heap_ref()? {
1613            HeapValue::Array(a) => Some(ArrayView::Generic(a)),
1614            HeapValue::IntArray(a) => Some(ArrayView::Int(a)),
1615            HeapValue::FloatArray(a) => Some(ArrayView::Float(a)),
1616            HeapValue::BoolArray(a) => Some(ArrayView::Bool(a)),
1617            HeapValue::I8Array(a) => Some(ArrayView::I8(a)),
1618            HeapValue::I16Array(a) => Some(ArrayView::I16(a)),
1619            HeapValue::I32Array(a) => Some(ArrayView::I32(a)),
1620            HeapValue::U8Array(a) => Some(ArrayView::U8(a)),
1621            HeapValue::U16Array(a) => Some(ArrayView::U16(a)),
1622            HeapValue::U32Array(a) => Some(ArrayView::U32(a)),
1623            HeapValue::U64Array(a) => Some(ArrayView::U64(a)),
1624            HeapValue::F32Array(a) => Some(ArrayView::F32(a)),
1625            _ => None,
1626        }
1627    }
1628
1629    /// Get a unified mutable view over any array variant. Uses Arc::make_mut for COW.
1630    #[inline]
1631    pub fn as_any_array_mut(&mut self) -> Option<ArrayViewMut<'_>> {
1632        match self.as_heap_mut()? {
1633            HeapValue::Array(a) => Some(ArrayViewMut::Generic(a)),
1634            HeapValue::IntArray(a) => Some(ArrayViewMut::Int(a)),
1635            HeapValue::FloatArray(a) => Some(ArrayViewMut::Float(a)),
1636            HeapValue::BoolArray(a) => Some(ArrayViewMut::Bool(a)),
1637            _ => None,
1638        }
1639    }
1640
1641    // ===== HeapValue extractors for new variants =====
1642
1643    /// Extract a reference to a DataTable.
1644    #[inline]
1645    pub fn as_datatable(&self) -> Option<&Arc<DataTable>> {
1646        match self.as_heap_ref()? {
1647            HeapValue::DataTable(dt) => Some(dt),
1648            _ => std::option::Option::None,
1649        }
1650    }
1651
1652    /// Extract TypedTable fields.
1653    #[inline]
1654    pub fn as_typed_table(&self) -> Option<(u64, &Arc<DataTable>)> {
1655        match self.as_heap_ref()? {
1656            HeapValue::TypedTable { schema_id, table } => Some((*schema_id, table)),
1657            _ => std::option::Option::None,
1658        }
1659    }
1660
1661    /// Extract RowView fields.
1662    #[inline]
1663    pub fn as_row_view(&self) -> Option<(u64, &Arc<DataTable>, usize)> {
1664        match self.as_heap_ref()? {
1665            HeapValue::RowView {
1666                schema_id,
1667                table,
1668                row_idx,
1669            } => Some((*schema_id, table, *row_idx)),
1670            _ => std::option::Option::None,
1671        }
1672    }
1673
1674    /// Extract ColumnRef fields.
1675    #[inline]
1676    pub fn as_column_ref(&self) -> Option<(u64, &Arc<DataTable>, u32)> {
1677        match self.as_heap_ref()? {
1678            HeapValue::ColumnRef {
1679                schema_id,
1680                table,
1681                col_id,
1682            } => Some((*schema_id, table, *col_id)),
1683            _ => std::option::Option::None,
1684        }
1685    }
1686
1687    /// Extract IndexedTable fields.
1688    #[inline]
1689    pub fn as_indexed_table(&self) -> Option<(u64, &Arc<DataTable>, u32)> {
1690        match self.as_heap_ref()? {
1691            HeapValue::IndexedTable {
1692                schema_id,
1693                table,
1694                index_col,
1695            } => Some((*schema_id, table, *index_col)),
1696            _ => std::option::Option::None,
1697        }
1698    }
1699
1700    /// Extract TypedObject fields (schema_id, slots, heap_mask).
1701    #[inline]
1702    pub fn as_typed_object(&self) -> Option<(u64, &[ValueSlot], u64)> {
1703        match self.as_heap_ref()? {
1704            HeapValue::TypedObject {
1705                schema_id,
1706                slots,
1707                heap_mask,
1708            } => Some((*schema_id, slots, *heap_mask)),
1709            _ => std::option::Option::None,
1710        }
1711    }
1712
1713    /// Extract Closure fields (function_id, upvalues).
1714    #[inline]
1715    pub fn as_closure(&self) -> Option<(u16, &[crate::value::Upvalue])> {
1716        match self.as_heap_ref()? {
1717            HeapValue::Closure {
1718                function_id,
1719                upvalues,
1720            } => Some((*function_id, upvalues)),
1721            _ => std::option::Option::None,
1722        }
1723    }
1724
1725    /// Extract the inner value from a Some variant.
1726    #[inline]
1727    pub fn as_some_inner(&self) -> Option<&ValueWord> {
1728        match self.as_heap_ref()? {
1729            HeapValue::Some(inner) => Some(inner),
1730            _ => std::option::Option::None,
1731        }
1732    }
1733
1734    /// Extract the inner value from an Ok variant.
1735    #[inline]
1736    pub fn as_ok_inner(&self) -> Option<&ValueWord> {
1737        match self.as_heap_ref()? {
1738            HeapValue::Ok(inner) => Some(inner),
1739            _ => std::option::Option::None,
1740        }
1741    }
1742
1743    /// Extract the inner value from an Err variant.
1744    #[inline]
1745    pub fn as_err_inner(&self) -> Option<&ValueWord> {
1746        match self.as_heap_ref()? {
1747            HeapValue::Err(inner) => Some(inner),
1748            _ => std::option::Option::None,
1749        }
1750    }
1751
1752    /// Extract the original payload from an Err variant, unwrapping AnyError
1753    /// normalization if present.
1754    ///
1755    /// When `Err(x)` is constructed at runtime, `x` is wrapped in an AnyError
1756    /// TypedObject (slot layout: [category, payload, cause, trace, message, code]).
1757    /// This method detects that wrapper and returns the original payload from
1758    /// slot 1 rather than the full AnyError struct.
1759    pub fn as_err_payload(&self) -> Option<ValueWord> {
1760        let inner = match self.as_heap_ref()? {
1761            HeapValue::Err(inner) => inner.as_ref(),
1762            _ => return std::option::Option::None,
1763        };
1764        // Check if inner is an AnyError TypedObject: slot 0 is "AnyError" string
1765        if let Some(HeapValue::TypedObject {
1766            slots, heap_mask, ..
1767        }) = inner.as_heap_ref()
1768        {
1769            const PAYLOAD_SLOT: usize = 1;
1770            if slots.len() >= 6 && *heap_mask & 1 != 0 {
1771                // Verify slot 0 is the "AnyError" category string
1772                let cat = slots[0].as_heap_value();
1773                if let HeapValue::String(s) = cat {
1774                    if s.as_str() == "AnyError" && PAYLOAD_SLOT < slots.len() {
1775                        let is_heap = *heap_mask & (1u64 << PAYLOAD_SLOT) != 0;
1776                        return Some(slots[PAYLOAD_SLOT].as_value_word(is_heap));
1777                    }
1778                }
1779            }
1780        }
1781        // Not an AnyError wrapper — return inner directly
1782        Some(inner.clone())
1783    }
1784
1785    /// Extract a Future ID.
1786    #[inline]
1787    pub fn as_future(&self) -> Option<u64> {
1788        match self.as_heap_ref()? {
1789            HeapValue::Future(id) => Some(*id),
1790            _ => std::option::Option::None,
1791        }
1792    }
1793
1794    /// Extract TraitObject fields.
1795    #[inline]
1796    pub fn as_trait_object(&self) -> Option<(&ValueWord, &Arc<VTable>)> {
1797        match self.as_heap_ref()? {
1798            HeapValue::TraitObject { value, vtable } => Some((value, vtable)),
1799            _ => std::option::Option::None,
1800        }
1801    }
1802
1803    /// Extract ExprProxy column name.
1804    #[inline]
1805    pub fn as_expr_proxy(&self) -> Option<&Arc<String>> {
1806        match self.as_heap_ref()? {
1807            HeapValue::ExprProxy(name) => Some(name),
1808            _ => std::option::Option::None,
1809        }
1810    }
1811
1812    /// Extract FilterExpr node.
1813    #[inline]
1814    pub fn as_filter_expr(&self) -> Option<&Arc<FilterNode>> {
1815        match self.as_heap_ref()? {
1816            HeapValue::FilterExpr(node) => Some(node),
1817            _ => std::option::Option::None,
1818        }
1819    }
1820
1821    /// Extract a HostClosure reference.
1822    #[inline]
1823    pub fn as_host_closure(&self) -> Option<&HostCallable> {
1824        match self.as_heap_ref()? {
1825            HeapValue::HostClosure(nc) => Some(nc),
1826            _ => std::option::Option::None,
1827        }
1828    }
1829
1830    /// Extract a Duration reference.
1831    #[inline]
1832    pub fn as_duration(&self) -> Option<&shape_ast::ast::Duration> {
1833        match self.as_heap_ref()? {
1834            HeapValue::Duration(d) => Some(d),
1835            _ => std::option::Option::None,
1836        }
1837    }
1838
1839    /// Extract a Range (start, end, inclusive).
1840    #[inline]
1841    pub fn as_range(&self) -> Option<(Option<&ValueWord>, Option<&ValueWord>, bool)> {
1842        match self.as_heap_ref()? {
1843            HeapValue::Range {
1844                start,
1845                end,
1846                inclusive,
1847            } => Some((
1848                start.as_ref().map(|b| b.as_ref()),
1849                end.as_ref().map(|b| b.as_ref()),
1850                *inclusive,
1851            )),
1852            _ => std::option::Option::None,
1853        }
1854    }
1855
1856    /// Extract a TimeSpan (chrono::Duration).
1857    #[inline]
1858    pub fn as_timespan(&self) -> Option<chrono::Duration> {
1859        match self.as_heap_ref()? {
1860            HeapValue::TimeSpan(ts) => Some(*ts),
1861            _ => std::option::Option::None,
1862        }
1863    }
1864
1865    /// Extract an EnumValue reference.
1866    #[inline]
1867    pub fn as_enum(&self) -> Option<&EnumValue> {
1868        match self.as_heap_ref()? {
1869            HeapValue::Enum(e) => Some(e.as_ref()),
1870            _ => std::option::Option::None,
1871        }
1872    }
1873
1874    /// Extract a Timeframe reference.
1875    #[inline]
1876    pub fn as_timeframe(&self) -> Option<&Timeframe> {
1877        match self.as_heap_ref()? {
1878            HeapValue::Timeframe(tf) => Some(tf),
1879            _ => std::option::Option::None,
1880        }
1881    }
1882
1883    /// Get the HashMap contents if this is a HashMap.
1884    #[inline]
1885    pub fn as_hashmap(
1886        &self,
1887    ) -> Option<(&Vec<ValueWord>, &Vec<ValueWord>, &HashMap<u64, Vec<usize>>)> {
1888        match self.as_heap_ref()? {
1889            HeapValue::HashMap(d) => Some((&d.keys, &d.values, &d.index)),
1890            _ => std::option::Option::None,
1891        }
1892    }
1893
1894    /// Get read-only access to the full HashMapData (includes shape_id).
1895    #[inline]
1896    pub fn as_hashmap_data(&self) -> Option<&HashMapData> {
1897        match self.as_heap_ref()? {
1898            HeapValue::HashMap(d) => Some(d),
1899            _ => std::option::Option::None,
1900        }
1901    }
1902
1903    /// Get mutable access to the HashMapData.
1904    /// Uses copy-on-write via `as_heap_mut()` (clones if Arc refcount > 1).
1905    #[inline]
1906    pub fn as_hashmap_mut(&mut self) -> Option<&mut HashMapData> {
1907        match self.as_heap_mut()? {
1908            HeapValue::HashMap(d) => Some(d),
1909            _ => std::option::Option::None,
1910        }
1911    }
1912
1913    /// Get the Set data if this is a Set.
1914    #[inline]
1915    pub fn as_set(&self) -> Option<&SetData> {
1916        match self.as_heap_ref()? {
1917            HeapValue::Set(d) => Some(d),
1918            _ => std::option::Option::None,
1919        }
1920    }
1921
1922    /// Get mutable access to the SetData.
1923    #[inline]
1924    pub fn as_set_mut(&mut self) -> Option<&mut SetData> {
1925        match self.as_heap_mut()? {
1926            HeapValue::Set(d) => Some(d),
1927            _ => std::option::Option::None,
1928        }
1929    }
1930
1931    /// Get the Deque data if this is a Deque.
1932    #[inline]
1933    pub fn as_deque(&self) -> Option<&DequeData> {
1934        match self.as_heap_ref()? {
1935            HeapValue::Deque(d) => Some(d),
1936            _ => std::option::Option::None,
1937        }
1938    }
1939
1940    /// Get mutable access to the DequeData.
1941    #[inline]
1942    pub fn as_deque_mut(&mut self) -> Option<&mut DequeData> {
1943        match self.as_heap_mut()? {
1944            HeapValue::Deque(d) => Some(d),
1945            _ => std::option::Option::None,
1946        }
1947    }
1948
1949    /// Get the PriorityQueue data if this is a PriorityQueue.
1950    #[inline]
1951    pub fn as_priority_queue(&self) -> Option<&PriorityQueueData> {
1952        match self.as_heap_ref()? {
1953            HeapValue::PriorityQueue(d) => Some(d),
1954            _ => std::option::Option::None,
1955        }
1956    }
1957
1958    /// Get mutable access to the PriorityQueueData.
1959    #[inline]
1960    pub fn as_priority_queue_mut(&mut self) -> Option<&mut PriorityQueueData> {
1961        match self.as_heap_mut()? {
1962            HeapValue::PriorityQueue(d) => Some(d),
1963            _ => std::option::Option::None,
1964        }
1965    }
1966
1967    /// Extract a ContentNode reference.
1968    #[inline]
1969    pub fn as_content(&self) -> Option<&ContentNode> {
1970        match self.as_heap_ref()? {
1971            HeapValue::Content(node) => Some(node),
1972            _ => std::option::Option::None,
1973        }
1974    }
1975
1976    /// Extract a DateTime<FixedOffset>.
1977    #[inline]
1978    pub fn as_time(&self) -> Option<DateTime<FixedOffset>> {
1979        match self.as_heap_ref()? {
1980            HeapValue::Time(t) => Some(*t),
1981            _ => std::option::Option::None,
1982        }
1983    }
1984
1985    /// Extract a reference to the Instant.
1986    #[inline]
1987    pub fn as_instant(&self) -> Option<&std::time::Instant> {
1988        match self.as_heap_ref()? {
1989            HeapValue::Instant(t) => Some(t.as_ref()),
1990            _ => std::option::Option::None,
1991        }
1992    }
1993
1994    /// Extract a reference to the IoHandleData.
1995    #[inline]
1996    pub fn as_io_handle(&self) -> Option<&crate::heap_value::IoHandleData> {
1997        match self.as_heap_ref()? {
1998            HeapValue::IoHandle(data) => Some(data.as_ref()),
1999            _ => std::option::Option::None,
2000        }
2001    }
2002
2003    /// Extract a width-aware native scalar value.
2004    #[inline]
2005    pub fn as_native_scalar(&self) -> Option<NativeScalar> {
2006        match self.as_heap_ref()? {
2007            HeapValue::NativeScalar(v) => Some(*v),
2008            _ => None,
2009        }
2010    }
2011
2012    /// Extract a pointer-backed native view.
2013    #[inline]
2014    pub fn as_native_view(&self) -> Option<&NativeViewData> {
2015        match self.as_heap_ref()? {
2016            HeapValue::NativeView(view) => Some(view.as_ref()),
2017            _ => None,
2018        }
2019    }
2020
2021    /// Extract a reference to the DateTime<FixedOffset>.
2022    #[inline]
2023    pub fn as_datetime(&self) -> Option<&DateTime<FixedOffset>> {
2024        match self.as_heap_ref()? {
2025            HeapValue::Time(t) => Some(t),
2026            _ => std::option::Option::None,
2027        }
2028    }
2029
2030    /// Extract an Arc<String> from a heap String.
2031    #[inline]
2032    pub fn as_arc_string(&self) -> Option<&Arc<String>> {
2033        match self.as_heap_ref()? {
2034            HeapValue::String(s) => Some(s),
2035            _ => std::option::Option::None,
2036        }
2037    }
2038
2039    // ===== Typed collection accessors =====
2040
2041    /// Extract a reference to an IntArray.
2042    #[inline]
2043    pub fn as_int_array(&self) -> Option<&Arc<crate::typed_buffer::TypedBuffer<i64>>> {
2044        match self.as_heap_ref()? {
2045            HeapValue::IntArray(a) => Some(a),
2046            _ => std::option::Option::None,
2047        }
2048    }
2049
2050    /// Extract a reference to a FloatArray.
2051    #[inline]
2052    pub fn as_float_array(&self) -> Option<&Arc<crate::typed_buffer::AlignedTypedBuffer>> {
2053        match self.as_heap_ref()? {
2054            HeapValue::FloatArray(a) => Some(a),
2055            _ => std::option::Option::None,
2056        }
2057    }
2058
2059    /// Extract a reference to a BoolArray.
2060    #[inline]
2061    pub fn as_bool_array(&self) -> Option<&Arc<crate::typed_buffer::TypedBuffer<u8>>> {
2062        match self.as_heap_ref()? {
2063            HeapValue::BoolArray(a) => Some(a),
2064            _ => std::option::Option::None,
2065        }
2066    }
2067
2068    /// Extract a reference to MatrixData.
2069    #[inline]
2070    pub fn as_matrix(&self) -> Option<&crate::heap_value::MatrixData> {
2071        match self.as_heap_ref()? {
2072            HeapValue::Matrix(m) => Some(m.as_ref()),
2073            _ => std::option::Option::None,
2074        }
2075    }
2076
2077    /// Extract a reference to IteratorState.
2078    #[inline]
2079    pub fn as_iterator(&self) -> Option<&crate::heap_value::IteratorState> {
2080        match self.as_heap_ref()? {
2081            HeapValue::Iterator(it) => Some(it.as_ref()),
2082            _ => std::option::Option::None,
2083        }
2084    }
2085
2086    /// Extract a reference to GeneratorState.
2087    #[inline]
2088    pub fn as_generator(&self) -> Option<&crate::heap_value::GeneratorState> {
2089        match self.as_heap_ref()? {
2090            HeapValue::Generator(g) => Some(g.as_ref()),
2091            _ => std::option::Option::None,
2092        }
2093    }
2094
2095    /// Get the length of a typed array (IntArray, FloatArray, BoolArray, width-specific).
2096    /// Returns None for non-typed-array values.
2097    #[inline]
2098    pub fn typed_array_len(&self) -> Option<usize> {
2099        match self.as_heap_ref()? {
2100            HeapValue::IntArray(a) => Some(a.len()),
2101            HeapValue::FloatArray(a) => Some(a.len()),
2102            HeapValue::BoolArray(a) => Some(a.len()),
2103            HeapValue::I8Array(a) => Some(a.len()),
2104            HeapValue::I16Array(a) => Some(a.len()),
2105            HeapValue::I32Array(a) => Some(a.len()),
2106            HeapValue::U8Array(a) => Some(a.len()),
2107            HeapValue::U16Array(a) => Some(a.len()),
2108            HeapValue::U32Array(a) => Some(a.len()),
2109            HeapValue::U64Array(a) => Some(a.len()),
2110            HeapValue::F32Array(a) => Some(a.len()),
2111            _ => std::option::Option::None,
2112        }
2113    }
2114
2115    /// Coerce a typed array to a FloatArray (zero-copy for FloatArray, convert for IntArray).
2116    pub fn coerce_to_float_array(&self) -> Option<Arc<crate::typed_buffer::AlignedTypedBuffer>> {
2117        match self.as_heap_ref()? {
2118            HeapValue::FloatArray(a) => Some(Arc::clone(a)),
2119            HeapValue::IntArray(a) => {
2120                let mut buf = crate::typed_buffer::AlignedTypedBuffer::with_capacity(a.len());
2121                for &v in a.iter() {
2122                    buf.push(v as f64);
2123                }
2124                Some(Arc::new(buf))
2125            }
2126            _ => std::option::Option::None,
2127        }
2128    }
2129
2130    /// Convert a typed array to a generic Array of ValueWord values.
2131    pub fn to_generic_array(&self) -> Option<crate::value::VMArray> {
2132        match self.as_heap_ref()? {
2133            HeapValue::IntArray(a) => Some(Arc::new(
2134                a.iter().map(|&v| ValueWord::from_i64(v)).collect(),
2135            )),
2136            HeapValue::FloatArray(a) => Some(Arc::new(
2137                a.iter().map(|&v| ValueWord::from_f64(v)).collect(),
2138            )),
2139            HeapValue::BoolArray(a) => Some(Arc::new(
2140                a.iter().map(|&v| ValueWord::from_bool(v != 0)).collect(),
2141            )),
2142            _ => std::option::Option::None,
2143        }
2144    }
2145
2146    /// Fast equality comparison without materializing HeapValue.
2147    /// For inline types (f64, i48, bool, none, unit, function), compares bits directly.
2148    /// For heap types, falls back to HeapValue equality.
2149    #[inline]
2150    pub fn vw_equals(&self, other: &ValueWord) -> bool {
2151        // Fast path: identical bits means identical value (except NaN)
2152        if self.0 == other.0 {
2153            // Special case: f64 NaN != NaN
2154            if !is_tagged(self.0) {
2155                let f = f64::from_bits(self.0);
2156                return !f.is_nan();
2157            }
2158            // For heap values, same bits = same pointer = definitely equal
2159            return true;
2160        }
2161        // Different bits — for inline types, they're definitely not equal
2162        if !is_tagged(self.0) || !is_tagged(other.0) {
2163            // At least one is f64 — if they're both f64 with different bits, not equal
2164            // (we already handled the case where both are identical)
2165            // Cross-type: f64 == i48 coercion
2166            if let (Some(a), Some(b)) = (self.as_number_coerce(), other.as_number_coerce()) {
2167                return a == b;
2168            }
2169            return false;
2170        }
2171        let tag_a = get_tag(self.0);
2172        let tag_b = get_tag(other.0);
2173        if tag_a != tag_b {
2174            // Different tags — check numeric coercion (f64 vs i48)
2175            if (tag_a == TAG_INT || !is_tagged(self.0)) && (tag_b == TAG_INT || !is_tagged(other.0))
2176            {
2177                if let (Some(a), Some(b)) = (self.as_number_coerce(), other.as_number_coerce()) {
2178                    return a == b;
2179                }
2180            }
2181            return false;
2182        }
2183        // Same tag, different bits — for heap values, compare HeapValue
2184        if tag_a == TAG_HEAP {
2185            let ptr_a = get_payload(self.0) as *const HeapValue;
2186            let ptr_b = get_payload(other.0) as *const HeapValue;
2187            return unsafe { (*ptr_a).equals(&*ptr_b) };
2188        }
2189        // For other same-tag inline values with different bits, not equal
2190        false
2191    }
2192
2193    /// Compute a hash for a ValueWord value, suitable for HashMap key usage.
2194    /// Uses the existing tag dispatch for O(1) inline types.
2195    pub fn vw_hash(&self) -> u64 {
2196        use ahash::AHasher;
2197        use std::hash::{Hash, Hasher};
2198
2199        let tag = self.tag();
2200        match tag {
2201            NanTag::F64 => {
2202                let f = unsafe { self.as_f64_unchecked() };
2203                let bits = if f == 0.0 { 0u64 } else { f.to_bits() };
2204                let mut hasher = AHasher::default();
2205                bits.hash(&mut hasher);
2206                hasher.finish()
2207            }
2208            NanTag::I48 => {
2209                let i = unsafe { self.as_i64_unchecked() };
2210                let mut hasher = AHasher::default();
2211                i.hash(&mut hasher);
2212                hasher.finish()
2213            }
2214            NanTag::Bool => {
2215                if unsafe { self.as_bool_unchecked() } {
2216                    1
2217                } else {
2218                    0
2219                }
2220            }
2221            NanTag::None => 0x_DEAD_0000,
2222            NanTag::Unit => 0x_DEAD_0001,
2223            NanTag::Heap => {
2224                if let Some(hv) = self.as_heap_ref() {
2225                    match hv {
2226                        HeapValue::String(s) => {
2227                            let mut hasher = AHasher::default();
2228                            s.hash(&mut hasher);
2229                            hasher.finish()
2230                        }
2231                        HeapValue::BigInt(i) => {
2232                            let mut hasher = AHasher::default();
2233                            i.hash(&mut hasher);
2234                            hasher.finish()
2235                        }
2236                        HeapValue::Decimal(d) => {
2237                            let mut hasher = AHasher::default();
2238                            d.mantissa().hash(&mut hasher);
2239                            d.scale().hash(&mut hasher);
2240                            hasher.finish()
2241                        }
2242                        HeapValue::NativeScalar(v) => {
2243                            let mut hasher = AHasher::default();
2244                            v.type_name().hash(&mut hasher);
2245                            v.to_string().hash(&mut hasher);
2246                            hasher.finish()
2247                        }
2248                        HeapValue::NativeView(v) => {
2249                            let mut hasher = AHasher::default();
2250                            v.ptr.hash(&mut hasher);
2251                            v.layout.name.hash(&mut hasher);
2252                            v.mutable.hash(&mut hasher);
2253                            hasher.finish()
2254                        }
2255                        _ => {
2256                            let mut hasher = AHasher::default();
2257                            self.0.hash(&mut hasher);
2258                            hasher.finish()
2259                        }
2260                    }
2261                } else {
2262                    let mut hasher = AHasher::default();
2263                    self.0.hash(&mut hasher);
2264                    hasher.finish()
2265                }
2266            }
2267            _ => {
2268                let mut hasher = AHasher::default();
2269                self.0.hash(&mut hasher);
2270                hasher.finish()
2271            }
2272        }
2273    }
2274
2275    // ===== Arithmetic helpers (operate directly on bits, no conversion) =====
2276
2277    /// Add two inline f64 values.
2278    ///
2279    /// # Safety
2280    /// Both `a` and `b` must be inline f64 values (`is_f64()` is true).
2281    #[inline(always)]
2282    pub unsafe fn add_f64(a: &Self, b: &Self) -> Self {
2283        debug_assert!(a.is_f64() && b.is_f64());
2284        let lhs = unsafe { a.as_f64_unchecked() };
2285        let rhs = unsafe { b.as_f64_unchecked() };
2286        Self::from_f64(lhs + rhs)
2287    }
2288
2289    /// Add two inline i48 values with overflow promotion to f64.
2290    ///
2291    /// # Safety
2292    /// Both `a` and `b` must be inline i48 values (`is_i64()` is true).
2293    #[inline(always)]
2294    pub unsafe fn add_i64(a: &Self, b: &Self) -> Self {
2295        debug_assert!(a.is_i64() && b.is_i64());
2296        let lhs = unsafe { a.as_i64_unchecked() };
2297        let rhs = unsafe { b.as_i64_unchecked() };
2298        match lhs.checked_add(rhs) {
2299            Some(result) if result >= I48_MIN && result <= I48_MAX => Self::from_i64(result),
2300            _ => Self::from_f64(lhs as f64 + rhs as f64),
2301        }
2302    }
2303
2304    /// Subtract two inline f64 values.
2305    ///
2306    /// # Safety
2307    /// Both `a` and `b` must be inline f64 values.
2308    #[inline(always)]
2309    pub unsafe fn sub_f64(a: &Self, b: &Self) -> Self {
2310        debug_assert!(a.is_f64() && b.is_f64());
2311        let lhs = unsafe { a.as_f64_unchecked() };
2312        let rhs = unsafe { b.as_f64_unchecked() };
2313        Self::from_f64(lhs - rhs)
2314    }
2315
2316    /// Subtract two inline i48 values with overflow promotion to f64.
2317    ///
2318    /// # Safety
2319    /// Both `a` and `b` must be inline i48 values.
2320    #[inline(always)]
2321    pub unsafe fn sub_i64(a: &Self, b: &Self) -> Self {
2322        debug_assert!(a.is_i64() && b.is_i64());
2323        let lhs = unsafe { a.as_i64_unchecked() };
2324        let rhs = unsafe { b.as_i64_unchecked() };
2325        match lhs.checked_sub(rhs) {
2326            Some(result) if result >= I48_MIN && result <= I48_MAX => Self::from_i64(result),
2327            _ => Self::from_f64(lhs as f64 - rhs as f64),
2328        }
2329    }
2330
2331    /// Multiply two inline f64 values.
2332    ///
2333    /// # Safety
2334    /// Both `a` and `b` must be inline f64 values.
2335    #[inline(always)]
2336    pub unsafe fn mul_f64(a: &Self, b: &Self) -> Self {
2337        debug_assert!(a.is_f64() && b.is_f64());
2338        let lhs = unsafe { a.as_f64_unchecked() };
2339        let rhs = unsafe { b.as_f64_unchecked() };
2340        Self::from_f64(lhs * rhs)
2341    }
2342
2343    /// Multiply two inline i48 values with overflow promotion to f64.
2344    ///
2345    /// # Safety
2346    /// Both `a` and `b` must be inline i48 values.
2347    #[inline(always)]
2348    pub unsafe fn mul_i64(a: &Self, b: &Self) -> Self {
2349        debug_assert!(a.is_i64() && b.is_i64());
2350        let lhs = unsafe { a.as_i64_unchecked() };
2351        let rhs = unsafe { b.as_i64_unchecked() };
2352        match lhs.checked_mul(rhs) {
2353            Some(result) if result >= I48_MIN && result <= I48_MAX => Self::from_i64(result),
2354            _ => Self::from_f64(lhs as f64 * rhs as f64),
2355        }
2356    }
2357
2358    /// Divide two inline f64 values.
2359    ///
2360    /// # Safety
2361    /// Both `a` and `b` must be inline f64 values.
2362    #[inline(always)]
2363    pub unsafe fn div_f64(a: &Self, b: &Self) -> Self {
2364        debug_assert!(a.is_f64() && b.is_f64());
2365        let lhs = unsafe { a.as_f64_unchecked() };
2366        let rhs = unsafe { b.as_f64_unchecked() };
2367        Self::from_f64(lhs / rhs)
2368    }
2369
2370    /// Binary arithmetic with integer-preserving semantics and overflow promotion.
2371    ///
2372    /// If both operands are inline I48, applies `int_op` (checked) to the i64 values.
2373    /// On overflow (None), falls back to `float_op` with the f64 coercions.
2374    /// If either operand is f64, applies `float_op` directly.
2375    /// Callers must ensure `a_num` and `b_num` are the `as_number_coerce()` results
2376    /// from the same `a`/`b` operands.
2377    #[inline(always)]
2378    pub fn binary_int_preserving(
2379        a: &Self,
2380        b: &Self,
2381        a_num: f64,
2382        b_num: f64,
2383        int_op: impl FnOnce(i64, i64) -> Option<i64>,
2384        float_op: impl FnOnce(f64, f64) -> f64,
2385    ) -> Self {
2386        if matches!(a.tag(), NanTag::I48) && matches!(b.tag(), NanTag::I48) {
2387            match int_op(unsafe { a.as_i64_unchecked() }, unsafe {
2388                b.as_i64_unchecked()
2389            }) {
2390                Some(result) => Self::from_i64(result),
2391                None => Self::from_f64(float_op(a_num, b_num)),
2392            }
2393        } else {
2394            Self::from_f64(float_op(a_num, b_num))
2395        }
2396    }
2397
2398    /// Compare two inline i48 values (greater than), returning a ValueWord bool.
2399    ///
2400    /// # Safety
2401    /// Both `a` and `b` must be inline i48 values.
2402    #[inline(always)]
2403    pub unsafe fn gt_i64(a: &Self, b: &Self) -> Self {
2404        debug_assert!(a.is_i64() && b.is_i64());
2405        let lhs = unsafe { a.as_i64_unchecked() };
2406        let rhs = unsafe { b.as_i64_unchecked() };
2407        Self::from_bool(lhs > rhs)
2408    }
2409
2410    /// Compare two inline i48 values (less than), returning a ValueWord bool.
2411    ///
2412    /// # Safety
2413    /// Both `a` and `b` must be inline i48 values.
2414    #[inline(always)]
2415    pub unsafe fn lt_i64(a: &Self, b: &Self) -> Self {
2416        debug_assert!(a.is_i64() && b.is_i64());
2417        let lhs = unsafe { a.as_i64_unchecked() };
2418        let rhs = unsafe { b.as_i64_unchecked() };
2419        Self::from_bool(lhs < rhs)
2420    }
2421
2422    /// Returns the raw u64 bits (for debugging/testing).
2423    #[inline(always)]
2424    pub fn raw_bits(&self) -> u64 {
2425        self.0
2426    }
2427
2428    /// Get the type name of this value.
2429    #[inline]
2430    pub fn type_name(&self) -> &'static str {
2431        if !is_tagged(self.0) {
2432            return "number";
2433        }
2434        let tag = get_tag(self.0);
2435        if tag == TAG_HEAP {
2436            let ptr = get_payload(self.0) as *const HeapValue;
2437            return unsafe { (*ptr).type_name() };
2438        }
2439        nan_tag_type_name(tag)
2440    }
2441
2442    // ===== Convenience aliases for common extraction patterns =====
2443
2444    /// Extract as f64, coercing i48 to f64 if needed.
2445    /// Alias for `as_number_coerce()` — convenience method.
2446    #[inline]
2447    pub fn to_number(&self) -> Option<f64> {
2448        self.as_number_coerce()
2449    }
2450
2451    /// Extract as bool.
2452    /// Alias for `as_bool()` — convenience method.
2453    #[inline]
2454    pub fn to_bool(&self) -> Option<bool> {
2455        self.as_bool()
2456    }
2457
2458    /// Convert Int or Number to usize (for indexing operations).
2459    pub fn as_usize(&self) -> Option<usize> {
2460        if let Some(i) = self.as_i64() {
2461            if i >= 0 {
2462                return Some(i as usize);
2463            }
2464        } else if let Some(n) = self.as_f64() {
2465            if n >= 0.0 && n.is_finite() {
2466                return Some(n as usize);
2467            }
2468        } else if let Some(d) = self.as_decimal() {
2469            use rust_decimal::prelude::ToPrimitive;
2470            if let Some(n) = d.to_f64() {
2471                if n >= 0.0 {
2472                    return Some(n as usize);
2473                }
2474            }
2475        } else if let Some(view) = self.as_native_view() {
2476            return Some(view.ptr);
2477        }
2478        None
2479    }
2480
2481    /// Convert this value to a JSON value for serialization.
2482    pub fn to_json_value(&self) -> serde_json::Value {
2483        use crate::heap_value::HeapValue;
2484        if let Some(n) = self.as_f64() {
2485            return serde_json::json!(n);
2486        }
2487        if let Some(i) = self.as_i64() {
2488            return serde_json::json!(i);
2489        }
2490        if let Some(b) = self.as_bool() {
2491            return serde_json::json!(b);
2492        }
2493        if self.is_none() || self.is_unit() {
2494            return serde_json::Value::Null;
2495        }
2496        if self.is_function() || self.is_module_function() {
2497            return serde_json::json!(format!("<{}>", self.type_name()));
2498        }
2499        match self.as_heap_ref() {
2500            Some(HeapValue::String(s)) => serde_json::json!(s.as_str()),
2501            Some(HeapValue::Decimal(d)) => {
2502                use rust_decimal::prelude::ToPrimitive;
2503                if let Some(f) = d.to_f64() {
2504                    serde_json::json!(f)
2505                } else {
2506                    serde_json::json!(d.to_string())
2507                }
2508            }
2509            Some(HeapValue::Array(arr)) => {
2510                let values: Vec<serde_json::Value> =
2511                    arr.iter().map(|v| v.to_json_value()).collect();
2512                serde_json::json!(values)
2513            }
2514            Some(HeapValue::Some(v)) => v.to_json_value(),
2515            Some(HeapValue::Ok(v)) => serde_json::json!({
2516                "status": "ok",
2517                "value": v.to_json_value()
2518            }),
2519            Some(HeapValue::Err(v)) => serde_json::json!({
2520                "status": "error",
2521                "value": v.to_json_value()
2522            }),
2523            Some(HeapValue::DataTable(dt)) => serde_json::json!({
2524                "type": "datatable",
2525                "rows": dt.row_count(),
2526                "columns": dt.column_names(),
2527            }),
2528            Some(HeapValue::TypedTable { table, schema_id }) => serde_json::json!({
2529                "type": "typed_table",
2530                "schema_id": schema_id,
2531                "rows": table.row_count(),
2532                "columns": table.column_names(),
2533            }),
2534            Some(HeapValue::RowView {
2535                schema_id, row_idx, ..
2536            }) => serde_json::json!({
2537                "type": "row",
2538                "schema_id": schema_id,
2539                "row_idx": row_idx,
2540            }),
2541            Some(HeapValue::ColumnRef {
2542                schema_id, col_id, ..
2543            }) => serde_json::json!({
2544                "type": "column_ref",
2545                "schema_id": schema_id,
2546                "col_id": col_id,
2547            }),
2548            Some(HeapValue::IndexedTable {
2549                schema_id,
2550                table,
2551                index_col,
2552            }) => serde_json::json!({
2553                "type": "indexed_table",
2554                "schema_id": schema_id,
2555                "rows": table.row_count(),
2556                "columns": table.column_names(),
2557                "index_col": index_col,
2558            }),
2559            Some(HeapValue::HashMap(d)) => {
2560                let mut map = serde_json::Map::new();
2561                for (k, v) in d.keys.iter().zip(d.values.iter()) {
2562                    map.insert(format!("{}", k), v.to_json_value());
2563                }
2564                serde_json::Value::Object(map)
2565            }
2566            Some(HeapValue::Set(d)) => {
2567                serde_json::Value::Array(d.items.iter().map(|v| v.to_json_value()).collect())
2568            }
2569            Some(HeapValue::Deque(d)) => {
2570                serde_json::Value::Array(d.items.iter().map(|v| v.to_json_value()).collect())
2571            }
2572            Some(HeapValue::PriorityQueue(d)) => {
2573                serde_json::Value::Array(d.items.iter().map(|v| v.to_json_value()).collect())
2574            }
2575            Some(HeapValue::NativeScalar(v)) => match v {
2576                NativeScalar::I8(n) => serde_json::json!({ "type": "i8", "value": n }),
2577                NativeScalar::U8(n) => serde_json::json!({ "type": "u8", "value": n }),
2578                NativeScalar::I16(n) => serde_json::json!({ "type": "i16", "value": n }),
2579                NativeScalar::U16(n) => serde_json::json!({ "type": "u16", "value": n }),
2580                NativeScalar::I32(n) => serde_json::json!({ "type": "i32", "value": n }),
2581                NativeScalar::U32(n) => serde_json::json!({ "type": "u32", "value": n }),
2582                NativeScalar::I64(n) => {
2583                    serde_json::json!({ "type": "i64", "value": n.to_string() })
2584                }
2585                NativeScalar::U64(n) => {
2586                    serde_json::json!({ "type": "u64", "value": n.to_string() })
2587                }
2588                NativeScalar::Isize(n) => {
2589                    serde_json::json!({ "type": "isize", "value": n.to_string() })
2590                }
2591                NativeScalar::Usize(n) => {
2592                    serde_json::json!({ "type": "usize", "value": n.to_string() })
2593                }
2594                NativeScalar::Ptr(n) => {
2595                    serde_json::json!({ "type": "ptr", "value": format!("0x{n:x}") })
2596                }
2597                NativeScalar::F32(n) => serde_json::json!({ "type": "f32", "value": n }),
2598            },
2599            Some(HeapValue::NativeView(v)) => serde_json::json!({
2600                "type": if v.mutable { "cmut" } else { "cview" },
2601                "layout": v.layout.name,
2602                "ptr": v.ptr,
2603            }),
2604            // Typed arrays — serialize as JSON arrays of their element values
2605            Some(HeapValue::IntArray(buf)) => {
2606                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2607            }
2608            Some(HeapValue::FloatArray(buf)) => {
2609                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2610            }
2611            Some(HeapValue::BoolArray(buf)) => serde_json::Value::Array(
2612                buf.data
2613                    .iter()
2614                    .map(|&v| serde_json::json!(v != 0))
2615                    .collect(),
2616            ),
2617            Some(HeapValue::I8Array(buf)) => {
2618                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2619            }
2620            Some(HeapValue::I16Array(buf)) => {
2621                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2622            }
2623            Some(HeapValue::I32Array(buf)) => {
2624                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2625            }
2626            Some(HeapValue::U8Array(buf)) => {
2627                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2628            }
2629            Some(HeapValue::U16Array(buf)) => {
2630                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2631            }
2632            Some(HeapValue::U32Array(buf)) => {
2633                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2634            }
2635            Some(HeapValue::U64Array(buf)) => {
2636                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2637            }
2638            Some(HeapValue::F32Array(buf)) => {
2639                serde_json::Value::Array(buf.data.iter().map(|&v| serde_json::json!(v)).collect())
2640            }
2641            _ => serde_json::json!(format!("<{}>", self.type_name())),
2642        }
2643    }
2644}
2645
2646// --- PartialEq implementation ---
2647//
2648// Compares ValueWord values by semantic equality:
2649// - Inline types: bit-exact comparison (f64 NaN != NaN, matching IEEE 754)
2650// - Heap types: delegates to HeapValue::partial_eq
2651
2652impl PartialEq for ValueWord {
2653    fn eq(&self, other: &Self) -> bool {
2654        // Bit-identical covers all inline types and same-Arc heap pointers.
2655        if self.0 == other.0 {
2656            return true;
2657        }
2658        // If both are heap-tagged, compare the underlying HeapValues structurally.
2659        if is_tagged(self.0)
2660            && get_tag(self.0) == TAG_HEAP
2661            && is_tagged(other.0)
2662            && get_tag(other.0) == TAG_HEAP
2663        {
2664            match (self.as_heap_ref(), other.as_heap_ref()) {
2665                (Some(a), Some(b)) => a.structural_eq(b),
2666                _ => false,
2667            }
2668        } else {
2669            false
2670        }
2671    }
2672}
2673
2674impl Eq for ValueWord {}
2675
2676// --- Clone implementation ---
2677
2678#[cfg(not(feature = "gc"))]
2679impl Clone for ValueWord {
2680    #[inline]
2681    fn clone(&self) -> Self {
2682        if is_tagged(self.0) && get_tag(self.0) == TAG_HEAP {
2683            // Bump the Arc refcount — no allocation, no deep clone.
2684            let ptr = get_payload(self.0) as *const HeapValue;
2685            unsafe { Arc::increment_strong_count(ptr) };
2686            Self(self.0)
2687        } else {
2688            // Non-heap values: just copy the bits.
2689            Self(self.0)
2690        }
2691    }
2692}
2693
2694#[cfg(feature = "gc")]
2695impl Clone for ValueWord {
2696    #[inline]
2697    fn clone(&self) -> Self {
2698        // GC path: bitwise copy. No refcount manipulation needed —
2699        // the GC traces live pointers to determine liveness.
2700        Self(self.0)
2701    }
2702}
2703
2704// --- Drop implementation ---
2705
2706#[cfg(not(feature = "gc"))]
2707impl Drop for ValueWord {
2708    fn drop(&mut self) {
2709        if is_tagged(self.0) && get_tag(self.0) == TAG_HEAP {
2710            let ptr = get_payload(self.0) as *const HeapValue;
2711            if !ptr.is_null() {
2712                unsafe {
2713                    // Decrement the Arc refcount; drops HeapValue when it reaches zero.
2714                    Arc::decrement_strong_count(ptr);
2715                }
2716            }
2717        }
2718    }
2719}
2720
2721#[cfg(feature = "gc")]
2722impl Drop for ValueWord {
2723    #[inline]
2724    fn drop(&mut self) {
2725        // GC path: no-op. The garbage collector handles deallocation
2726        // by tracing live objects and reclaiming dead ones.
2727    }
2728}
2729
2730impl std::fmt::Display for ValueWord {
2731    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2732        if self.is_f64() {
2733            let n = unsafe { self.as_f64_unchecked() };
2734            if n == n.trunc() && n.abs() < 1e15 {
2735                write!(f, "{}.0", n as i64)
2736            } else {
2737                write!(f, "{}", n)
2738            }
2739        } else if self.is_i64() {
2740            write!(f, "{}", unsafe { self.as_i64_unchecked() })
2741        } else if self.is_bool() {
2742            write!(f, "{}", unsafe { self.as_bool_unchecked() })
2743        } else if self.is_none() {
2744            write!(f, "none")
2745        } else if self.is_unit() {
2746            write!(f, "()")
2747        } else if self.is_function() {
2748            write!(f, "<function:{}>", unsafe { self.as_function_unchecked() })
2749        } else if self.is_module_function() {
2750            write!(f, "<module_function>")
2751        } else if let Some(target) = self.as_ref_target() {
2752            match target {
2753                RefTarget::Stack(slot) => write!(f, "&slot_{}", slot),
2754                RefTarget::ModuleBinding(slot) => write!(f, "&module_{}", slot),
2755                RefTarget::Projected(_) => write!(f, "&ref"),
2756            }
2757        } else if let Some(hv) = self.as_heap_ref() {
2758            match hv {
2759                HeapValue::Char(c) => write!(f, "{}", c),
2760                HeapValue::String(s) => write!(f, "{}", s),
2761                HeapValue::Array(arr) => {
2762                    write!(f, "[")?;
2763                    for (i, elem) in arr.iter().enumerate() {
2764                        if i > 0 {
2765                            write!(f, ", ")?;
2766                        }
2767                        write!(f, "{}", elem)?;
2768                    }
2769                    write!(f, "]")
2770                }
2771                HeapValue::TypedObject { .. } => write!(f, "{{...}}"),
2772                HeapValue::Closure { function_id, .. } => write!(f, "<closure:{}>", function_id),
2773                HeapValue::Decimal(d) => write!(f, "{}", d),
2774                HeapValue::BigInt(i) => write!(f, "{}", i),
2775                HeapValue::HostClosure(_) => write!(f, "<host_closure>"),
2776                HeapValue::DataTable(dt) => {
2777                    write!(f, "<datatable:{}x{}>", dt.row_count(), dt.column_count())
2778                }
2779                HeapValue::TypedTable { table, .. } => write!(
2780                    f,
2781                    "<typed_table:{}x{}>",
2782                    table.row_count(),
2783                    table.column_count()
2784                ),
2785                HeapValue::RowView { row_idx, .. } => write!(f, "<row:{}>", row_idx),
2786                HeapValue::ColumnRef { col_id, .. } => write!(f, "<column:{}>", col_id),
2787                HeapValue::IndexedTable { table, .. } => write!(
2788                    f,
2789                    "<indexed_table:{}x{}>",
2790                    table.row_count(),
2791                    table.column_count()
2792                ),
2793                HeapValue::HashMap(d) => {
2794                    write!(f, "HashMap{{")?;
2795                    for (i, (k, v)) in d.keys.iter().zip(d.values.iter()).enumerate() {
2796                        if i > 0 {
2797                            write!(f, ", ")?;
2798                        }
2799                        write!(f, "{}: {}", k, v)?;
2800                    }
2801                    write!(f, "}}")
2802                }
2803                HeapValue::Set(d) => {
2804                    write!(f, "Set{{")?;
2805                    for (i, item) in d.items.iter().enumerate() {
2806                        if i > 0 {
2807                            write!(f, ", ")?;
2808                        }
2809                        write!(f, "{}", item)?;
2810                    }
2811                    write!(f, "}}")
2812                }
2813                HeapValue::Deque(d) => {
2814                    write!(f, "Deque[")?;
2815                    for (i, item) in d.items.iter().enumerate() {
2816                        if i > 0 {
2817                            write!(f, ", ")?;
2818                        }
2819                        write!(f, "{}", item)?;
2820                    }
2821                    write!(f, "]")
2822                }
2823                HeapValue::PriorityQueue(d) => {
2824                    write!(f, "PriorityQueue[")?;
2825                    for (i, item) in d.items.iter().enumerate() {
2826                        if i > 0 {
2827                            write!(f, ", ")?;
2828                        }
2829                        write!(f, "{}", item)?;
2830                    }
2831                    write!(f, "]")
2832                }
2833                HeapValue::Content(node) => write!(f, "{}", node),
2834                HeapValue::Instant(t) => write!(f, "<instant:{:?}>", t.elapsed()),
2835                HeapValue::IoHandle(data) => {
2836                    let status = if data.is_open() { "open" } else { "closed" };
2837                    write!(f, "<io_handle:{}:{}>", data.path, status)
2838                }
2839                HeapValue::Range {
2840                    start,
2841                    end,
2842                    inclusive,
2843                } => {
2844                    if let Some(s) = start {
2845                        write!(f, "{}", s)?;
2846                    }
2847                    write!(f, "{}", if *inclusive { "..=" } else { ".." })?;
2848                    if let Some(e) = end {
2849                        write!(f, "{}", e)?;
2850                    }
2851                    std::fmt::Result::Ok(())
2852                }
2853                HeapValue::Enum(e) => {
2854                    write!(f, "{}", e.variant)?;
2855                    match &e.payload {
2856                        crate::enums::EnumPayload::Unit => Ok(()),
2857                        crate::enums::EnumPayload::Tuple(values) => {
2858                            write!(f, "(")?;
2859                            for (i, v) in values.iter().enumerate() {
2860                                if i > 0 {
2861                                    write!(f, ", ")?;
2862                                }
2863                                write!(f, "{}", v)?;
2864                            }
2865                            write!(f, ")")
2866                        }
2867                        crate::enums::EnumPayload::Struct(fields) => {
2868                            let mut pairs: Vec<_> = fields.iter().collect();
2869                            pairs.sort_by_key(|(k, _)| (*k).clone());
2870                            write!(f, " {{ ")?;
2871                            for (i, (k, v)) in pairs.iter().enumerate() {
2872                                if i > 0 {
2873                                    write!(f, ", ")?;
2874                                }
2875                                write!(f, "{}: {}", k, v)?;
2876                            }
2877                            write!(f, " }}")
2878                        }
2879                    }
2880                }
2881                HeapValue::Some(v) => write!(f, "some({})", v),
2882                HeapValue::Ok(v) => write!(f, "ok({})", v),
2883                HeapValue::Err(v) => write!(f, "err({})", v),
2884                HeapValue::Future(id) => write!(f, "<future:{}>", id),
2885                HeapValue::TaskGroup { task_ids, .. } => {
2886                    write!(f, "<task_group:{}>", task_ids.len())
2887                }
2888                HeapValue::TraitObject { value, .. } => write!(f, "{}", value),
2889                HeapValue::ExprProxy(name) => write!(f, "<expr:{}>", name),
2890                HeapValue::FilterExpr(_) => write!(f, "<filter_expr>"),
2891                HeapValue::Time(t) => write!(f, "{}", t),
2892                HeapValue::Duration(d) => write!(f, "{:?}", d),
2893                HeapValue::TimeSpan(ts) => write!(f, "{}", ts),
2894                HeapValue::Timeframe(tf) => write!(f, "{:?}", tf),
2895                HeapValue::TimeReference(_) => write!(f, "<time_ref>"),
2896                HeapValue::DateTimeExpr(_) => write!(f, "<datetime_expr>"),
2897                HeapValue::DataDateTimeRef(_) => write!(f, "<data_datetime_ref>"),
2898                HeapValue::TypeAnnotation(_) => write!(f, "<type_annotation>"),
2899                HeapValue::TypeAnnotatedValue { type_name, value } => {
2900                    write!(f, "{}({})", type_name, value)
2901                }
2902                HeapValue::PrintResult(_) => write!(f, "<print_result>"),
2903                HeapValue::SimulationCall(data) => write!(f, "<simulation:{}>", data.name),
2904                HeapValue::FunctionRef { name, .. } => write!(f, "<fn:{}>", name),
2905                HeapValue::ProjectedRef(_) => write!(f, "&ref"),
2906                HeapValue::DataReference(data) => write!(f, "<data:{}>", data.id),
2907                HeapValue::NativeScalar(v) => write!(f, "{v}"),
2908                HeapValue::NativeView(v) => write!(
2909                    f,
2910                    "<{}:{}@0x{:x}>",
2911                    if v.mutable { "cmut" } else { "cview" },
2912                    v.layout.name,
2913                    v.ptr
2914                ),
2915                HeapValue::SharedCell(arc) => write!(f, "{}", arc.read().unwrap()),
2916                HeapValue::IntArray(a) => {
2917                    write!(f, "Vec<int>[")?;
2918                    for (i, v) in a.iter().enumerate() {
2919                        if i > 0 {
2920                            write!(f, ", ")?;
2921                        }
2922                        write!(f, "{}", v)?;
2923                    }
2924                    write!(f, "]")
2925                }
2926                HeapValue::FloatArray(a) => {
2927                    write!(f, "Vec<number>[")?;
2928                    for (i, v) in a.iter().enumerate() {
2929                        if i > 0 {
2930                            write!(f, ", ")?;
2931                        }
2932                        if *v == v.trunc() && v.abs() < 1e15 {
2933                            write!(f, "{}", *v as i64)?;
2934                        } else {
2935                            write!(f, "{}", v)?;
2936                        }
2937                    }
2938                    write!(f, "]")
2939                }
2940                HeapValue::BoolArray(a) => {
2941                    write!(f, "Vec<bool>[")?;
2942                    for (i, v) in a.iter().enumerate() {
2943                        if i > 0 {
2944                            write!(f, ", ")?;
2945                        }
2946                        write!(f, "{}", *v != 0)?;
2947                    }
2948                    write!(f, "]")
2949                }
2950                HeapValue::Matrix(m) => {
2951                    write!(f, "<Mat<number>:{}x{}>", m.rows, m.cols)
2952                }
2953                HeapValue::Iterator(it) => {
2954                    write!(
2955                        f,
2956                        "<iterator:pos={},transforms={}>",
2957                        it.position,
2958                        it.transforms.len()
2959                    )
2960                }
2961                HeapValue::Generator(g) => {
2962                    write!(f, "<generator:state={}>", g.state)
2963                }
2964                HeapValue::Mutex(_) => write!(f, "<mutex>"),
2965                HeapValue::Atomic(a) => {
2966                    write!(
2967                        f,
2968                        "<atomic:{}>",
2969                        a.inner.load(std::sync::atomic::Ordering::Relaxed)
2970                    )
2971                }
2972                HeapValue::Channel(c) => {
2973                    if c.is_sender() {
2974                        write!(f, "<channel:sender>")
2975                    } else {
2976                        write!(f, "<channel:receiver>")
2977                    }
2978                }
2979                HeapValue::Lazy(l) => {
2980                    let initialized = l.value.lock().map(|g| g.is_some()).unwrap_or(false);
2981                    if initialized {
2982                        write!(f, "<lazy:initialized>")
2983                    } else {
2984                        write!(f, "<lazy:pending>")
2985                    }
2986                }
2987                HeapValue::I8Array(a) => {
2988                    write!(f, "Vec<i8>[")?;
2989                    for (i, v) in a.iter().enumerate() {
2990                        if i > 0 {
2991                            write!(f, ", ")?;
2992                        }
2993                        write!(f, "{}", v)?;
2994                    }
2995                    write!(f, "]")
2996                }
2997                HeapValue::I16Array(a) => {
2998                    write!(f, "Vec<i16>[")?;
2999                    for (i, v) in a.iter().enumerate() {
3000                        if i > 0 {
3001                            write!(f, ", ")?;
3002                        }
3003                        write!(f, "{}", v)?;
3004                    }
3005                    write!(f, "]")
3006                }
3007                HeapValue::I32Array(a) => {
3008                    write!(f, "Vec<i32>[")?;
3009                    for (i, v) in a.iter().enumerate() {
3010                        if i > 0 {
3011                            write!(f, ", ")?;
3012                        }
3013                        write!(f, "{}", v)?;
3014                    }
3015                    write!(f, "]")
3016                }
3017                HeapValue::U8Array(a) => {
3018                    write!(f, "Vec<u8>[")?;
3019                    for (i, v) in a.iter().enumerate() {
3020                        if i > 0 {
3021                            write!(f, ", ")?;
3022                        }
3023                        write!(f, "{}", v)?;
3024                    }
3025                    write!(f, "]")
3026                }
3027                HeapValue::U16Array(a) => {
3028                    write!(f, "Vec<u16>[")?;
3029                    for (i, v) in a.iter().enumerate() {
3030                        if i > 0 {
3031                            write!(f, ", ")?;
3032                        }
3033                        write!(f, "{}", v)?;
3034                    }
3035                    write!(f, "]")
3036                }
3037                HeapValue::U32Array(a) => {
3038                    write!(f, "Vec<u32>[")?;
3039                    for (i, v) in a.iter().enumerate() {
3040                        if i > 0 {
3041                            write!(f, ", ")?;
3042                        }
3043                        write!(f, "{}", v)?;
3044                    }
3045                    write!(f, "]")
3046                }
3047                HeapValue::U64Array(a) => {
3048                    write!(f, "Vec<u64>[")?;
3049                    for (i, v) in a.iter().enumerate() {
3050                        if i > 0 {
3051                            write!(f, ", ")?;
3052                        }
3053                        write!(f, "{}", v)?;
3054                    }
3055                    write!(f, "]")
3056                }
3057                HeapValue::F32Array(a) => {
3058                    write!(f, "Vec<f32>[")?;
3059                    for (i, v) in a.iter().enumerate() {
3060                        if i > 0 {
3061                            write!(f, ", ")?;
3062                        }
3063                        write!(f, "{}", v)?;
3064                    }
3065                    write!(f, "]")
3066                }
3067                HeapValue::FloatArraySlice {
3068                    parent,
3069                    offset,
3070                    len,
3071                } => {
3072                    let slice =
3073                        &parent.data[*offset as usize..(*offset + *len) as usize];
3074                    write!(f, "Vec<number>[")?;
3075                    for (i, v) in slice.iter().enumerate() {
3076                        if i > 0 {
3077                            write!(f, ", ")?;
3078                        }
3079                        if *v == v.trunc() && v.abs() < 1e15 {
3080                            write!(f, "{}", *v as i64)?;
3081                        } else {
3082                            write!(f, "{}", v)?;
3083                        }
3084                    }
3085                    write!(f, "]")
3086                }
3087            }
3088        } else {
3089            write!(f, "<unknown>")
3090        }
3091    }
3092}
3093
3094impl std::fmt::Debug for ValueWord {
3095    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3096        if self.is_f64() {
3097            write!(f, "ValueWord(f64: {})", unsafe { self.as_f64_unchecked() })
3098        } else if self.is_i64() {
3099            write!(f, "ValueWord(i64: {})", unsafe { self.as_i64_unchecked() })
3100        } else if self.is_bool() {
3101            write!(f, "ValueWord(bool: {})", unsafe {
3102                self.as_bool_unchecked()
3103            })
3104        } else if self.is_none() {
3105            write!(f, "ValueWord(None)")
3106        } else if self.is_unit() {
3107            write!(f, "ValueWord(Unit)")
3108        } else if self.is_function() {
3109            write!(f, "ValueWord(Function({}))", unsafe {
3110                self.as_function_unchecked()
3111            })
3112        } else if let Some(target) = self.as_ref_target() {
3113            write!(f, "ValueWord(Ref({:?}))", target)
3114        } else if self.is_heap() {
3115            let ptr = get_payload(self.0) as *const HeapValue;
3116            let hv = unsafe { &*ptr };
3117            write!(f, "ValueWord(heap: {:?})", hv)
3118        } else {
3119            write!(f, "ValueWord(0x{:016x})", self.0)
3120        }
3121    }
3122}
3123
3124#[cfg(test)]
3125mod tests {
3126    use super::*;
3127    use std::sync::Arc;
3128
3129    // ===== f64 round-trips =====
3130
3131    #[test]
3132    fn test_f64_roundtrip_positive() {
3133        let v = ValueWord::from_f64(3.14);
3134        assert!(v.is_f64());
3135        assert_eq!(v.as_f64(), Some(3.14));
3136        unsafe { assert_eq!(v.as_f64_unchecked(), 3.14) };
3137    }
3138
3139    #[test]
3140    fn test_f64_roundtrip_negative() {
3141        let v = ValueWord::from_f64(-123.456);
3142        assert!(v.is_f64());
3143        assert_eq!(v.as_f64(), Some(-123.456));
3144    }
3145
3146    #[test]
3147    fn test_f64_zero() {
3148        let v = ValueWord::from_f64(0.0);
3149        assert!(v.is_f64());
3150        assert_eq!(v.as_f64(), Some(0.0));
3151    }
3152
3153    #[test]
3154    fn test_f64_negative_zero() {
3155        let v = ValueWord::from_f64(-0.0);
3156        assert!(v.is_f64());
3157        let extracted = v.as_f64().unwrap();
3158        // -0.0 and 0.0 are equal per IEEE 754
3159        assert_eq!(extracted, 0.0);
3160        // But the sign bit should be preserved
3161        assert!(extracted.is_sign_negative());
3162    }
3163
3164    #[test]
3165    fn test_f64_infinity() {
3166        let pos = ValueWord::from_f64(f64::INFINITY);
3167        assert!(pos.is_f64());
3168        assert_eq!(pos.as_f64(), Some(f64::INFINITY));
3169
3170        let neg = ValueWord::from_f64(f64::NEG_INFINITY);
3171        assert!(neg.is_f64());
3172        assert_eq!(neg.as_f64(), Some(f64::NEG_INFINITY));
3173    }
3174
3175    #[test]
3176    fn test_f64_nan_canonicalized() {
3177        let v = ValueWord::from_f64(f64::NAN);
3178        // NaN is stored as a canonical NaN, which is still a valid f64 NaN
3179        assert!(v.is_f64());
3180        let extracted = v.as_f64().unwrap();
3181        assert!(extracted.is_nan());
3182    }
3183
3184    #[test]
3185    fn test_f64_subnormal() {
3186        let tiny = f64::MIN_POSITIVE / 2.0; // subnormal
3187        let v = ValueWord::from_f64(tiny);
3188        assert!(v.is_f64());
3189        assert_eq!(v.as_f64(), Some(tiny));
3190    }
3191
3192    #[test]
3193    fn test_f64_max_min() {
3194        let max = ValueWord::from_f64(f64::MAX);
3195        assert!(max.is_f64());
3196        assert_eq!(max.as_f64(), Some(f64::MAX));
3197
3198        let min = ValueWord::from_f64(f64::MIN);
3199        assert!(min.is_f64());
3200        assert_eq!(min.as_f64(), Some(f64::MIN));
3201    }
3202
3203    // ===== i64 round-trips =====
3204
3205    #[test]
3206    fn test_i64_small_positive() {
3207        let v = ValueWord::from_i64(42);
3208        assert!(v.is_i64());
3209        assert_eq!(v.as_i64(), Some(42));
3210        unsafe { assert_eq!(v.as_i64_unchecked(), 42) };
3211    }
3212
3213    #[test]
3214    fn test_i64_small_negative() {
3215        let v = ValueWord::from_i64(-42);
3216        assert!(v.is_i64());
3217        assert_eq!(v.as_i64(), Some(-42));
3218    }
3219
3220    #[test]
3221    fn test_i64_zero() {
3222        let v = ValueWord::from_i64(0);
3223        assert!(v.is_i64());
3224        assert_eq!(v.as_i64(), Some(0));
3225    }
3226
3227    #[test]
3228    fn test_i64_i48_max() {
3229        let max = I48_MAX;
3230        let v = ValueWord::from_i64(max);
3231        assert!(v.is_i64());
3232        assert_eq!(v.as_i64(), Some(max));
3233    }
3234
3235    #[test]
3236    fn test_i64_i48_min() {
3237        let min = I48_MIN;
3238        let v = ValueWord::from_i64(min);
3239        assert!(v.is_i64());
3240        assert_eq!(v.as_i64(), Some(min));
3241    }
3242
3243    #[test]
3244    fn test_i64_large_needs_heap() {
3245        // i64::MAX exceeds 48 bits, should heap-box as BigInt
3246        let v = ValueWord::from_i64(i64::MAX);
3247        assert!(v.is_heap());
3248        assert_eq!(v.as_i64(), Some(i64::MAX));
3249    }
3250
3251    #[test]
3252    fn test_i64_min_needs_heap() {
3253        let v = ValueWord::from_i64(i64::MIN);
3254        assert!(v.is_heap());
3255        assert_eq!(v.as_i64(), Some(i64::MIN));
3256    }
3257
3258    #[test]
3259    fn test_i64_just_outside_i48_positive() {
3260        let val = I48_MAX + 1;
3261        let v = ValueWord::from_i64(val);
3262        assert!(v.is_heap());
3263        assert_eq!(v.as_i64(), Some(val));
3264    }
3265
3266    #[test]
3267    fn test_i64_just_outside_i48_negative() {
3268        let val = I48_MIN - 1;
3269        let v = ValueWord::from_i64(val);
3270        assert!(v.is_heap());
3271        assert_eq!(v.as_i64(), Some(val));
3272    }
3273
3274    // ===== bool round-trips =====
3275
3276    #[test]
3277    fn test_bool_true() {
3278        let v = ValueWord::from_bool(true);
3279        assert!(v.is_bool());
3280        assert_eq!(v.as_bool(), Some(true));
3281        unsafe { assert_eq!(v.as_bool_unchecked(), true) };
3282    }
3283
3284    #[test]
3285    fn test_bool_false() {
3286        let v = ValueWord::from_bool(false);
3287        assert!(v.is_bool());
3288        assert_eq!(v.as_bool(), Some(false));
3289        unsafe { assert_eq!(v.as_bool_unchecked(), false) };
3290    }
3291
3292    // ===== None / Unit =====
3293
3294    #[test]
3295    fn test_none() {
3296        let v = ValueWord::none();
3297        assert!(v.is_none());
3298        assert!(!v.is_f64());
3299        assert!(!v.is_i64());
3300        assert!(!v.is_bool());
3301    }
3302
3303    #[test]
3304    fn test_unit() {
3305        let v = ValueWord::unit();
3306        assert!(v.is_unit());
3307    }
3308
3309    // ===== Function =====
3310
3311    #[test]
3312    fn test_function() {
3313        let v = ValueWord::from_function(42);
3314        assert!(v.is_function());
3315        assert_eq!(v.as_function(), Some(42));
3316        unsafe { assert_eq!(v.as_function_unchecked(), 42) };
3317    }
3318
3319    #[test]
3320    fn test_function_max_id() {
3321        let v = ValueWord::from_function(u16::MAX);
3322        assert!(v.is_function());
3323        assert_eq!(v.as_function(), Some(u16::MAX));
3324    }
3325
3326    // ===== Module function =====
3327
3328    #[test]
3329    fn test_module_function() {
3330        let v = ValueWord::from_module_function(99);
3331        assert!(is_tagged(v.0));
3332        assert_eq!(get_tag(v.0), TAG_MODULE_FN);
3333        assert_eq!(v.as_module_function(), Some(99));
3334    }
3335
3336    // ===== Heap round-trips =====
3337
3338    #[test]
3339    fn test_heap_string_roundtrip() {
3340        let v = ValueWord::from_string(Arc::new("hello world".to_string()));
3341        assert!(v.is_heap());
3342        assert_eq!(v.as_arc_string().map(|s| s.as_str()), Some("hello world"));
3343    }
3344
3345    #[test]
3346    fn test_heap_array_roundtrip() {
3347        let arr = Arc::new(vec![
3348            ValueWord::from_i64(1),
3349            ValueWord::from_i64(2),
3350            ValueWord::from_i64(3),
3351        ]);
3352        let v = ValueWord::from_array(arr.clone());
3353        assert!(v.is_heap());
3354        let extracted = v.as_array().expect("should be array");
3355        assert_eq!(extracted.len(), 3);
3356    }
3357
3358    #[test]
3359    fn test_heap_clone() {
3360        let v = ValueWord::from_string(Arc::new("clone me".to_string()));
3361        let cloned = v.clone();
3362
3363        // Both should extract the same string
3364        assert_eq!(v.as_arc_string().map(|s| s.as_str()), Some("clone me"));
3365        assert_eq!(cloned.as_arc_string().map(|s| s.as_str()), Some("clone me"));
3366
3367        // Clones share the same Arc allocation (refcount bump, not deep copy)
3368        assert_eq!(
3369            get_payload(v.0),
3370            get_payload(cloned.0),
3371            "cloned heap pointers should be identical (Arc shared)"
3372        );
3373    }
3374
3375    // ===== Arithmetic helpers =====
3376
3377    #[test]
3378    fn test_add_f64() {
3379        let a = ValueWord::from_f64(1.5);
3380        let b = ValueWord::from_f64(2.5);
3381        let result = unsafe { ValueWord::add_f64(&a, &b) };
3382        assert_eq!(result.as_f64(), Some(4.0));
3383    }
3384
3385    #[test]
3386    fn test_add_i64() {
3387        let a = ValueWord::from_i64(100);
3388        let b = ValueWord::from_i64(200);
3389        let result = unsafe { ValueWord::add_i64(&a, &b) };
3390        assert_eq!(result.as_i64(), Some(300));
3391    }
3392
3393    #[test]
3394    fn test_add_i64_negative() {
3395        let a = ValueWord::from_i64(-50);
3396        let b = ValueWord::from_i64(30);
3397        let result = unsafe { ValueWord::add_i64(&a, &b) };
3398        assert_eq!(result.as_i64(), Some(-20));
3399    }
3400
3401    #[test]
3402    fn test_sub_f64() {
3403        let a = ValueWord::from_f64(10.0);
3404        let b = ValueWord::from_f64(3.0);
3405        let result = unsafe { ValueWord::sub_f64(&a, &b) };
3406        assert_eq!(result.as_f64(), Some(7.0));
3407    }
3408
3409    #[test]
3410    fn test_sub_i64() {
3411        let a = ValueWord::from_i64(50);
3412        let b = ValueWord::from_i64(80);
3413        let result = unsafe { ValueWord::sub_i64(&a, &b) };
3414        assert_eq!(result.as_i64(), Some(-30));
3415    }
3416
3417    #[test]
3418    fn test_mul_f64() {
3419        let a = ValueWord::from_f64(3.0);
3420        let b = ValueWord::from_f64(4.0);
3421        let result = unsafe { ValueWord::mul_f64(&a, &b) };
3422        assert_eq!(result.as_f64(), Some(12.0));
3423    }
3424
3425    #[test]
3426    fn test_mul_i64() {
3427        let a = ValueWord::from_i64(7);
3428        let b = ValueWord::from_i64(-6);
3429        let result = unsafe { ValueWord::mul_i64(&a, &b) };
3430        assert_eq!(result.as_i64(), Some(-42));
3431    }
3432
3433    #[test]
3434    fn test_div_f64() {
3435        let a = ValueWord::from_f64(10.0);
3436        let b = ValueWord::from_f64(4.0);
3437        let result = unsafe { ValueWord::div_f64(&a, &b) };
3438        assert_eq!(result.as_f64(), Some(2.5));
3439    }
3440
3441    #[test]
3442    fn test_gt_i64() {
3443        let a = ValueWord::from_i64(10);
3444        let b = ValueWord::from_i64(5);
3445        let result = unsafe { ValueWord::gt_i64(&a, &b) };
3446        assert_eq!(result.as_bool(), Some(true));
3447
3448        let result2 = unsafe { ValueWord::gt_i64(&b, &a) };
3449        assert_eq!(result2.as_bool(), Some(false));
3450    }
3451
3452    #[test]
3453    fn test_lt_i64() {
3454        let a = ValueWord::from_i64(3);
3455        let b = ValueWord::from_i64(7);
3456        let result = unsafe { ValueWord::lt_i64(&a, &b) };
3457        assert_eq!(result.as_bool(), Some(true));
3458
3459        let result2 = unsafe { ValueWord::lt_i64(&b, &a) };
3460        assert_eq!(result2.as_bool(), Some(false));
3461    }
3462
3463    // ===== i64 overflow in arithmetic =====
3464
3465    #[test]
3466    fn test_add_i64_overflow_to_heap() {
3467        // Adding two values near i48 max that overflow to > i48 range
3468        // promotes to f64 (V8 SMI semantics) instead of heap-boxing as BigInt
3469        let a = ValueWord::from_i64(I48_MAX);
3470        let b = ValueWord::from_i64(1);
3471        let result = unsafe { ValueWord::add_i64(&a, &b) };
3472        assert!(result.is_f64());
3473        let expected = (I48_MAX + 1) as f64;
3474        assert_eq!(result.as_f64(), Some(expected));
3475    }
3476
3477    // ===== Type discrimination =====
3478
3479    #[test]
3480    fn test_type_checks_exclusive() {
3481        let f = ValueWord::from_f64(1.0);
3482        assert!(f.is_f64());
3483        assert!(!f.is_i64());
3484        assert!(!f.is_bool());
3485        assert!(!f.is_none());
3486        assert!(!f.is_unit());
3487        assert!(!f.is_function());
3488        assert!(!f.is_heap());
3489
3490        let i = ValueWord::from_i64(1);
3491        assert!(!i.is_f64());
3492        assert!(i.is_i64());
3493        assert!(!i.is_bool());
3494        assert!(!i.is_none());
3495        assert!(!i.is_unit());
3496        assert!(!i.is_function());
3497        assert!(!i.is_heap());
3498
3499        let b = ValueWord::from_bool(true);
3500        assert!(!b.is_f64());
3501        assert!(!b.is_i64());
3502        assert!(b.is_bool());
3503        assert!(!b.is_none());
3504
3505        let n = ValueWord::none();
3506        assert!(!n.is_f64());
3507        assert!(!n.is_i64());
3508        assert!(!n.is_bool());
3509        assert!(n.is_none());
3510
3511        let u = ValueWord::unit();
3512        assert!(u.is_unit());
3513        assert!(!u.is_none());
3514
3515        let func = ValueWord::from_function(0);
3516        assert!(func.is_function());
3517        assert!(!func.is_f64());
3518    }
3519
3520    // ===== Size check =====
3521
3522    #[test]
3523    fn test_size_is_8_bytes() {
3524        assert_eq!(std::mem::size_of::<ValueWord>(), 8);
3525    }
3526
3527    #[test]
3528    fn test_heap_value_size() {
3529        use crate::heap_value::HeapValue;
3530        let hv_size = std::mem::size_of::<HeapValue>();
3531        // Largest payload is TypedObject (32 bytes) or FunctionRef (String 24 + Option<Box> 8 = 32),
3532        // plus discriminant → ~40 bytes. Allow up to 48 for alignment padding.
3533        assert!(
3534            hv_size <= 48,
3535            "HeapValue grew beyond expected 48 bytes: {} bytes",
3536            hv_size
3537        );
3538    }
3539
3540    // ===== Debug output =====
3541
3542    #[test]
3543    fn test_debug_format() {
3544        let v = ValueWord::from_f64(3.14);
3545        let dbg = format!("{:?}", v);
3546        assert!(dbg.contains("f64"));
3547        assert!(dbg.contains("3.14"));
3548
3549        let v = ValueWord::from_i64(42);
3550        let dbg = format!("{:?}", v);
3551        assert!(dbg.contains("i64"));
3552        assert!(dbg.contains("42"));
3553
3554        let v = ValueWord::none();
3555        let dbg = format!("{:?}", v);
3556        assert!(dbg.contains("None"));
3557    }
3558
3559    // ===== Edge: sign extension correctness =====
3560
3561    #[test]
3562    fn test_sign_extension_negative_one() {
3563        let v = ValueWord::from_i64(-1);
3564        assert!(v.is_i64());
3565        assert_eq!(v.as_i64(), Some(-1));
3566    }
3567
3568    #[test]
3569    fn test_sign_extension_boundary() {
3570        // -1 in 48-bit two's complement is 0x0000_FFFF_FFFF_FFFF (all 48 bits set)
3571        let v = ValueWord::from_i64(-1);
3572        let payload = get_payload(v.0);
3573        assert_eq!(payload, 0x0000_FFFF_FFFF_FFFF);
3574        assert_eq!(sign_extend_i48(payload), -1);
3575
3576        // Most negative i48: -2^47
3577        let v = ValueWord::from_i64(I48_MIN);
3578        let payload = get_payload(v.0);
3579        // Bit 47 should be set, bits 46-0 should be 0
3580        assert_eq!(payload, 0x0000_8000_0000_0000);
3581        assert_eq!(sign_extend_i48(payload), I48_MIN);
3582    }
3583
3584    // ===== Drop safety =====
3585
3586    #[test]
3587    fn test_drop_non_heap_is_noop() {
3588        // These should drop without issue (no heap allocation).
3589        let _ = ValueWord::from_f64(1.0);
3590        let _ = ValueWord::from_i64(1);
3591        let _ = ValueWord::from_bool(true);
3592        let _ = ValueWord::none();
3593        let _ = ValueWord::unit();
3594        let _ = ValueWord::from_function(0);
3595    }
3596
3597    #[test]
3598    fn test_drop_heap_frees_memory() {
3599        // Create a heap value and let it drop — should not leak or crash.
3600        let _v = ValueWord::from_string(Arc::new("drop test".to_string()));
3601        // Dropped here — if Drop is wrong, ASAN/MSAN would catch it.
3602    }
3603
3604    #[test]
3605    fn test_multiple_clones_and_drops() {
3606        let v1 = ValueWord::from_string(Arc::new("multi clone".to_string()));
3607        let v2 = v1.clone();
3608        let v3 = v2.clone();
3609
3610        assert_eq!(v1.as_arc_string().map(|s| s.as_str()), Some("multi clone"));
3611        assert_eq!(v2.as_arc_string().map(|s| s.as_str()), Some("multi clone"));
3612        assert_eq!(v3.as_arc_string().map(|s| s.as_str()), Some("multi clone"));
3613
3614        // All three drop independently without double-free.
3615        drop(v2);
3616        assert_eq!(v1.as_arc_string().map(|s| s.as_str()), Some("multi clone"));
3617        assert_eq!(v3.as_arc_string().map(|s| s.as_str()), Some("multi clone"));
3618    }
3619
3620    // ===== DateTime<FixedOffset> round-trips =====
3621
3622    #[test]
3623    fn test_datetime_fixed_offset_roundtrip() {
3624        use chrono::TimeZone;
3625        let offset = chrono::FixedOffset::east_opt(5 * 3600 + 30 * 60).unwrap(); // +05:30
3626        let dt = offset.with_ymd_and_hms(2024, 6, 15, 14, 30, 0).unwrap();
3627        let v = ValueWord::from_time(dt);
3628        let extracted = v.as_time().unwrap();
3629        assert_eq!(extracted, dt);
3630        assert_eq!(extracted.offset().local_minus_utc(), 5 * 3600 + 30 * 60);
3631    }
3632
3633    #[test]
3634    fn test_datetime_utc_convenience() {
3635        use chrono::TimeZone;
3636        let utc_dt = chrono::Utc.with_ymd_and_hms(2024, 1, 15, 10, 0, 0).unwrap();
3637        let v = ValueWord::from_time_utc(utc_dt);
3638        let extracted = v.as_time().unwrap();
3639        assert_eq!(extracted.offset().local_minus_utc(), 0);
3640        assert_eq!(extracted.timestamp(), utc_dt.timestamp());
3641    }
3642
3643    #[test]
3644    fn test_as_datetime_returns_ref() {
3645        use chrono::TimeZone;
3646        let offset = chrono::FixedOffset::west_opt(4 * 3600).unwrap(); // -04:00
3647        let dt = offset.with_ymd_and_hms(2024, 12, 25, 8, 0, 0).unwrap();
3648        let v = ValueWord::from_time(dt);
3649        let dt_ref = v.as_datetime().unwrap();
3650        assert_eq!(*dt_ref, dt);
3651        assert_eq!(dt_ref.offset().local_minus_utc(), -4 * 3600);
3652    }
3653
3654    #[test]
3655    fn test_datetime_display() {
3656        use chrono::TimeZone;
3657        let utc_dt = chrono::Utc.with_ymd_and_hms(2024, 3, 1, 12, 0, 0).unwrap();
3658        let v = ValueWord::from_time_utc(utc_dt);
3659        let display = format!("{}", v);
3660        assert!(display.contains("2024-03-01"));
3661    }
3662
3663    #[test]
3664    fn test_as_number_coerce_rejects_native_i64_u64() {
3665        let i64_nb = ValueWord::from_native_scalar(NativeScalar::I64(42));
3666        let u64_nb = ValueWord::from_native_u64(u64::MAX);
3667        assert_eq!(i64_nb.as_number_coerce(), None);
3668        assert_eq!(u64_nb.as_number_coerce(), None);
3669    }
3670
3671    #[test]
3672    fn test_as_number_coerce_accepts_native_f32() {
3673        let v = ValueWord::from_native_f32(12.5);
3674        assert_eq!(v.as_number_coerce(), Some(12.5));
3675    }
3676
3677    #[test]
3678    fn test_exact_integer_extractors_cover_u64() {
3679        let v = ValueWord::from_native_u64(u64::MAX);
3680        assert_eq!(v.as_u64(), Some(u64::MAX));
3681        assert_eq!(v.as_i128_exact(), Some(u64::MAX as i128));
3682    }
3683
3684    // ===== Typed array (IntArray/FloatArray/BoolArray) tests =====
3685
3686    #[test]
3687    fn test_int_array_construction_and_roundtrip() {
3688        let data = vec![1i64, 2, 3, -100, 0];
3689        let nb = ValueWord::from_int_array(Arc::new(data.clone().into()));
3690        assert!(nb.is_heap());
3691        let arr = nb.as_int_array().unwrap();
3692        assert_eq!(arr.as_slice(), &data);
3693    }
3694
3695    #[test]
3696    fn test_float_array_construction_and_roundtrip() {
3697        use crate::aligned_vec::AlignedVec;
3698        let mut aligned = AlignedVec::with_capacity(4);
3699        aligned.push(1.0);
3700        aligned.push(2.5);
3701        aligned.push(-3.14);
3702        aligned.push(0.0);
3703        let nb = ValueWord::from_float_array(Arc::new(aligned.into()));
3704        assert!(nb.is_heap());
3705        let arr = nb.as_float_array().unwrap();
3706        assert_eq!(arr.len(), 4);
3707        assert_eq!(arr[0], 1.0);
3708        assert_eq!(arr[2], -3.14);
3709    }
3710
3711    #[test]
3712    fn test_bool_array_construction_and_roundtrip() {
3713        let data = vec![1u8, 0, 1, 1, 0];
3714        let nb = ValueWord::from_bool_array(Arc::new(data.clone().into()));
3715        assert!(nb.is_heap());
3716        let arr = nb.as_bool_array().unwrap();
3717        assert_eq!(arr.as_slice(), &data);
3718    }
3719
3720    #[test]
3721    fn test_int_array_type_name() {
3722        let nb = ValueWord::from_int_array(Arc::new(vec![1i64, 2, 3].into()));
3723        assert_eq!(nb.type_name(), "Vec<int>");
3724    }
3725
3726    #[test]
3727    fn test_float_array_type_name() {
3728        use crate::aligned_vec::AlignedVec;
3729        let mut aligned = AlignedVec::with_capacity(1);
3730        aligned.push(1.0);
3731        let nb = ValueWord::from_float_array(Arc::new(aligned.into()));
3732        assert_eq!(nb.type_name(), "Vec<number>");
3733    }
3734
3735    #[test]
3736    fn test_bool_array_type_name() {
3737        let nb = ValueWord::from_bool_array(Arc::new(vec![0u8, 1].into()));
3738        assert_eq!(nb.type_name(), "Vec<bool>");
3739    }
3740
3741    #[test]
3742    fn test_int_array_is_truthy_nonempty() {
3743        let nb = ValueWord::from_int_array(Arc::new(vec![42i64].into()));
3744        assert!(nb.is_truthy());
3745    }
3746
3747    #[test]
3748    fn test_int_array_is_truthy_empty() {
3749        let nb = ValueWord::from_int_array(Arc::new(Vec::<i64>::new().into()));
3750        assert!(!nb.is_truthy());
3751    }
3752
3753    #[test]
3754    fn test_float_array_is_truthy_nonempty() {
3755        use crate::aligned_vec::AlignedVec;
3756        let mut aligned = AlignedVec::with_capacity(1);
3757        aligned.push(0.0);
3758        let nb = ValueWord::from_float_array(Arc::new(aligned.into()));
3759        assert!(nb.is_truthy());
3760    }
3761
3762    #[test]
3763    fn test_float_array_is_truthy_empty() {
3764        use crate::aligned_vec::AlignedVec;
3765        let nb = ValueWord::from_float_array(Arc::new(AlignedVec::new().into()));
3766        assert!(!nb.is_truthy());
3767    }
3768
3769    #[test]
3770    fn test_bool_array_is_truthy_nonempty() {
3771        let nb = ValueWord::from_bool_array(Arc::new(vec![0u8].into()));
3772        assert!(nb.is_truthy());
3773    }
3774
3775    #[test]
3776    fn test_bool_array_is_truthy_empty() {
3777        let nb = ValueWord::from_bool_array(Arc::new(Vec::<u8>::new().into()));
3778        assert!(!nb.is_truthy());
3779    }
3780
3781    #[test]
3782    fn test_int_array_clone_arc_refcount() {
3783        let data: Arc<crate::typed_buffer::TypedBuffer<i64>> = Arc::new(vec![10i64, 20, 30].into());
3784        let nb1 = ValueWord::from_int_array(data.clone());
3785        let nb2 = nb1.clone();
3786        let arr1 = nb1.as_int_array().unwrap();
3787        let arr2 = nb2.as_int_array().unwrap();
3788        assert_eq!(arr1.as_ref(), arr2.as_ref());
3789        assert!(Arc::ptr_eq(arr1, arr2));
3790    }
3791
3792    #[test]
3793    fn test_float_array_clone_arc_refcount() {
3794        use crate::aligned_vec::AlignedVec;
3795        let mut aligned = AlignedVec::with_capacity(2);
3796        aligned.push(1.0);
3797        aligned.push(2.0);
3798        let data: Arc<crate::typed_buffer::AlignedTypedBuffer> = Arc::new(aligned.into());
3799        let nb1 = ValueWord::from_float_array(data.clone());
3800        let nb2 = nb1.clone();
3801        let arr1 = nb1.as_float_array().unwrap();
3802        let arr2 = nb2.as_float_array().unwrap();
3803        assert!(Arc::ptr_eq(arr1, arr2));
3804    }
3805
3806    #[test]
3807    fn test_typed_array_len() {
3808        let int_nb = ValueWord::from_int_array(Arc::new(vec![1i64, 2, 3].into()));
3809        assert_eq!(int_nb.typed_array_len(), Some(3));
3810
3811        use crate::aligned_vec::AlignedVec;
3812        let mut aligned = AlignedVec::with_capacity(2);
3813        aligned.push(1.0);
3814        aligned.push(2.0);
3815        let float_nb = ValueWord::from_float_array(Arc::new(aligned.into()));
3816        assert_eq!(float_nb.typed_array_len(), Some(2));
3817
3818        let bool_nb = ValueWord::from_bool_array(Arc::new(vec![0u8, 1, 1, 0].into()));
3819        assert_eq!(bool_nb.typed_array_len(), Some(4));
3820
3821        let number_nb = ValueWord::from_f64(42.0);
3822        assert_eq!(number_nb.typed_array_len(), None);
3823    }
3824
3825    #[test]
3826    fn test_coerce_to_float_array_from_float() {
3827        use crate::aligned_vec::AlignedVec;
3828        let mut aligned = AlignedVec::with_capacity(3);
3829        aligned.push(1.0);
3830        aligned.push(2.0);
3831        aligned.push(3.0);
3832        let nb = ValueWord::from_float_array(Arc::new(aligned.into()));
3833        let result = nb.coerce_to_float_array().unwrap();
3834        assert_eq!(result.len(), 3);
3835        assert_eq!(result[0], 1.0);
3836    }
3837
3838    #[test]
3839    fn test_coerce_to_float_array_from_int() {
3840        let nb = ValueWord::from_int_array(Arc::new(vec![10i64, 20, 30].into()));
3841        let result = nb.coerce_to_float_array().unwrap();
3842        assert_eq!(result.len(), 3);
3843        assert_eq!(result[0], 10.0);
3844        assert_eq!(result[1], 20.0);
3845        assert_eq!(result[2], 30.0);
3846    }
3847
3848    #[test]
3849    fn test_to_generic_array_int() {
3850        let nb = ValueWord::from_int_array(Arc::new(vec![5i64, 10].into()));
3851        let generic = nb.to_generic_array().unwrap();
3852        assert_eq!(generic.len(), 2);
3853        assert_eq!(generic[0].as_i64(), Some(5));
3854        assert_eq!(generic[1].as_i64(), Some(10));
3855    }
3856
3857    #[test]
3858    fn test_to_generic_array_float() {
3859        use crate::aligned_vec::AlignedVec;
3860        let mut aligned = AlignedVec::with_capacity(2);
3861        aligned.push(1.5);
3862        aligned.push(2.5);
3863        let nb = ValueWord::from_float_array(Arc::new(aligned.into()));
3864        let generic = nb.to_generic_array().unwrap();
3865        assert_eq!(generic.len(), 2);
3866        assert_eq!(generic[0].as_f64(), Some(1.5));
3867        assert_eq!(generic[1].as_f64(), Some(2.5));
3868    }
3869
3870    #[test]
3871    fn test_to_generic_array_bool() {
3872        let nb = ValueWord::from_bool_array(Arc::new(vec![1u8, 0, 1].into()));
3873        let generic = nb.to_generic_array().unwrap();
3874        assert_eq!(generic.len(), 3);
3875        assert_eq!(generic[0].as_bool(), Some(true));
3876        assert_eq!(generic[1].as_bool(), Some(false));
3877        assert_eq!(generic[2].as_bool(), Some(true));
3878    }
3879
3880    #[test]
3881    fn test_int_array_nb_equals() {
3882        let a = ValueWord::from_int_array(Arc::new(vec![1i64, 2, 3].into()));
3883        let b = ValueWord::from_int_array(Arc::new(vec![1i64, 2, 3].into()));
3884        let c = ValueWord::from_int_array(Arc::new(vec![1i64, 2, 4].into()));
3885        assert!(a.vw_equals(&b));
3886        assert!(!a.vw_equals(&c));
3887    }
3888
3889    #[test]
3890    fn test_float_array_nb_equals() {
3891        use crate::aligned_vec::AlignedVec;
3892        let mut a_data = AlignedVec::with_capacity(2);
3893        a_data.push(1.0);
3894        a_data.push(2.0);
3895        let mut b_data = AlignedVec::with_capacity(2);
3896        b_data.push(1.0);
3897        b_data.push(2.0);
3898        let a = ValueWord::from_float_array(Arc::new(a_data.into()));
3899        let b = ValueWord::from_float_array(Arc::new(b_data.into()));
3900        assert!(a.vw_equals(&b));
3901    }
3902
3903    #[test]
3904    fn test_cross_type_accessor_returns_none() {
3905        let int_nb = ValueWord::from_int_array(Arc::new(vec![1i64, 2].into()));
3906        assert!(int_nb.as_float_array().is_none());
3907        assert!(int_nb.as_bool_array().is_none());
3908        assert!(int_nb.as_array().is_none());
3909
3910        use crate::aligned_vec::AlignedVec;
3911        let float_nb = ValueWord::from_float_array(Arc::new(AlignedVec::new().into()));
3912        assert!(float_nb.as_int_array().is_none());
3913
3914        let bool_nb = ValueWord::from_bool_array(Arc::new(Vec::<u8>::new().into()));
3915        assert!(bool_nb.as_int_array().is_none());
3916        assert!(bool_nb.as_float_array().is_none());
3917    }
3918
3919    // ===== to_json_value for typed arrays =====
3920
3921    #[test]
3922    fn test_to_json_value_int_array() {
3923        let buf = crate::typed_buffer::TypedBuffer {
3924            data: vec![1i64, 2, 3],
3925            validity: None,
3926        };
3927        let v = ValueWord::from_int_array(Arc::new(buf));
3928        let json = v.to_json_value();
3929        assert_eq!(json, serde_json::json!([1, 2, 3]));
3930    }
3931
3932    #[test]
3933    fn test_to_json_value_float_array() {
3934        use crate::aligned_vec::AlignedVec;
3935        let mut av = AlignedVec::new();
3936        av.push(1.5);
3937        av.push(2.5);
3938        let buf = crate::typed_buffer::AlignedTypedBuffer {
3939            data: av,
3940            validity: None,
3941        };
3942        let v = ValueWord::from_float_array(Arc::new(buf));
3943        let json = v.to_json_value();
3944        assert_eq!(json, serde_json::json!([1.5, 2.5]));
3945    }
3946
3947    #[test]
3948    fn test_to_json_value_bool_array() {
3949        let buf = crate::typed_buffer::TypedBuffer {
3950            data: vec![1u8, 0, 1],
3951            validity: None,
3952        };
3953        let v = ValueWord::from_bool_array(Arc::new(buf));
3954        let json = v.to_json_value();
3955        assert_eq!(json, serde_json::json!([true, false, true]));
3956    }
3957
3958    #[test]
3959    fn test_to_json_value_empty_int_array() {
3960        let buf = crate::typed_buffer::TypedBuffer::<i64> {
3961            data: vec![],
3962            validity: None,
3963        };
3964        let v = ValueWord::from_int_array(Arc::new(buf));
3965        let json = v.to_json_value();
3966        assert_eq!(json, serde_json::json!([]));
3967    }
3968
3969    #[test]
3970    fn test_to_json_value_i32_array() {
3971        let buf = crate::typed_buffer::TypedBuffer {
3972            data: vec![10i32, 20, 30],
3973            validity: None,
3974        };
3975        let v = ValueWord::heap_box(HeapValue::I32Array(Arc::new(buf)));
3976        let json = v.to_json_value();
3977        assert_eq!(json, serde_json::json!([10, 20, 30]));
3978    }
3979
3980    #[test]
3981    fn test_to_json_value_u64_array() {
3982        let buf = crate::typed_buffer::TypedBuffer {
3983            data: vec![100u64, 200],
3984            validity: None,
3985        };
3986        let v = ValueWord::heap_box(HeapValue::U64Array(Arc::new(buf)));
3987        let json = v.to_json_value();
3988        assert_eq!(json, serde_json::json!([100, 200]));
3989    }
3990
3991    #[test]
3992    fn test_to_json_value_f32_array() {
3993        let buf = crate::typed_buffer::TypedBuffer {
3994            data: vec![1.0f32, 2.0],
3995            validity: None,
3996        };
3997        let v = ValueWord::heap_box(HeapValue::F32Array(Arc::new(buf)));
3998        let json = v.to_json_value();
3999        assert_eq!(json, serde_json::json!([1.0, 2.0]));
4000    }
4001}