From 826e4969aa04914b0924f51850bcc5efded9ffba Mon Sep 17 00:00:00 2001 From: Abel Lucas Date: Mon, 9 Mar 2026 08:15:30 +0100 Subject: [PATCH 1/2] feat: add `array` emitter for `[T; N]` fields Add an `array()` helper that constructs a `[T; N]` from an array of `Emit` builders, enabling fixed-size array fields in derive-generated `make()` factories. Co-Authored-By: Claude Opus 4.6 --- nearest/src/emit.rs | 21 +++++++++++++++++++++ nearest/src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/nearest/src/emit.rs b/nearest/src/emit.rs index 4485159..589e934 100644 --- a/nearest/src/emit.rs +++ b/nearest/src/emit.rs @@ -182,6 +182,27 @@ pub fn none() -> impl Emit>> { W(PhantomData) } +/// Construct a `[T; N]` from an array of `Emit` builders. +/// +/// Used as a wrapper when passing builders to `make()` for `[T; N]` fields: +/// ```ignore +/// Term::make_brif(cond, array([Jmp::make(...), Jmp::make(...)])) +/// ``` +pub fn array, const N: usize>(builders: [B; N]) -> impl Emit<[T; N]> { + struct W([B; N]); + // SAFETY: Writes each element at its correct offset within the [T; N] allocation. + unsafe impl, const N: usize> Emit<[T; N]> for W { + unsafe fn write_at(self, p: &mut impl Patch, at: Pos) { + for (i, builder) in self.0.into_iter().enumerate() { + // SAFETY: `at` was allocated for `[T; N]`. Each element is at + // offset `i * size_of::()` within that allocation. + unsafe { builder.write_at(p, at.offset(i * size_of::())) }; + } + } + } + W(builders) +} + /// Returns an empty iterator suitable for any `NearList` emitter parameter. /// /// Since [`Infallible`](core::convert::Infallible) implements [`Emit`] for all diff --git a/nearest/src/lib.rs b/nearest/src/lib.rs index 086ee9e..201c25c 100644 --- a/nearest/src/lib.rs +++ b/nearest/src/lib.rs @@ -436,7 +436,7 @@ mod validate; #[cfg(feature = "alloc")] pub use buf::AlignedBuf; pub use buf::{Buf, FixedBuf}; -pub use emit::{Emit, empty, list, maybe, near, none}; +pub use emit::{Emit, array, empty, list, maybe, near, none}; /// Not part of the public API. Used by the derive macro. #[doc(hidden)] From 4bff524980dad8a3cd3b8836f78a34b5e5d3582d Mon Sep 17 00:00:00 2001 From: Abel Lucas Date: Mon, 9 Mar 2026 08:33:57 +0100 Subject: [PATCH 2/2] test: add integration tests for `array()` emitter Cover primitive arrays, arrays of Near with nested data, and clone behavior for regions containing array fields. Co-Authored-By: Claude Opus 4.6 --- nearest/tests/integration.rs | 103 ++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/nearest/tests/integration.rs b/nearest/tests/integration.rs index 7167585..b99a3c0 100644 --- a/nearest/tests/integration.rs +++ b/nearest/tests/integration.rs @@ -1,6 +1,8 @@ #![feature(offset_of_enum)] -use nearest::{Flat, Near, NearList, Ref, Region, ValidateError, empty, list, maybe, near, none}; +use nearest::{ + Flat, Near, NearList, Ref, Region, ValidateError, array, empty, list, maybe, near, none, +}; // --- IR type definitions using derive(Flat) --- @@ -71,6 +73,20 @@ enum Wrapper { Pair(CT, u32), } +// Struct with a primitive array field. +#[derive(Flat, Debug)] +struct PrimArray { + tag: u32, + values: [u32; 3], +} + +// Struct with an array of Near fields (requires array() helper). +#[derive(Flat, Debug)] +struct Brif { + cond: u32, + targets: [Near; 2], +} + // =========================================================================== // Builder API tests — zero Pos, zero unsafe, zero Emitter in user code // =========================================================================== @@ -3553,3 +3569,88 @@ fn into_buf_fixed_buf() { // assert_eq!(ob.name, Symbol(1)); // assert!(ob.items.is_none()); // } + +// =========================================================================== +// array() emitter tests +// =========================================================================== + +#[test] +fn array_primitive_field() { + let region: Region = Region::new(PrimArray::make(42, [10u32, 20, 30])); + assert_eq!(region.tag, 42); + assert_eq!(region.values, [10, 20, 30]); +} + +#[test] +fn array_primitive_field_zeros() { + let region: Region = Region::new(PrimArray::make(0, [0u32, 0, 0])); + assert_eq!(region.tag, 0); + assert_eq!(region.values, [0, 0, 0]); +} + +#[test] +fn array_near_field() { + let region: Region = Region::new(Brif::make( + 1, + array([ + near(Block::make(Symbol(10), empty(), empty(), Term::make_ret(empty()))), + near(Block::make(Symbol(20), empty(), empty(), Term::make_ret(empty()))), + ]), + )); + assert_eq!(region.cond, 1); + assert_eq!(region.targets[0].name, Symbol(10)); + assert_eq!(region.targets[1].name, Symbol(20)); +} + +#[test] +fn array_near_field_with_contents() { + let region: Region = Region::new(Brif::make( + 7, + array([ + near(Block::make( + Symbol(1), + empty(), + list([Inst::make(1, Type(0), list([Value::Const(42)]))]), + Term::make_ret(list([Value::Const(100)])), + )), + near(Block::make( + Symbol(2), + empty(), + list([Inst::make(2, Type(1), list([Value::Const(99)]))]), + Term::make_ret(list([Value::Const(200)])), + )), + ]), + )); + assert_eq!(region.cond, 7); + + let b0 = &*region.targets[0]; + assert_eq!(b0.name, Symbol(1)); + assert_eq!(b0.insts.len(), 1); + match &b0.term { + Term::Ret { values } => assert_eq!(values[0], Value::Const(100)), + Term::Jmp(_) => panic!("expected Ret"), + } + + let b1 = &*region.targets[1]; + assert_eq!(b1.name, Symbol(2)); + assert_eq!(b1.insts.len(), 1); + match &b1.term { + Term::Ret { values } => assert_eq!(values[0], Value::Const(200)), + Term::Jmp(_) => panic!("expected Ret"), + } +} + +#[test] +fn array_clone_region() { + let region: Region = Region::new(Brif::make( + 3, + array([ + near(Block::make(Symbol(10), empty(), empty(), Term::make_ret(empty()))), + near(Block::make(Symbol(20), empty(), empty(), Term::make_ret(empty()))), + ]), + )); + let cloned = region.clone(); + assert_eq!(region.cond, cloned.cond); + assert_eq!(cloned.targets[0].name, Symbol(10)); + assert_eq!(cloned.targets[1].name, Symbol(20)); +}