lean_rs_sys/ctor.rs
1//! Constructor objects and polymorphic boxing — Rust mirrors of
2//! `lean.h:642–760` and the `lean_box_uint*` / `lean_box_float` family at
3//! `lean.h:2811–2873`.
4//!
5//! Lean's polymorphic boxing for fixed-width unboxed values (`UInt32` on
6//! 32-bit, `UInt64`, `USize`, `Float`, `Float32`) wraps the value in a
7//! single-field constructor (`tag=0`, `num_objs=0`, scalar payload
8//! sized for the value). The boxed form is the representation an
9//! `Array UIntN` / `Option UIntN` / `Except E UIntN` field carries; the
10//! direct unboxed form is reserved for argument and return positions in
11//! exported Lean functions.
12//!
13//! Each helper here threads through the `lean_alloc_object` extern declared
14//! in [`crate::object`] and writes header bytes via the crate-private
15//! `LeanCtorObjectRepr` layout in `crate::repr`. The pinned
16//! `EXPECTED_HEADER_DIGEST` guarantees the field offsets match the active
17//! `lean.h`.
18
19#![allow(clippy::inline_always)]
20
21use core::mem::size_of;
22
23use crate::object::lean_alloc_object;
24use crate::repr::{LeanCtorObjectRepr, LeanObjectRepr};
25use crate::types::{b_lean_obj_arg, lean_obj_res, lean_object};
26
27/// Write the single-threaded header (`m_rc=1`, `m_tag`, `m_other`) on a
28/// freshly allocated object — Rust mirror of `lean_set_st_header`
29/// (`lean.h:615–623`).
30///
31/// `m_cs_sz` is intentionally **not** touched: under the default mimalloc
32/// build of `libleanshared`, the allocator stores the slot size in that
33/// field via `lean_alloc_object` and overwriting it would corrupt
34/// `lean_object_byte_size`. Without mimalloc, `lean_alloc_object` stores
35/// the size via a prefix word instead, so `m_cs_sz` is unused and leaving
36/// it as the allocator returned it is fine.
37///
38/// # Safety
39///
40/// `o` must point to a freshly allocated, otherwise-uninitialized Lean
41/// heap object whose layout matches [`LeanObjectRepr`].
42#[inline(always)]
43unsafe fn set_st_header(o: *mut lean_object, tag: u8, other: u8) {
44 // SAFETY: precondition above; layout pinned by `EXPECTED_HEADER_DIGEST`.
45 unsafe {
46 let repr = o.cast::<LeanObjectRepr>();
47 (*repr).m_rc = 1;
48 (*repr).m_tag = tag;
49 (*repr).m_other = other;
50 }
51}
52
53/// Number of object-pointer fields stored in a constructor (`lean.h:644`).
54///
55/// # Safety
56///
57/// `o` must be a borrowed Lean constructor object.
58#[inline(always)]
59pub unsafe fn lean_ctor_num_objs(o: b_lean_obj_arg) -> u8 {
60 // SAFETY: precondition above; `m_other` is the num-objs field for ctor
61 // objects (`lean.h:129`).
62 unsafe { crate::object::lean_ptr_other(o) }
63}
64
65/// Pointer to the constructor's object-field storage (`lean.h:649–652`).
66///
67/// # Safety
68///
69/// `o` must be a borrowed Lean constructor object. The returned pointer is
70/// valid for `lean_ctor_num_objs(o)` `*mut lean_object` slots.
71#[inline(always)]
72pub unsafe fn lean_ctor_obj_cptr(o: *mut lean_object) -> *mut *mut lean_object {
73 // SAFETY: precondition above; flexible-array member at fixed offset.
74 unsafe { (&raw mut (*o.cast::<LeanCtorObjectRepr>()).objs).cast::<*mut lean_object>() }
75}
76
77/// Pointer to the constructor's scalar payload storage
78/// (`lean.h:654–657`). Sits immediately past the object-pointer slots.
79///
80/// # Safety
81///
82/// `o` must be a borrowed Lean constructor object whose scalar payload area
83/// is at least `offset + size_of::<T>()` bytes wide for any `offset` the
84/// caller subsequently passes to `lean_ctor_get_*` / `lean_ctor_set_*`.
85#[inline(always)]
86pub unsafe fn lean_ctor_scalar_cptr(o: *mut lean_object) -> *mut u8 {
87 // SAFETY: precondition above; the scalar area starts one element past
88 // the last `*mut lean_object` slot.
89 unsafe {
90 let num_objs = lean_ctor_num_objs(o) as usize;
91 lean_ctor_obj_cptr(o).add(num_objs).cast::<u8>()
92 }
93}
94
95/// Allocate a freshly initialized constructor object — Rust mirror of
96/// `lean_alloc_ctor` (`lean.h:659–664`).
97///
98/// `tag` selects the constructor (`0..=LEAN_MAX_CTOR_TAG`), `num_objs`
99/// names how many object-pointer fields it carries, and `scalar_sz` is the
100/// byte width of the appended scalar payload. The returned object has
101/// `m_rc=1`; the caller subsequently initialises every object field via
102/// [`lean_ctor_obj_cptr`] writes and every scalar field via
103/// `lean_ctor_set_*`.
104///
105/// # Safety
106///
107/// All three sizing parameters must fit `lean.h`'s
108/// `LEAN_MAX_CTOR_TAG` / `LEAN_MAX_CTOR_FIELDS` / `LEAN_MAX_CTOR_SCALARS_SIZE`
109/// ceilings. The caller must fully initialise every declared field before
110/// passing the object to other Lean routines (notably the object-pointer
111/// fields, which Lean's RC machinery will otherwise read as garbage).
112#[inline(always)]
113pub unsafe fn lean_alloc_ctor(tag: u8, num_objs: u8, scalar_sz: usize) -> lean_obj_res {
114 let sz = size_of::<LeanObjectRepr>()
115 .strict_add(size_of::<*mut lean_object>().strict_mul(num_objs as usize))
116 .strict_add(scalar_sz);
117 // SAFETY: `lean_alloc_object` returns a non-null pointer to `sz` bytes of
118 // uninitialised Lean-managed memory; we immediately install the
119 // single-threaded header so any subsequent access through Lean's
120 // predicates sees a well-formed object.
121 unsafe {
122 let o = lean_alloc_object(sz);
123 set_st_header(o, tag, num_objs);
124 o
125 }
126}
127
128/// Box a `u32` as a single-field constructor (`lean.h:2813–2823`).
129///
130/// On 64-bit hosts Lean's `UInt32` is already representable as a
131/// scalar-tagged pointer via [`crate::object::lean_box`]; this helper is
132/// the polymorphic-boxed form needed when a `UInt32` value lands in a
133/// constructor field of an `Array UInt32` / `Option UInt32` / etc.
134///
135/// # Safety
136///
137/// Pure pointer arithmetic plus one `lean_alloc_object` call; no caller
138/// preconditions.
139#[inline(always)]
140pub unsafe fn lean_box_uint32(v: u32) -> lean_obj_res {
141 // SAFETY: ctor allocation is unconditional; we initialise the single
142 // scalar payload before returning.
143 unsafe {
144 let o = lean_alloc_ctor(0, 0, size_of::<u32>());
145 lean_ctor_set_uint32(o, 0, v);
146 o
147 }
148}
149
150/// Recover the `u32` payload from a constructor produced by
151/// [`lean_box_uint32`] (`lean.h:2825–2833`).
152///
153/// # Safety
154///
155/// `o` must be a borrowed constructor object produced by
156/// [`lean_box_uint32`] (or by Lean's compiler in a polymorphic position
157/// holding a `UInt32`). On 64-bit hosts, scalar-tagged `o` is read
158/// directly via [`crate::object::lean_unbox`] instead.
159#[inline(always)]
160pub unsafe fn lean_unbox_uint32(o: b_lean_obj_arg) -> u32 {
161 // SAFETY: precondition above; layout pinned by build digest.
162 unsafe { lean_ctor_get_uint32(o, 0) }
163}
164
165/// Box a `u64` as a single-field constructor (`lean.h:2835–2839`).
166///
167/// # Safety
168///
169/// Same as [`lean_box_uint32`].
170#[inline(always)]
171pub unsafe fn lean_box_uint64(v: u64) -> lean_obj_res {
172 // SAFETY: ctor allocation is unconditional; payload initialised below.
173 unsafe {
174 let o = lean_alloc_ctor(0, 0, size_of::<u64>());
175 lean_ctor_set_uint64(o, 0, v);
176 o
177 }
178}
179
180/// Recover the `u64` payload from a constructor produced by
181/// [`lean_box_uint64`] (`lean.h:2841–2843`).
182///
183/// # Safety
184///
185/// `o` must be a borrowed constructor object produced by
186/// [`lean_box_uint64`] (or by Lean's compiler in a polymorphic position
187/// holding a `UInt64`).
188#[inline(always)]
189pub unsafe fn lean_unbox_uint64(o: b_lean_obj_arg) -> u64 {
190 // SAFETY: precondition above.
191 unsafe { lean_ctor_get_uint64(o, 0) }
192}
193
194/// Box a `usize` as a single-field constructor (`lean.h:2845–2849`).
195///
196/// # Safety
197///
198/// Same as [`lean_box_uint32`].
199#[inline(always)]
200pub unsafe fn lean_box_usize(v: usize) -> lean_obj_res {
201 // SAFETY: ctor allocation is unconditional; payload initialised below.
202 unsafe {
203 let o = lean_alloc_ctor(0, 0, size_of::<usize>());
204 lean_ctor_set_usize(o, 0, v);
205 o
206 }
207}
208
209/// Recover the `usize` payload from a constructor produced by
210/// [`lean_box_usize`] (`lean.h:2851–2853`).
211///
212/// # Safety
213///
214/// `o` must be a borrowed constructor object produced by
215/// [`lean_box_usize`] (or by Lean's compiler in a polymorphic position
216/// holding a `USize`).
217#[inline(always)]
218pub unsafe fn lean_unbox_usize(o: b_lean_obj_arg) -> usize {
219 // SAFETY: precondition above.
220 unsafe { lean_ctor_get_usize(o, 0) }
221}
222
223/// Box an `f64` as a single-field constructor (`lean.h:2855–2859`).
224///
225/// # Safety
226///
227/// Same as [`lean_box_uint32`].
228#[inline(always)]
229pub unsafe fn lean_box_float(v: f64) -> lean_obj_res {
230 // SAFETY: ctor allocation is unconditional; payload initialised below.
231 unsafe {
232 let o = lean_alloc_ctor(0, 0, size_of::<f64>());
233 lean_ctor_set_float(o, 0, v);
234 o
235 }
236}
237
238/// Recover the `f64` payload from a constructor produced by
239/// [`lean_box_float`] (`lean.h:2861–2863`).
240///
241/// # Safety
242///
243/// `o` must be a borrowed constructor object produced by [`lean_box_float`]
244/// (or by Lean's compiler in a polymorphic position holding a `Float`).
245#[inline(always)]
246pub unsafe fn lean_unbox_float(o: b_lean_obj_arg) -> f64 {
247 // SAFETY: precondition above.
248 unsafe { lean_ctor_get_float(o, 0) }
249}
250
251/// Read a `usize` field stored after the object-pointer slots (`lean.h:692`).
252///
253/// # Safety
254///
255/// `o` must be a borrowed constructor object whose scalar payload includes
256/// at least one `usize` at slot `i` (counted in `usize` units past the
257/// object-pointer fields).
258#[inline(always)]
259pub unsafe fn lean_ctor_get_usize(o: b_lean_obj_arg, i: u8) -> usize {
260 // SAFETY: precondition above. The scalar area starts at the first
261 // object-slot pointer, which is `*mut lean_object`-aligned (8 bytes on
262 // 64-bit, 4 on 32-bit) — sufficient for `usize`. `read_unaligned` is
263 // used to mirror C's byte-pointer cast without invoking Rust's
264 // strict-alignment requirement on plain pointer reads.
265 unsafe { lean_ctor_obj_cptr(o).add(i as usize).cast::<usize>().read_unaligned() }
266}
267
268/// Read a `u8` field at byte `offset` within the scalar payload
269/// (`lean.h:697–700`).
270///
271/// # Safety
272///
273/// `o` must be a borrowed constructor object whose scalar payload extends
274/// at least `offset + 1` bytes past the object-pointer slots.
275#[inline(always)]
276pub unsafe fn lean_ctor_get_uint8(o: b_lean_obj_arg, offset: u32) -> u8 {
277 // SAFETY: precondition above; mirrors C's pointer arithmetic verbatim.
278 unsafe { *lean_ctor_scalar_cptr(o).add(offset as usize) }
279}
280
281/// Read a `u16` field at byte `offset` within the scalar payload
282/// (`lean.h:702–705`).
283///
284/// # Safety
285///
286/// Same as [`lean_ctor_get_uint8`]; the caller is expected to use a
287/// naturally aligned `offset`, but `read_unaligned` makes the call sound
288/// even if alignment is off.
289#[inline(always)]
290pub unsafe fn lean_ctor_get_uint16(o: b_lean_obj_arg, offset: u32) -> u16 {
291 // SAFETY: precondition above.
292 unsafe {
293 lean_ctor_scalar_cptr(o)
294 .add(offset as usize)
295 .cast::<u16>()
296 .read_unaligned()
297 }
298}
299
300/// Read a `u32` field at byte `offset` within the scalar payload
301/// (`lean.h:707–710`).
302///
303/// # Safety
304///
305/// Same as [`lean_ctor_get_uint16`].
306#[inline(always)]
307pub unsafe fn lean_ctor_get_uint32(o: b_lean_obj_arg, offset: u32) -> u32 {
308 // SAFETY: precondition above.
309 unsafe {
310 lean_ctor_scalar_cptr(o)
311 .add(offset as usize)
312 .cast::<u32>()
313 .read_unaligned()
314 }
315}
316
317/// Read a `u64` field at byte `offset` within the scalar payload
318/// (`lean.h:712–715`).
319///
320/// # Safety
321///
322/// Same as [`lean_ctor_get_uint16`].
323#[inline(always)]
324pub unsafe fn lean_ctor_get_uint64(o: b_lean_obj_arg, offset: u32) -> u64 {
325 // SAFETY: precondition above.
326 unsafe {
327 lean_ctor_scalar_cptr(o)
328 .add(offset as usize)
329 .cast::<u64>()
330 .read_unaligned()
331 }
332}
333
334/// Read an `f64` field at byte `offset` within the scalar payload
335/// (`lean.h:717–720`).
336///
337/// # Safety
338///
339/// Same as [`lean_ctor_get_uint64`].
340#[inline(always)]
341pub unsafe fn lean_ctor_get_float(o: b_lean_obj_arg, offset: u32) -> f64 {
342 // SAFETY: precondition above.
343 unsafe {
344 lean_ctor_scalar_cptr(o)
345 .add(offset as usize)
346 .cast::<f64>()
347 .read_unaligned()
348 }
349}
350
351/// Write a `usize` field at slot `i` (counted in `usize` units past the
352/// object-pointer slots) — mirror of `lean.h:727–730`.
353///
354/// # Safety
355///
356/// `o` must be a borrowed Lean constructor object whose scalar payload
357/// includes at least one `usize` at slot `i`.
358#[inline(always)]
359pub unsafe fn lean_ctor_set_usize(o: b_lean_obj_arg, i: u8, v: usize) {
360 // SAFETY: precondition above; pointer-aligned write through
361 // `write_unaligned` to match the read-side helper.
362 unsafe { lean_ctor_obj_cptr(o).add(i as usize).cast::<usize>().write_unaligned(v) }
363}
364
365/// Write a `u8` field at byte `offset` within the scalar payload
366/// (`lean.h:732–735`).
367///
368/// # Safety
369///
370/// `o` must be a borrowed Lean constructor object whose scalar payload
371/// extends at least `offset + 1` bytes past the object-pointer slots.
372#[inline(always)]
373pub unsafe fn lean_ctor_set_uint8(o: b_lean_obj_arg, offset: u32, v: u8) {
374 // SAFETY: precondition above.
375 unsafe { *lean_ctor_scalar_cptr(o).add(offset as usize) = v }
376}
377
378/// Write a `u16` field at byte `offset` within the scalar payload
379/// (`lean.h:737–740`).
380///
381/// # Safety
382///
383/// Same as [`lean_ctor_set_uint8`].
384#[inline(always)]
385pub unsafe fn lean_ctor_set_uint16(o: b_lean_obj_arg, offset: u32, v: u16) {
386 // SAFETY: precondition above.
387 unsafe {
388 lean_ctor_scalar_cptr(o)
389 .add(offset as usize)
390 .cast::<u16>()
391 .write_unaligned(v);
392 }
393}
394
395/// Write a `u32` field at byte `offset` within the scalar payload
396/// (`lean.h:742–745`).
397///
398/// # Safety
399///
400/// Same as [`lean_ctor_set_uint16`].
401#[inline(always)]
402pub unsafe fn lean_ctor_set_uint32(o: b_lean_obj_arg, offset: u32, v: u32) {
403 // SAFETY: precondition above.
404 unsafe {
405 lean_ctor_scalar_cptr(o)
406 .add(offset as usize)
407 .cast::<u32>()
408 .write_unaligned(v);
409 }
410}
411
412/// Write a `u64` field at byte `offset` within the scalar payload
413/// (`lean.h:747–750`).
414///
415/// # Safety
416///
417/// Same as [`lean_ctor_set_uint16`].
418#[inline(always)]
419pub unsafe fn lean_ctor_set_uint64(o: b_lean_obj_arg, offset: u32, v: u64) {
420 // SAFETY: precondition above.
421 unsafe {
422 lean_ctor_scalar_cptr(o)
423 .add(offset as usize)
424 .cast::<u64>()
425 .write_unaligned(v);
426 }
427}
428
429/// Write an `f64` field at byte `offset` within the scalar payload
430/// (`lean.h:752–755`).
431///
432/// # Safety
433///
434/// Same as [`lean_ctor_set_uint64`].
435#[inline(always)]
436pub unsafe fn lean_ctor_set_float(o: b_lean_obj_arg, offset: u32, v: f64) {
437 // SAFETY: precondition above.
438 unsafe {
439 lean_ctor_scalar_cptr(o)
440 .add(offset as usize)
441 .cast::<f64>()
442 .write_unaligned(v);
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 //! Round-trip tests over the polymorphic-boxed scalar helpers.
449 //!
450 //! These touch real Lean allocations; they require `libleanshared` to be
451 //! discoverable at run time (the workspace's `build.rs` files bake an
452 //! rpath into the test binary).
453
454 #![allow(clippy::expect_used, clippy::float_cmp)]
455
456 use super::{lean_box_float, lean_box_uint32, lean_box_uint64, lean_box_usize};
457 use super::{lean_unbox_float, lean_unbox_uint32, lean_unbox_uint64, lean_unbox_usize};
458 use crate::init::{lean_initialize, lean_initialize_runtime_module};
459 use crate::io::lean_io_mark_end_initialization;
460 use crate::object::lean_box;
461 use crate::refcount::lean_dec;
462
463 /// Bring the Lean runtime up exactly once for this crate's tests. The
464 /// safe `LeanRuntime` lives in `lean-rs`, which is downstream; here we
465 /// open-code the same one-shot pattern with a local `OnceLock` so this
466 /// crate's tests stay self-contained.
467 fn ensure_runtime() {
468 use std::sync::OnceLock;
469 static INIT: OnceLock<()> = OnceLock::new();
470 INIT.get_or_init(|| {
471 // SAFETY: standard Lean init sequence (`lean.h` "How to use").
472 unsafe {
473 lean_initialize_runtime_module();
474 lean_initialize();
475 lean_io_mark_end_initialization();
476 }
477 });
478 }
479
480 #[test]
481 fn box_unbox_uint64_round_trips() {
482 ensure_runtime();
483 for v in [0_u64, 1, u64::from(u32::MAX), u64::MAX] {
484 // SAFETY: `lean_box_uint64` produces an owned ctor; we read it
485 // back through `lean_unbox_uint64` and release via `lean_dec`.
486 unsafe {
487 let o = lean_box_uint64(v);
488 assert_eq!(lean_unbox_uint64(o), v);
489 lean_dec(o);
490 }
491 }
492 }
493
494 #[test]
495 fn box_unbox_usize_round_trips() {
496 ensure_runtime();
497 for v in [0_usize, 1, usize::MAX] {
498 // SAFETY: same shape as `box_unbox_uint64_round_trips`.
499 unsafe {
500 let o = lean_box_usize(v);
501 assert_eq!(lean_unbox_usize(o), v);
502 lean_dec(o);
503 }
504 }
505 }
506
507 #[test]
508 fn box_unbox_uint32_round_trips() {
509 ensure_runtime();
510 for v in [0_u32, 1, u32::MAX] {
511 // SAFETY: same shape as `box_unbox_uint64_round_trips`.
512 unsafe {
513 let o = lean_box_uint32(v);
514 assert_eq!(lean_unbox_uint32(o), v);
515 lean_dec(o);
516 }
517 }
518 }
519
520 #[test]
521 fn box_unbox_float_round_trips() {
522 ensure_runtime();
523 for v in [0.0_f64, -1.5, core::f64::consts::PI, f64::INFINITY] {
524 // SAFETY: same shape as `box_unbox_uint64_round_trips`.
525 unsafe {
526 let o = lean_box_float(v);
527 assert_eq!(lean_unbox_float(o), v);
528 lean_dec(o);
529 }
530 }
531 // NaN is a separate assertion: `==` is false against itself.
532 // SAFETY: same shape as above.
533 unsafe {
534 let o = lean_box_float(f64::NAN);
535 assert!(lean_unbox_float(o).is_nan());
536 lean_dec(o);
537 }
538 }
539
540 #[test]
541 fn alloc_sarray_round_trips_payload_bytes() {
542 ensure_runtime();
543 use crate::array::{
544 lean_alloc_sarray, lean_sarray_capacity, lean_sarray_cptr, lean_sarray_elem_size, lean_sarray_size,
545 };
546
547 let bytes: &[u8] = b"hello\0world";
548 // SAFETY: allocate a one-byte-element sarray sized to `bytes`, copy
549 // into the storage, then read the header + payload back out.
550 unsafe {
551 let o = lean_alloc_sarray(1, bytes.len(), bytes.len());
552 core::ptr::copy_nonoverlapping(bytes.as_ptr(), lean_sarray_cptr(o), bytes.len());
553
554 assert_eq!(lean_sarray_elem_size(o), 1);
555 assert_eq!(lean_sarray_size(o), bytes.len());
556 assert_eq!(lean_sarray_capacity(o), bytes.len());
557
558 let view = core::slice::from_raw_parts(lean_sarray_cptr(o), lean_sarray_size(o));
559 assert_eq!(view, bytes);
560
561 lean_dec(o);
562 }
563 }
564
565 #[test]
566 fn alloc_sarray_empty_is_valid() {
567 ensure_runtime();
568 use crate::array::{lean_alloc_sarray, lean_sarray_size};
569 // SAFETY: zero-length sarray; allocation succeeds and size reads back
570 // as zero.
571 unsafe {
572 let o = lean_alloc_sarray(1, 0, 0);
573 assert_eq!(lean_sarray_size(o), 0);
574 lean_dec(o);
575 }
576 }
577
578 #[test]
579 fn alloc_array_round_trips_object_slots() {
580 ensure_runtime();
581 use crate::array::{
582 lean_alloc_array, lean_array_capacity, lean_array_get_core, lean_array_set_core, lean_array_size,
583 };
584 use crate::object::{lean_box, lean_is_array, lean_unbox};
585
586 // SAFETY: build an object array of three scalar elements, read each
587 // slot back via `lean_array_get_core`, then release. Scalar
588 // elements skip refcount churn so the test isolates the array
589 // allocator and slot-write path.
590 unsafe {
591 let o = lean_alloc_array(3, 3);
592 assert!(lean_is_array(o));
593 assert_eq!(lean_array_size(o), 3);
594 assert_eq!(lean_array_capacity(o), 3);
595
596 lean_array_set_core(o, 0, lean_box(10));
597 lean_array_set_core(o, 1, lean_box(20));
598 lean_array_set_core(o, 2, lean_box(30));
599
600 assert_eq!(lean_unbox(lean_array_get_core(o, 0)), 10);
601 assert_eq!(lean_unbox(lean_array_get_core(o, 1)), 20);
602 assert_eq!(lean_unbox(lean_array_get_core(o, 2)), 30);
603
604 lean_dec(o);
605 }
606 }
607
608 #[test]
609 fn alloc_array_empty_is_valid() {
610 ensure_runtime();
611 use crate::array::{lean_alloc_array, lean_array_capacity, lean_array_size};
612 use crate::object::lean_is_array;
613
614 // SAFETY: zero-length object array; allocation succeeds and the
615 // size/capacity header reads back as zero. No element slots to
616 // initialise.
617 unsafe {
618 let o = lean_alloc_array(0, 0);
619 assert!(lean_is_array(o));
620 assert_eq!(lean_array_size(o), 0);
621 assert_eq!(lean_array_capacity(o), 0);
622 lean_dec(o);
623 }
624 }
625
626 #[test]
627 fn scalar_box_unbox_remains_inline_for_small_nat() {
628 // Sanity: the existing scalar `lean_box` / `lean_unbox` from
629 // `crate::object` is a distinct path that must not interact with
630 // the ctor-box helpers added here.
631 // SAFETY: scalar-tagged pointer arithmetic only.
632 unsafe {
633 let o = lean_box(42);
634 assert_eq!(crate::object::lean_unbox(o), 42);
635 lean_dec(o);
636 }
637 }
638}