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