Skip to main content

shape_value/v2/
typed_array.rs

1//! Typed contiguous array for v2 runtime.
2//!
3//! `TypedArray<T>` is a 24-byte `#[repr(C)]` heap object with a `HeapHeader`,
4//! a pointer to a contiguous `T` buffer, length, and capacity. The compiler
5//! monomorphizes: `Array<number>` and `Array<i32>` are different `TypedArray`
6//! instantiations with no element-level type checking.
7//!
8//! ## Memory layout (24 bytes)
9//!
10//! ```text
11//! Offset  Size  Field
12//! ------  ----  -----
13//!   0       8   header (HeapHeader — refcount at offset 0)
14//!   8       8   data (*mut T — pointer to contiguous T buffer)
15//!  16       4   len (element count)
16//!  20       4   cap (allocated capacity)
17//! ```
18
19use super::heap_header::{HeapHeader, HEAP_KIND_V2_TYPED_ARRAY};
20use std::alloc::{Layout, alloc, dealloc, realloc};
21use std::ptr;
22
23/// Typed contiguous array with refcounted header.
24///
25/// Allocated on the heap via raw allocator. The `data` pointer points to a
26/// separate allocation holding `cap` elements of type `T`.
27#[repr(C)]
28pub struct TypedArray<T> {
29    /// 8-byte v2 heap header (refcount at offset 0).
30    pub header: HeapHeader,
31    /// Pointer to contiguous T buffer.
32    pub data: *mut T,
33    /// Number of elements currently stored.
34    pub len: u32,
35    /// Allocated capacity in number of elements.
36    pub cap: u32,
37}
38
39// Compile-time size assertion.
40const _: () = {
41    assert!(std::mem::size_of::<TypedArray<f64>>() == 24);
42    assert!(std::mem::size_of::<TypedArray<i32>>() == 24);
43    assert!(std::mem::size_of::<TypedArray<u8>>() == 24);
44    // Wave 2 Agent A1 (2026-05-14) — F32 + Char scalar monomorphizations.
45    assert!(std::mem::size_of::<TypedArray<f32>>() == 24);
46    assert!(std::mem::size_of::<TypedArray<char>>() == 24);
47};
48
49impl<T: Copy> TypedArray<T> {
50    /// Allocate a new empty TypedArray with capacity 0.
51    ///
52    /// Returns a raw pointer to the heap-allocated array. The caller is
53    /// responsible for eventually calling `drop_array` to free it.
54    pub fn new() -> *mut Self {
55        Self::with_capacity(0)
56    }
57
58    /// Allocate a new TypedArray with the given capacity.
59    ///
60    /// Returns a raw pointer to the heap-allocated array.
61    pub fn with_capacity(cap: u32) -> *mut Self {
62        let layout = Layout::new::<Self>();
63        let ptr = unsafe { alloc(layout) as *mut Self };
64        assert!(!ptr.is_null(), "allocation failed for TypedArray");
65
66        let data = if cap > 0 {
67            let data_layout = Layout::array::<T>(cap as usize).expect("invalid array layout");
68            let data_ptr = unsafe { alloc(data_layout) as *mut T };
69            assert!(!data_ptr.is_null(), "allocation failed for TypedArray data");
70            data_ptr
71        } else {
72            ptr::null_mut()
73        };
74
75        unsafe {
76            ptr::write(
77                ptr,
78                Self {
79                    header: HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY),
80                    data,
81                    len: 0,
82                    cap,
83                },
84            );
85        }
86
87        ptr
88    }
89
90    /// Create a TypedArray from a slice, copying all elements.
91    pub fn from_slice(slice: &[T]) -> *mut Self {
92        let len = slice.len() as u32;
93        let ptr = Self::with_capacity(len);
94        unsafe {
95            if len > 0 {
96                ptr::copy_nonoverlapping(slice.as_ptr(), (*ptr).data, slice.len());
97            }
98            (*ptr).len = len;
99        }
100        ptr
101    }
102
103    /// Get an element by index, returning `None` if out of bounds.
104    ///
105    /// # Safety
106    /// `this` must point to a valid, live `TypedArray<T>`.
107    #[inline]
108    pub unsafe fn get(this: *const Self, index: u32) -> Option<T> {
109        unsafe {
110            if index >= (*this).len {
111                None
112            } else {
113                Some(ptr::read((*this).data.add(index as usize)))
114            }
115        }
116    }
117
118    /// Get an element by index without bounds checking.
119    ///
120    /// # Safety
121    /// `this` must point to a valid, live `TypedArray<T>`, and `index` must
122    /// be less than the array's length.
123    #[inline]
124    pub unsafe fn get_unchecked(this: *const Self, index: u32) -> T {
125        unsafe { ptr::read((*this).data.add(index as usize)) }
126    }
127
128    /// Set an element by index. Panics if out of bounds.
129    ///
130    /// # Safety
131    /// `this` must point to a valid, live `TypedArray<T>`.
132    #[inline]
133    pub unsafe fn set(this: *mut Self, index: u32, val: T) {
134        unsafe {
135            assert!(
136                index < (*this).len,
137                "TypedArray::set index {} out of bounds (len {})",
138                index,
139                (*this).len
140            );
141            ptr::write((*this).data.add(index as usize), val);
142        }
143    }
144
145    /// Push an element, growing the buffer if necessary (doubling strategy).
146    ///
147    /// # Safety
148    /// `this` must point to a valid, live `TypedArray<T>`.
149    pub unsafe fn push(this: *mut Self, val: T) {
150        unsafe {
151            let arr = &mut *this;
152            if arr.len == arr.cap {
153                Self::grow(this);
154            }
155            let arr = &mut *this;
156            ptr::write(arr.data.add(arr.len as usize), val);
157            arr.len += 1;
158        }
159    }
160
161    /// Pop the last element, returning `None` if empty.
162    ///
163    /// # Safety
164    /// `this` must point to a valid, live `TypedArray<T>`.
165    pub unsafe fn pop(this: *mut Self) -> Option<T> {
166        unsafe {
167            let arr = &mut *this;
168            if arr.len == 0 {
169                None
170            } else {
171                arr.len -= 1;
172                Some(ptr::read(arr.data.add(arr.len as usize)))
173            }
174        }
175    }
176
177    /// Get the number of elements.
178    ///
179    /// # Safety
180    /// `this` must point to a valid, live `TypedArray<T>`.
181    #[inline]
182    pub unsafe fn len(this: *const Self) -> u32 {
183        unsafe { (*this).len }
184    }
185
186    /// Get the allocated capacity.
187    ///
188    /// # Safety
189    /// `this` must point to a valid, live `TypedArray<T>`.
190    #[inline]
191    pub unsafe fn capacity(this: *const Self) -> u32 {
192        unsafe { (*this).cap }
193    }
194
195    /// Check if the array is empty.
196    ///
197    /// # Safety
198    /// `this` must point to a valid, live `TypedArray<T>`.
199    #[inline]
200    pub unsafe fn is_empty(this: *const Self) -> bool {
201        unsafe { (*this).len == 0 }
202    }
203
204    /// Get the elements as a slice.
205    ///
206    /// # Safety
207    /// `this` must point to a valid, live `TypedArray<T>`.
208    #[inline]
209    pub unsafe fn as_slice<'a>(this: *const Self) -> &'a [T] {
210        unsafe {
211            if (*this).len == 0 {
212                &[]
213            } else {
214                std::slice::from_raw_parts((*this).data, (*this).len as usize)
215            }
216        }
217    }
218
219    /// Get the elements as a mutable slice.
220    ///
221    /// # Safety
222    /// `this` must point to a valid, live `TypedArray<T>`.
223    #[inline]
224    pub unsafe fn as_mut_slice<'a>(this: *mut Self) -> &'a mut [T] {
225        unsafe {
226            if (*this).len == 0 {
227                &mut []
228            } else {
229                std::slice::from_raw_parts_mut((*this).data, (*this).len as usize)
230            }
231        }
232    }
233
234    /// Deallocate the array and its data buffer.
235    ///
236    /// # Safety
237    /// `ptr` must point to a `TypedArray<T>` that was allocated by this module.
238    /// After calling this, `ptr` is invalid.
239    pub unsafe fn drop_array(ptr: *mut Self) {
240        unsafe {
241            let arr = &*ptr;
242            // Free the data buffer if it was allocated.
243            if arr.cap > 0 && !arr.data.is_null() {
244                let data_layout =
245                    Layout::array::<T>(arr.cap as usize).expect("invalid array layout");
246                dealloc(arr.data as *mut u8, data_layout);
247            }
248            // Free the TypedArray struct itself.
249            let layout = Layout::new::<Self>();
250            dealloc(ptr as *mut u8, layout);
251        }
252    }
253
254    /// Grow the data buffer (doubling strategy, minimum 4).
255    ///
256    /// # Safety
257    /// `this` must point to a valid, live `TypedArray<T>`.
258    unsafe fn grow(this: *mut Self) {
259        unsafe {
260            let arr = &mut *this;
261            let new_cap = if arr.cap == 0 {
262                4
263            } else {
264                arr.cap.checked_mul(2).expect("capacity overflow")
265            };
266            let new_layout = Layout::array::<T>(new_cap as usize).expect("invalid array layout");
267
268            let new_data = if arr.cap == 0 || arr.data.is_null() {
269                alloc(new_layout) as *mut T
270            } else {
271                let old_layout =
272                    Layout::array::<T>(arr.cap as usize).expect("invalid array layout");
273                realloc(arr.data as *mut u8, old_layout, new_layout.size()) as *mut T
274            };
275            assert!(!new_data.is_null(), "reallocation failed for TypedArray");
276
277            arr.data = new_data;
278            arr.cap = new_cap;
279        }
280    }
281}
282
283/// Heap-element-aware drop dispatch for `TypedArray<*const T>` where `T:
284/// HeapElement`.
285///
286/// Per ADR-006 §2.7.24 Q25.A SUPERSEDED + R20 S2-prime audit deliverable (b)
287/// §4.1.B decision: `drop_array_heap` walks the element buffer and calls
288/// `T::release_elem(elem_ptr)` for each stored pointer, then frees the data
289/// buffer + the TypedArray struct itself. Per-T dispatch is monomorphized at
290/// compile time via the `HeapElement` trait — no runtime `NativeKind` probe.
291///
292/// Pairs with the POD-element `drop_array` for `T: Copy` (above). Callers
293/// pick at compile time based on whether the element type is POD (plain
294/// scalar like f64/i64) or HeapHeader-equipped (`*const StringObj` /
295/// `*const DecimalObj` / ...).
296impl<T: super::heap_element::HeapElement> TypedArray<*const T> {
297    /// Deallocate the array, releasing per-element shares via
298    /// `T::release_elem`, then freeing the data buffer + the struct.
299    ///
300    /// # Safety
301    /// `ptr` must point to a `TypedArray<*const T>` that was allocated by
302    /// this module. Each stored `*const T` must be a valid pointer to a
303    /// live `T` allocation with at least one refcount share owned by this
304    /// array. After this call, `ptr` is invalid.
305    pub unsafe fn drop_array_heap(ptr: *mut Self) {
306        unsafe {
307            let arr = &*ptr;
308            if arr.cap > 0 && !arr.data.is_null() {
309                // Walk element buffer; release per-element shares.
310                for i in 0..arr.len {
311                    let elem_ptr = ptr::read(arr.data.add(i as usize));
312                    T::release_elem(elem_ptr);
313                }
314                // Free the data buffer.
315                let data_layout = Layout::array::<*const T>(arr.cap as usize)
316                    .expect("invalid array layout");
317                dealloc(arr.data as *mut u8, data_layout);
318            }
319            // Free the TypedArray struct itself.
320            let layout = Layout::new::<Self>();
321            dealloc(ptr as *mut u8, layout);
322        }
323    }
324}
325
326// Allocation + size-only operations available for non-Copy element types
327// (e.g. `TypedObjectPtr` with manual `Drop`). Per ADR-006 §2.7.24 Q25.B
328// SUPERSEDED + Wave 2 Round 3b C2-joint ckpt-1 — `HashMapData<V>` (in
329// `crates/shape-value/src/heap_value.rs`) instantiates `TypedArray<V>` for
330// `V = TypedObjectPtr` / `TraitObjectPtr` (transparent newtypes with manual
331// Drop), which are not `Copy`. The methods here are size/allocation only —
332// no `ptr::read` / `ptr::write` that would require `T: Copy` for soundness.
333//
334// Methods needing element copy semantics (`get_unchecked`, `set`, `push`,
335// `pop`, `from_slice`) remain bounded by `T: Copy` in the impl block above;
336// non-Copy element types use the per-element-Drop-aware paths in
337// `HashMapValueElem::release_typed_array`.
338impl<T> TypedArray<T> {
339    /// Allocate a new empty TypedArray with capacity 0 — non-Copy variant.
340    ///
341    /// Returns a raw pointer to the heap-allocated array. The caller is
342    /// responsible for eventually freeing it via `HashMapValueElem::
343    /// release_typed_array` (for `HashMapData<V>` value buffers) or the
344    /// equivalent per-T release path.
345    #[doc(alias = "new")]
346    pub fn new_generic() -> *mut Self {
347        Self::with_capacity_generic(0)
348    }
349
350    /// Allocate a new TypedArray with the given capacity — non-Copy variant.
351    ///
352    /// Returns a raw pointer to the heap-allocated array. No elements are
353    /// written; the data buffer is uninitialized memory of length `cap *
354    /// size_of::<T>()`.
355    #[doc(alias = "with_capacity")]
356    pub fn with_capacity_generic(cap: u32) -> *mut Self {
357        let layout = Layout::new::<Self>();
358        let ptr = unsafe { alloc(layout) as *mut Self };
359        assert!(!ptr.is_null(), "allocation failed for TypedArray");
360
361        let data = if cap > 0 {
362            let data_layout = Layout::array::<T>(cap as usize).expect("invalid array layout");
363            let data_ptr = unsafe { alloc(data_layout) as *mut T };
364            assert!(!data_ptr.is_null(), "allocation failed for TypedArray data");
365            data_ptr
366        } else {
367            ptr::null_mut()
368        };
369
370        unsafe {
371            ptr::write(
372                ptr,
373                Self {
374                    header: HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY),
375                    data,
376                    len: 0,
377                    cap,
378                },
379            );
380        }
381
382        ptr
383    }
384
385    /// Get the number of elements — non-Copy variant.
386    ///
387    /// # Safety
388    /// `this` must point to a valid, live `TypedArray<T>`.
389    #[inline]
390    pub unsafe fn len_generic(this: *const Self) -> u32 {
391        unsafe { (*this).len }
392    }
393
394    /// Get the allocated capacity — non-Copy variant.
395    ///
396    /// # Safety
397    /// `this` must point to a valid, live `TypedArray<T>`.
398    #[inline]
399    pub unsafe fn capacity_generic(this: *const Self) -> u32 {
400        unsafe { (*this).cap }
401    }
402
403    /// Check if the array is empty — non-Copy variant.
404    ///
405    /// # Safety
406    /// `this` must point to a valid, live `TypedArray<T>`.
407    #[inline]
408    pub unsafe fn is_empty_generic(this: *const Self) -> bool {
409        unsafe { (*this).len == 0 }
410    }
411
412    /// Get the elements as a slice — non-Copy variant.
413    ///
414    /// # Safety
415    /// `this` must point to a valid, live `TypedArray<T>`.
416    #[inline]
417    pub unsafe fn as_slice_generic<'a>(this: *const Self) -> &'a [T] {
418        unsafe {
419            if (*this).len == 0 {
420                &[]
421            } else {
422                std::slice::from_raw_parts((*this).data, (*this).len as usize)
423            }
424        }
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_size_of_typed_array() {
434        assert_eq!(std::mem::size_of::<TypedArray<f64>>(), 24);
435        assert_eq!(std::mem::size_of::<TypedArray<i32>>(), 24);
436        assert_eq!(std::mem::size_of::<TypedArray<i64>>(), 24);
437        assert_eq!(std::mem::size_of::<TypedArray<u8>>(), 24);
438    }
439
440    #[test]
441    fn test_field_offsets() {
442        let arr = TypedArray::<f64>::with_capacity(0);
443        unsafe {
444            let base = arr as *const u8 as usize;
445            let header_offset = &(*arr).header as *const _ as usize - base;
446            let data_offset = &(*arr).data as *const _ as usize - base;
447            let len_offset = &(*arr).len as *const _ as usize - base;
448            let cap_offset = &(*arr).cap as *const _ as usize - base;
449
450            assert_eq!(header_offset, 0);
451            assert_eq!(data_offset, 8);
452            assert_eq!(len_offset, 16);
453            assert_eq!(cap_offset, 20);
454
455            TypedArray::drop_array(arr);
456        }
457    }
458
459    #[test]
460    fn test_new_empty() {
461        let arr = TypedArray::<f64>::new();
462        unsafe {
463            assert_eq!(TypedArray::len(arr), 0);
464            assert_eq!(TypedArray::capacity(arr), 0);
465            assert!(TypedArray::is_empty(arr));
466            assert_eq!((*arr).header.kind(), HEAP_KIND_V2_TYPED_ARRAY);
467            assert_eq!((*arr).header.get_refcount(), 1);
468            TypedArray::drop_array(arr);
469        }
470    }
471
472    #[test]
473    fn test_with_capacity() {
474        let arr = TypedArray::<f64>::with_capacity(16);
475        unsafe {
476            assert_eq!(TypedArray::len(arr), 0);
477            assert_eq!(TypedArray::capacity(arr), 16);
478            assert!(TypedArray::is_empty(arr));
479            TypedArray::drop_array(arr);
480        }
481    }
482
483    #[test]
484    fn test_push_and_get_f64() {
485        let arr = TypedArray::<f64>::new();
486        unsafe {
487            TypedArray::push(arr, 1.0);
488            TypedArray::push(arr, 2.5);
489            TypedArray::push(arr, 3.14);
490
491            assert_eq!(TypedArray::len(arr), 3);
492            assert!(!TypedArray::is_empty(arr));
493
494            assert_eq!(TypedArray::get(arr, 0), Some(1.0));
495            assert_eq!(TypedArray::get(arr, 1), Some(2.5));
496            assert_eq!(TypedArray::get(arr, 2), Some(3.14));
497            assert_eq!(TypedArray::get(arr, 3), None); // out of bounds
498
499            TypedArray::drop_array(arr);
500        }
501    }
502
503    #[test]
504    fn test_push_and_get_i32() {
505        let arr = TypedArray::<i32>::new();
506        unsafe {
507            TypedArray::push(arr, 42);
508            TypedArray::push(arr, -7);
509            TypedArray::push(arr, 0);
510
511            assert_eq!(TypedArray::len(arr), 3);
512            assert_eq!(TypedArray::get(arr, 0), Some(42));
513            assert_eq!(TypedArray::get(arr, 1), Some(-7));
514            assert_eq!(TypedArray::get(arr, 2), Some(0));
515            assert_eq!(TypedArray::get(arr, 3), None);
516
517            TypedArray::drop_array(arr);
518        }
519    }
520
521    #[test]
522    fn test_push_and_get_i64() {
523        let arr = TypedArray::<i64>::new();
524        unsafe {
525            TypedArray::push(arr, i64::MAX);
526            TypedArray::push(arr, i64::MIN);
527
528            assert_eq!(TypedArray::get(arr, 0), Some(i64::MAX));
529            assert_eq!(TypedArray::get(arr, 1), Some(i64::MIN));
530
531            TypedArray::drop_array(arr);
532        }
533    }
534
535    #[test]
536    fn test_push_and_get_u8_bool() {
537        let arr = TypedArray::<u8>::new();
538        unsafe {
539            TypedArray::push(arr, 1u8); // true
540            TypedArray::push(arr, 0u8); // false
541            TypedArray::push(arr, 1u8); // true
542
543            assert_eq!(TypedArray::len(arr), 3);
544            assert_eq!(TypedArray::get(arr, 0), Some(1));
545            assert_eq!(TypedArray::get(arr, 1), Some(0));
546            assert_eq!(TypedArray::get(arr, 2), Some(1));
547
548            TypedArray::drop_array(arr);
549        }
550    }
551
552    #[test]
553    fn test_get_unchecked() {
554        let arr = TypedArray::<f64>::from_slice(&[10.0, 20.0, 30.0]);
555        unsafe {
556            assert_eq!(TypedArray::get_unchecked(arr, 0), 10.0);
557            assert_eq!(TypedArray::get_unchecked(arr, 1), 20.0);
558            assert_eq!(TypedArray::get_unchecked(arr, 2), 30.0);
559            TypedArray::drop_array(arr);
560        }
561    }
562
563    #[test]
564    fn test_set() {
565        let arr = TypedArray::<f64>::from_slice(&[1.0, 2.0, 3.0]);
566        unsafe {
567            TypedArray::set(arr, 1, 99.0);
568            assert_eq!(TypedArray::get(arr, 1), Some(99.0));
569
570            // Other elements unchanged
571            assert_eq!(TypedArray::get(arr, 0), Some(1.0));
572            assert_eq!(TypedArray::get(arr, 2), Some(3.0));
573
574            TypedArray::drop_array(arr);
575        }
576    }
577
578    #[test]
579    #[should_panic(expected = "out of bounds")]
580    fn test_set_out_of_bounds() {
581        let arr = TypedArray::<f64>::from_slice(&[1.0, 2.0]);
582        unsafe {
583            TypedArray::set(arr, 5, 99.0);
584            // Leak is fine in a panic test
585        }
586    }
587
588    #[test]
589    fn test_pop() {
590        let arr = TypedArray::<i32>::from_slice(&[10, 20, 30]);
591        unsafe {
592            assert_eq!(TypedArray::pop(arr), Some(30));
593            assert_eq!(TypedArray::len(arr), 2);
594
595            assert_eq!(TypedArray::pop(arr), Some(20));
596            assert_eq!(TypedArray::len(arr), 1);
597
598            assert_eq!(TypedArray::pop(arr), Some(10));
599            assert_eq!(TypedArray::len(arr), 0);
600
601            assert_eq!(TypedArray::pop(arr), None);
602            assert!(TypedArray::is_empty(arr));
603
604            TypedArray::drop_array(arr);
605        }
606    }
607
608    #[test]
609    fn test_from_slice() {
610        let data = [1.0f64, 2.0, 3.0, 4.0, 5.0];
611        let arr = TypedArray::from_slice(&data);
612        unsafe {
613            assert_eq!(TypedArray::len(arr), 5);
614            assert_eq!(TypedArray::capacity(arr), 5);
615
616            for (i, &expected) in data.iter().enumerate() {
617                assert_eq!(TypedArray::get(arr, i as u32), Some(expected));
618            }
619
620            TypedArray::drop_array(arr);
621        }
622    }
623
624    #[test]
625    fn test_from_empty_slice() {
626        let arr = TypedArray::<f64>::from_slice(&[]);
627        unsafe {
628            assert_eq!(TypedArray::len(arr), 0);
629            assert_eq!(TypedArray::capacity(arr), 0);
630            assert!(TypedArray::is_empty(arr));
631            TypedArray::drop_array(arr);
632        }
633    }
634
635    #[test]
636    fn test_as_slice() {
637        let arr = TypedArray::from_slice(&[10i32, 20, 30]);
638        unsafe {
639            let s = TypedArray::as_slice(arr);
640            assert_eq!(s, &[10, 20, 30]);
641            TypedArray::drop_array(arr);
642        }
643    }
644
645    #[test]
646    fn test_as_mut_slice() {
647        let arr = TypedArray::from_slice(&[1.0f64, 2.0, 3.0]);
648        unsafe {
649            let s = TypedArray::as_mut_slice(arr);
650            s[1] = 99.0;
651            assert_eq!(TypedArray::get(arr, 1), Some(99.0));
652            TypedArray::drop_array(arr);
653        }
654    }
655
656    #[test]
657    fn test_as_slice_empty() {
658        let arr = TypedArray::<f64>::new();
659        unsafe {
660            let s = TypedArray::as_slice(arr);
661            assert!(s.is_empty());
662            TypedArray::drop_array(arr);
663        }
664    }
665
666    #[test]
667    fn test_capacity_growth() {
668        let arr = TypedArray::<f64>::new();
669        unsafe {
670            // Start with cap 0, first push should grow to 4
671            TypedArray::push(arr, 1.0);
672            assert!(TypedArray::capacity(arr) >= 1);
673
674            // Push enough to trigger several doublings
675            for i in 2..=20 {
676                TypedArray::push(arr, i as f64);
677            }
678            assert_eq!(TypedArray::len(arr), 20);
679
680            // Verify all values
681            for i in 0..20 {
682                assert_eq!(TypedArray::get(arr, i), Some((i + 1) as f64));
683            }
684
685            TypedArray::drop_array(arr);
686        }
687    }
688
689    #[test]
690    fn test_header_kind() {
691        let arr = TypedArray::<f64>::new();
692        unsafe {
693            assert_eq!((*arr).header.kind(), HEAP_KIND_V2_TYPED_ARRAY);
694            assert_eq!((*arr).header.get_refcount(), 1);
695            TypedArray::drop_array(arr);
696        }
697    }
698
699    #[test]
700    fn test_drop_safety() {
701        // Create and drop many arrays to verify no leaks (under Miri/valgrind).
702        unsafe {
703            for _ in 0..100 {
704                let arr = TypedArray::<f64>::new();
705                for i in 0..50 {
706                    TypedArray::push(arr, i as f64);
707                }
708                TypedArray::drop_array(arr);
709            }
710            // Empty arrays
711            for _ in 0..100 {
712                let arr = TypedArray::<i32>::new();
713                TypedArray::drop_array(arr);
714            }
715        }
716    }
717
718    #[test]
719    fn test_get_out_of_bounds_returns_none() {
720        let arr = TypedArray::<f64>::new();
721        unsafe {
722            // Empty array: any index is out of bounds
723            assert_eq!(TypedArray::get(arr, 0), None);
724            assert_eq!(TypedArray::get(arr, 100), None);
725            assert_eq!(TypedArray::get(arr, u32::MAX), None);
726
727            TypedArray::push(arr, 1.0);
728            assert_eq!(TypedArray::get(arr, 0), Some(1.0));
729            assert_eq!(TypedArray::get(arr, 1), None);
730
731            TypedArray::drop_array(arr);
732        }
733    }
734
735    #[test]
736    fn test_refcount_with_typed_array() {
737        use crate::v2::refcount::{v2_get_refcount, v2_retain, v2_release};
738
739        let arr = TypedArray::<f64>::from_slice(&[1.0, 2.0]);
740        unsafe {
741            let header_ptr = arr as *const HeapHeader;
742
743            assert_eq!(v2_get_refcount(header_ptr), 1);
744
745            v2_retain(header_ptr);
746            assert_eq!(v2_get_refcount(header_ptr), 2);
747
748            assert!(!v2_release(header_ptr)); // 2 -> 1
749            assert_eq!(v2_get_refcount(header_ptr), 1);
750
751            // Don't call v2_release to 0 here since we use drop_array for cleanup
752            TypedArray::drop_array(arr);
753        }
754    }
755
756    // ──────────────────────────────────────────────────────────────────────
757    // drop_array_heap tests per ADR-006 §2.7.24 Q25.A SUPERSEDED + R20
758    // S2-prime audit deliverable (b) §4.1.B.
759    // ──────────────────────────────────────────────────────────────────────
760
761    #[test]
762    fn test_drop_array_heap_string_obj() {
763        use crate::v2::string_obj::StringObj;
764        unsafe {
765            // Allocate a TypedArray<*const StringObj> with capacity 4.
766            let arr: *mut TypedArray<*const StringObj> = TypedArray::with_capacity(4);
767            // Push 3 StringObj pointers.
768            let s1 = StringObj::new("hello");
769            let s2 = StringObj::new("world");
770            let s3 = StringObj::new("!");
771            TypedArray::push(arr, s1 as *const StringObj);
772            TypedArray::push(arr, s2 as *const StringObj);
773            TypedArray::push(arr, s3 as *const StringObj);
774            assert_eq!(TypedArray::len(arr), 3);
775            // drop_array_heap releases per-element shares then dealloc the
776            // buffer + struct.
777            TypedArray::<*const StringObj>::drop_array_heap(arr);
778        }
779    }
780
781    #[test]
782    fn test_drop_array_heap_decimal_obj() {
783        use crate::v2::decimal_obj::DecimalObj;
784        use rust_decimal::Decimal;
785        use rust_decimal::prelude::FromPrimitive;
786        unsafe {
787            let arr: *mut TypedArray<*const DecimalObj> = TypedArray::with_capacity(4);
788            let d1 = DecimalObj::new(Decimal::from_f64(1.5).unwrap());
789            let d2 = DecimalObj::new(Decimal::from_f64(2.5).unwrap());
790            let d3 = DecimalObj::new(Decimal::ZERO);
791            TypedArray::push(arr, d1 as *const DecimalObj);
792            TypedArray::push(arr, d2 as *const DecimalObj);
793            TypedArray::push(arr, d3 as *const DecimalObj);
794            assert_eq!(TypedArray::len(arr), 3);
795            TypedArray::<*const DecimalObj>::drop_array_heap(arr);
796        }
797    }
798
799    #[test]
800    fn test_drop_array_heap_empty() {
801        use crate::v2::string_obj::StringObj;
802        unsafe {
803            // Empty TypedArray (no allocated buffer).
804            let arr: *mut TypedArray<*const StringObj> = TypedArray::new();
805            TypedArray::<*const StringObj>::drop_array_heap(arr);
806        }
807    }
808
809    // ──────────────────────────────────────────────────────────────────────
810    // Wave 2 Agent A1 (2026-05-14) — F32 + Char monomorphization smokes.
811    // ──────────────────────────────────────────────────────────────────────
812
813    #[test]
814    fn test_size_of_typed_array_f32_char() {
815        assert_eq!(std::mem::size_of::<TypedArray<f32>>(), 24);
816        assert_eq!(std::mem::size_of::<TypedArray<char>>(), 24);
817    }
818
819    #[test]
820    fn test_push_and_get_f32() {
821        let arr = TypedArray::<f32>::new();
822        unsafe {
823            TypedArray::push(arr, 1.5_f32);
824            TypedArray::push(arr, 2.25_f32);
825            TypedArray::push(arr, std::f32::consts::PI);
826            assert_eq!(TypedArray::len(arr), 3);
827            assert_eq!(TypedArray::get(arr, 0), Some(1.5_f32));
828            assert_eq!(TypedArray::get(arr, 1), Some(2.25_f32));
829            assert_eq!(TypedArray::get(arr, 2), Some(std::f32::consts::PI));
830            assert_eq!(TypedArray::get(arr, 3), None);
831            TypedArray::drop_array(arr);
832        }
833    }
834
835    #[test]
836    fn test_push_and_get_char() {
837        let arr = TypedArray::<char>::new();
838        unsafe {
839            TypedArray::push(arr, 'a');
840            TypedArray::push(arr, '☃');
841            TypedArray::push(arr, '👋');
842            assert_eq!(TypedArray::len(arr), 3);
843            assert_eq!(TypedArray::get(arr, 0), Some('a'));
844            assert_eq!(TypedArray::get(arr, 1), Some('☃'));
845            assert_eq!(TypedArray::get(arr, 2), Some('👋'));
846            assert_eq!(TypedArray::get(arr, 3), None);
847            TypedArray::drop_array(arr);
848        }
849    }
850
851    #[test]
852    fn test_from_slice_f32() {
853        let data: [f32; 5] = [1.0, 2.0, 3.0, 4.0, 5.0];
854        let arr = TypedArray::from_slice(&data);
855        unsafe {
856            assert_eq!(TypedArray::len(arr), 5);
857            for (i, &expected) in data.iter().enumerate() {
858                assert_eq!(TypedArray::get(arr, i as u32), Some(expected));
859            }
860            TypedArray::drop_array(arr);
861        }
862    }
863
864    #[test]
865    fn test_from_slice_char() {
866        let data = ['h', 'i', '!'];
867        let arr = TypedArray::from_slice(&data);
868        unsafe {
869            assert_eq!(TypedArray::len(arr), 3);
870            for (i, &expected) in data.iter().enumerate() {
871                assert_eq!(TypedArray::get(arr, i as u32), Some(expected));
872            }
873            TypedArray::drop_array(arr);
874        }
875    }
876
877    #[test]
878    fn test_drop_array_heap_with_held_share() {
879        use crate::v2::refcount::{v2_get_refcount, v2_retain};
880        use crate::v2::string_obj::StringObj;
881        unsafe {
882            // Allocate one StringObj with refcount 2 (one for the array, one held
883            // externally). drop_array_heap should decrement to 1, not deallocate.
884            let arr: *mut TypedArray<*const StringObj> = TypedArray::with_capacity(2);
885            let s = StringObj::new("shared");
886            v2_retain(&(*s).header); // refcount = 2
887            TypedArray::push(arr, s as *const StringObj);
888
889            TypedArray::<*const StringObj>::drop_array_heap(arr);
890
891            // External share still valid; refcount should be 1.
892            assert_eq!(v2_get_refcount(&(*s).header), 1);
893            assert_eq!(StringObj::as_str(s), "shared");
894            // Clean up.
895            StringObj::drop(s);
896        }
897    }
898}