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