Skip to main content

shape_runtime/
marshal.rs

1//! Strict-typed marshal layer for native module function dispatch.
2//!
3//! Replaces the deleted `Fn(&[ValueWord], &ModuleContext) -> Result<ValueWord>`
4//! body shape (the dynamic-FFI escape hatch). Native function bodies now
5//! take **typed Rust arguments** that implement [`FromSlot`]; the function's
6//! Rust signature *is* the typed signature, and the marshal layer cannot be
7//! registered against mismatching kinds because the Rust trait system rejects
8//! the [`register_typed_fn_N`] generic constraints.
9//!
10//! Mirrors the structural-enforcement track from Phase 2a: forbidden
11//! mismatches are unrepresentable, not just unreachable. See
12//! `docs/defections.md` 2026-05-06 (Phase 2b unified marshal + wire/snapshot).
13//!
14//! ## What's here
15//!
16//! - [`FromSlot`] / [`ToSlot`]: read/write a typed value from/to an 8-byte
17//!   `u64` slot. Each impl pins a single [`NativeKind`] via the associated
18//!   constant.
19//! - [`MarshalError`]: typed error returned by the marshal boundary.
20//! - [`register_typed_fn_0`] … [`register_typed_fn_3`]: per-arity
21//!   registration helpers. Each wraps a body whose Rust parameter types
22//!   carry the typed argument contract (each `Pi: FromSlot`).
23//!
24//! ## What's not here yet
25//!
26//! - Higher-arity helpers (4+) — added on demand when stdlib migrations need them.
27//! - `ToSlot` for container `TypedReturn` variants (`Ok`/`Err`/`Some`/
28//!   `ObjectPairs`/etc.) — these need monomorphized heap representations
29//!   and land alongside the per-stdlib-module migrations in Phase 2c.
30
31use crate::module_exports::ModuleContext;
32use crate::typed_module_exports::TypedReturn;
33use shape_value::NativeKind;
34use std::sync::Arc;
35
36/// Read a typed value from an 8-byte raw-bits slot.
37///
38/// The associated constant [`Self::NATIVE_KIND`] declares which kind
39/// the slot must have. The marshal-layer dispatcher guarantees the
40/// contract by reading `arg_kinds()` at registration and only invoking
41/// the body with matching slot bits — `from_slot` impls therefore do
42/// not invoke the deleted `tag_bits` dispatch.
43pub trait FromSlot: Sized {
44    const NATIVE_KIND: NativeKind;
45    /// SAFETY contract (enforced by the marshal-layer wrapper, not by
46    /// this trait method): `bits` must have been produced by a slot
47    /// that was statically proven to have kind `NATIVE_KIND`.
48    fn from_slot(bits: u64) -> Self;
49}
50
51/// Write a typed value into an 8-byte raw-bits slot.
52///
53/// Symmetric to [`FromSlot`]. Used by per-arity registration helpers
54/// when the body returns a primitive-typed value directly. Container
55/// `TypedReturn` variants (`Ok`/`Err`/`Some`/`ObjectPairs`/etc.)
56/// don't impl `ToSlot` — they're projected by the dispatcher's
57/// `TypedReturn → slot push` step (Phase 2c per-module migrations).
58pub trait ToSlot {
59    const NATIVE_KIND: NativeKind;
60    fn to_slot(self) -> u64;
61}
62
63/// Typed error returned at the marshal boundary.
64///
65/// Replaces panics from the deleted `into_value_word()` boundary. The
66/// dispatcher converts `MarshalError` into a `Result<TypedReturn, String>`
67/// at the registry edge so legacy `String`-error paths keep working
68/// during the migration.
69#[derive(Debug, Clone, PartialEq)]
70pub enum MarshalError {
71    /// Arg count mismatch between the function's registered arity and
72    /// the slot slice handed in by the dispatcher.
73    ArgCount { expected: usize, got: usize },
74    /// The body returned an `Err(String)` — surfaced verbatim.
75    Body(String),
76}
77
78impl std::fmt::Display for MarshalError {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            MarshalError::ArgCount { expected, got } => {
82                write!(f, "expected {} arg(s), got {}", expected, got)
83            }
84            MarshalError::Body(msg) => f.write_str(msg),
85        }
86    }
87}
88
89impl std::error::Error for MarshalError {}
90
91impl From<MarshalError> for String {
92    fn from(e: MarshalError) -> Self {
93        e.to_string()
94    }
95}
96
97// ───────────────────────────── FromSlot impls ─────────────────────────────
98
99impl FromSlot for i64 {
100    const NATIVE_KIND: NativeKind = NativeKind::Int64;
101    #[inline]
102    fn from_slot(bits: u64) -> Self {
103        bits as i64
104    }
105}
106
107impl FromSlot for f64 {
108    const NATIVE_KIND: NativeKind = NativeKind::Float64;
109    #[inline]
110    fn from_slot(bits: u64) -> Self {
111        f64::from_bits(bits)
112    }
113}
114
115// NaN-sentinel discrimination matches NullableFloat64's documented contract
116// (native_kind.rs:36). Reusing an already-declared sentinel kind is consumer-side
117// adoption, not a new sentinel introduction.
118impl FromSlot for Option<f64> {
119    const NATIVE_KIND: NativeKind = NativeKind::NullableFloat64;
120    #[inline]
121    fn from_slot(bits: u64) -> Self {
122        let v = f64::from_bits(bits);
123        if v.is_nan() {
124            None
125        } else {
126            Some(v)
127        }
128    }
129}
130
131impl FromSlot for bool {
132    const NATIVE_KIND: NativeKind = NativeKind::Bool;
133    #[inline]
134    fn from_slot(bits: u64) -> Self {
135        bits != 0
136    }
137}
138
139/// Read an `Arc<String>` from a heap-pointer slot.
140///
141/// The slot owns one strong reference; cloning it for the body's use
142/// requires incrementing the refcount. The marshal wrapper does not
143/// take ownership of the slot — it stays valid for the duration of
144/// the call. The body receives an independent strong reference.
145impl FromSlot for Arc<String> {
146    const NATIVE_KIND: NativeKind = NativeKind::String;
147    #[inline]
148    fn from_slot(bits: u64) -> Self {
149        let ptr = bits as *const String;
150        // SAFETY: NATIVE_KIND::String pins this slot to an Arc<String>
151        // raw pointer produced by `Arc::into_raw` at write time. The
152        // dispatcher guarantees kind match via the Phase 2b registration
153        // contract.
154        unsafe {
155            Arc::increment_strong_count(ptr);
156            Arc::from_raw(ptr)
157        }
158    }
159}
160
161// ───────────────────────────── ToSlot impls ─────────────────────────────
162
163impl ToSlot for i64 {
164    const NATIVE_KIND: NativeKind = NativeKind::Int64;
165    #[inline]
166    fn to_slot(self) -> u64 {
167        self as u64
168    }
169}
170
171impl ToSlot for f64 {
172    const NATIVE_KIND: NativeKind = NativeKind::Float64;
173    #[inline]
174    fn to_slot(self) -> u64 {
175        self.to_bits()
176    }
177}
178
179impl ToSlot for bool {
180    const NATIVE_KIND: NativeKind = NativeKind::Bool;
181    #[inline]
182    fn to_slot(self) -> u64 {
183        self as u64
184    }
185}
186
187impl ToSlot for Arc<String> {
188    const NATIVE_KIND: NativeKind = NativeKind::String;
189    #[inline]
190    fn to_slot(self) -> u64 {
191        Arc::into_raw(self) as u64
192    }
193}
194
195// ──────────────────── heap-pointer FromSlot/ToSlot ────────────────────
196//
197// Heap-allocated stdlib returns and slot reads project through
198// `Arc<HeapValue>`. The slot bits are an `Arc<HeapValue>` raw pointer;
199// the kind (`NativeKind::Ptr(HeapKind::*)`) tells the dispatcher which
200// `HeapValue` arm decodes the bits without probing the object's
201// self-reported discriminant.
202//
203// Body-side helpers below construct typed return values from the inner
204// Rust types (`Arc<DataTable>`, `Arc<Instant>`, etc.) by wrapping in
205// `HeapValue::*` then `Arc::new`. Reading goes the other way: cast bits
206// to `*const HeapValue`, pattern-match the expected arm.
207
208/// Read the inner `Arc<DataTable>` from a `NativeKind::Ptr(HeapKind::DataTable)` slot.
209impl FromSlot for Arc<shape_value::DataTable>
210where
211    Self: Sized,
212{
213    const NATIVE_KIND: NativeKind =
214        NativeKind::Ptr(shape_value::HeapKind::DataTable);
215    #[inline]
216    fn from_slot(bits: u64) -> Self {
217        let ptr = bits as *const shape_value::HeapValue;
218        // SAFETY: NATIVE_KIND::Ptr(HeapKind::DataTable) pins the bits to
219        // an Arc<HeapValue> with the DataTable variant. We clone the
220        // inner Arc<DataTable> without consuming the slot's strong ref.
221        unsafe {
222            Arc::increment_strong_count(ptr);
223            let arc_hv = Arc::from_raw(ptr);
224            match &*arc_hv {
225                shape_value::HeapValue::DataTable(arc_dt) => Arc::clone(arc_dt),
226                other => panic!(
227                    "FromSlot<Arc<DataTable>>: slot bits decoded to HeapValue::{:?}, \
228                     not DataTable. Marshal kind contract violated by caller.",
229                    other.kind()
230                ),
231            }
232        }
233    }
234}
235
236/// Write an `Arc<DataTable>` into a heap slot by wrapping in
237/// `HeapValue::DataTable` and producing the raw `Arc<HeapValue>` pointer.
238impl ToSlot for Arc<shape_value::DataTable> {
239    const NATIVE_KIND: NativeKind =
240        NativeKind::Ptr(shape_value::HeapKind::DataTable);
241    #[inline]
242    fn to_slot(self) -> u64 {
243        let hv = Arc::new(shape_value::HeapValue::DataTable(self));
244        Arc::into_raw(hv) as u64
245    }
246}
247
248// ────────────────────── IoHandle FromSlot/ToSlot (option γ) ───────────────
249//
250// Cluster #2 (docs/defections.md 2026-05-06): IoHandle marshal extension
251// via Arc<IoHandleData>. Mirrors the Arc<DataTable> shape exactly.
252//
253// `HeapValue::IoHandle` payload was changed from Box<IoHandleData> to
254// Arc<IoHandleData> in the prior commit specifically so the FromSlot
255// projection here is one atomic op (Arc::clone of the inner Arc) rather
256// than a Box clone (alloc + memcpy). Bodies declare
257// `handle: Arc<IoHandleData>` and call methods on it via Arc::deref —
258// `handle.is_open()`, `handle.close()`, `handle.resource.lock()`.
259//
260// Same consistency-check residual as Arc<DataTable> at marshal.rs:193 —
261// the body's `Arc<IoHandleData>` parameter type pins the expected
262// `HeapValue::IoHandle` variant; the panic-on-mismatch arm is
263// unreachable in a well-typed system per
264// `docs/runtime-v2-spec.md` ("consistency check, not probe").
265
266/// Read the inner `Arc<IoHandleData>` from a `NativeKind::Ptr(HeapKind::IoHandle)` slot.
267impl FromSlot for Arc<shape_value::heap_value::IoHandleData>
268where
269    Self: Sized,
270{
271    const NATIVE_KIND: NativeKind =
272        NativeKind::Ptr(shape_value::HeapKind::IoHandle);
273    #[inline]
274    fn from_slot(bits: u64) -> Self {
275        let ptr = bits as *const shape_value::HeapValue;
276        // SAFETY: NATIVE_KIND::Ptr(HeapKind::IoHandle) pins the bits to
277        // an Arc<HeapValue> with the IoHandle variant. We clone the
278        // inner Arc<IoHandleData> without consuming the slot's strong ref.
279        unsafe {
280            Arc::increment_strong_count(ptr);
281            let arc_hv = Arc::from_raw(ptr);
282            match &*arc_hv {
283                shape_value::HeapValue::IoHandle(arc_io) => Arc::clone(arc_io),
284                other => panic!(
285                    "FromSlot<Arc<IoHandleData>>: slot bits decoded to HeapValue::{:?}, \
286                     not IoHandle. Marshal kind contract violated by caller.",
287                    other.kind()
288                ),
289            }
290        }
291    }
292}
293
294/// Write an `Arc<IoHandleData>` into a heap slot by wrapping in
295/// `HeapValue::IoHandle` and producing the raw `Arc<HeapValue>` pointer.
296impl ToSlot for Arc<shape_value::heap_value::IoHandleData> {
297    const NATIVE_KIND: NativeKind =
298        NativeKind::Ptr(shape_value::HeapKind::IoHandle);
299    #[inline]
300    fn to_slot(self) -> u64 {
301        let hv = Arc::new(shape_value::HeapValue::IoHandle(self));
302        Arc::into_raw(hv) as u64
303    }
304}
305
306// ──────────────────── typed-array FromSlot/ToSlot (option β) ─────────────────
307//
308// V3-S5 ckpt-5-prime²c (2026-05-15): the `HeapValue::TypedArray` outer arm +
309// `TypedArrayData` enum + `TypedBuffer<T>` / `AlignedTypedBuffer` wrapper
310// layer were retired wholesale at V3-S5 ckpt-1..ckpt-5 per
311// `docs/cluster-audits/w12-typed-array-data-deletion-audit.md` §3.5/§3.6 +
312// audit §B + ADR-006 §2.7.24 Q25.A SUPERSEDED. The strict-typed array
313// carrier is now the monomorphic flat-struct `*mut TypedArray<T>` shape per
314// `docs/runtime-v2-spec.md`; slot bits for `NativeKind::Ptr(HeapKind::
315// TypedArray)` are a raw pointer to a `crate::v2::typed_array::TypedArray<T>`
316// for the element-width `T` that the body declares.
317//
318// Element-width discrimination is via the body's declared parameter type
319// (`Vec<u8>` vs `Vec<i64>` vs `Vec<f64>` vs `Vec<Arc<String>>`, or their
320// `Arc<Vec<T>>` wrappers), not via NativeKind: `NATIVE_KIND` stays
321// `Ptr(HeapKind::TypedArray)` for all element widths. Element-width threading
322// is enforced by the Rust type system at the impl level, with an unsafe
323// raw-pointer read of the matching `TypedArray<T>::as_slice` that copies
324// elements into a fresh `Vec<T>` (owns-clone semantics) or wraps in
325// `Arc<Vec<T>>` (zero-copy of the inner `Vec`, one `Arc::new` for the outer).
326//
327// Per `docs/runtime-v2-spec.md`: "the kind tells you the arm; the body's
328// declared parameter type tells you the element width; no runtime
329// element-width probe." The dispatcher's registration-time arg-kind contract
330// already verified the slot bits decode to a `HeapKind::TypedArray` raw
331// pointer; the per-`T` impl picks the element width via the Rust type
332// system. If a slot's actual element-width disagrees with the impl's
333// declared `T`, the result is UB by design (compiler/dispatcher contract
334// violation), not a panic — same as the post-strict-typing dispatch
335// contract for typed slots in general.
336//
337// V3-S5 ckpt-5-prime²c migration shape (a) RATIFIED:
338//   `Arc<AlignedTypedBuffer>` → `Arc<Vec<f64>>`  (intrinsics body-type)
339//   `Arc<TypedBuffer<i64>>`   → `Arc<Vec<i64>>`  (intrinsics body-type)
340//   `Arc<TypedBuffer<u8>>`    → `Arc<Vec<u8>>`   (intrinsics body-type)
341//   `Arc<TypedBuffer<Arc<String>>>` → `Arc<Vec<Arc<String>>>`  (not yet
342//     reached by intrinsics; kept aligned to the same shape for the future
343//     string-cluster migration when a stdlib body surfaces).
344//
345// The `Vec<Arc<HeapValue>>` polymorphic-element marshal path is
346// surface-and-stop in this checkpoint: the `materialize_heap_arcs` helper
347// that re-wrapped each strict-typed element into a `HeapValue::*` Arc
348// referenced the deleted `TypedArrayData` enum directly. Stdlib bodies
349// declaring `Vec<Arc<HeapValue>>` parameters cannot decode the new
350// `*mut TypedArray<T>` slot bits without a per-`T` dispatcher (Round 2
351// follow-up — pairs with the `from_typed_array_<T>` constructor wave at
352// `crates/shape-value/src/slot.rs:142`). Active impl panics with a
353// structured error pointing at the follow-up.
354
355/// Read a `Vec<u8>` from a `NativeKind::Ptr(HeapKind::TypedArray)` slot
356/// whose payload is `*mut TypedArray<u8>`.
357///
358/// V3-S5 ckpt-5-prime²c (2026-05-15): rewritten for the v2-raw flat-struct
359/// carrier. Slot bits are a raw `*mut TypedArray<u8>` pointer; element-data
360/// is copied into a fresh `Vec<u8>` (owns-clone semantics — body receives
361/// an owned vector independent of the slot's refcount share).
362impl FromSlot for Vec<u8> {
363    const NATIVE_KIND: NativeKind =
364        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
365    #[inline]
366    fn from_slot(bits: u64) -> Self {
367        // SAFETY: NATIVE_KIND::Ptr(HeapKind::TypedArray) + body-declared
368        // element type Vec<u8> pins the slot bits to a live
369        // *mut TypedArray<u8>. The marshal kind contract guarantees both;
370        // dispatcher-side stamp_elem_type at array.rs:78 carries the
371        // element discriminant for completeness but the body type is the
372        // primary discriminator per the post-strict-typing contract.
373        let arr = bits as usize as *const shape_value::v2::typed_array::TypedArray<u8>;
374        if arr.is_null() {
375            return Vec::new();
376        }
377        unsafe {
378            shape_value::v2::typed_array::TypedArray::<u8>::as_slice(arr).to_vec()
379        }
380    }
381}
382
383/// Read a `Vec<i64>` from a `NativeKind::Ptr(HeapKind::TypedArray)` slot
384/// whose payload is `*mut TypedArray<i64>`. V3-S5 ckpt-5-prime²c
385/// (2026-05-15): rewritten for the v2-raw flat-struct carrier. Owns-clone
386/// semantics.
387impl FromSlot for Vec<i64> {
388    const NATIVE_KIND: NativeKind =
389        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
390    #[inline]
391    fn from_slot(bits: u64) -> Self {
392        // SAFETY: see Vec<u8>::from_slot above.
393        let arr = bits as usize as *const shape_value::v2::typed_array::TypedArray<i64>;
394        if arr.is_null() {
395            return Vec::new();
396        }
397        unsafe {
398            shape_value::v2::typed_array::TypedArray::<i64>::as_slice(arr).to_vec()
399        }
400    }
401}
402
403/// Read a `Vec<Arc<String>>` from a `NativeKind::Ptr(HeapKind::TypedArray)`
404/// slot whose payload is `*mut TypedArray<*const StringObj>`. V3-S5
405/// ckpt-5-prime²c (2026-05-15): rewritten for the v2-raw flat-struct
406/// carrier (each element is a raw `*const StringObj` — the per-element
407/// allocator-managed v2 string carrier per `crates/shape-value/src/v2/
408/// string_obj.rs`). Each element string is copied into a fresh
409/// `Arc<String>` (owns-clone semantics — body receives an owned vector
410/// of independent Arcs).
411impl FromSlot for Vec<Arc<String>> {
412    const NATIVE_KIND: NativeKind =
413        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
414    #[inline]
415    fn from_slot(bits: u64) -> Self {
416        // SAFETY: see Vec<u8>::from_slot above. The body-declared element
417        // type pins the slot to a *mut TypedArray<*const StringObj>.
418        let arr = bits as usize
419            as *const shape_value::v2::typed_array::TypedArray<
420                *const shape_value::v2::string_obj::StringObj,
421            >;
422        if arr.is_null() {
423            return Vec::new();
424        }
425        unsafe {
426            let slice = shape_value::v2::typed_array::TypedArray::<
427                *const shape_value::v2::string_obj::StringObj,
428            >::as_slice(arr);
429            slice
430                .iter()
431                .map(|&p| {
432                    Arc::new(
433                        shape_value::v2::string_obj::StringObj::as_str(p).to_owned(),
434                    )
435                })
436                .collect()
437        }
438    }
439}
440
441/// Read a `Vec<Arc<HeapValue>>` from a `NativeKind::Ptr(HeapKind::TypedArray)`
442/// slot.
443///
444/// V3-S5 ckpt-5-prime²c (2026-05-15) SURFACE-AND-STOP: the
445/// `materialize_heap_arcs` helper (deleted alongside this comment block)
446/// re-wrapped each strict-typed element into a `HeapValue::*` Arc by
447/// pattern-matching the deleted `TypedArrayData` enum. The new
448/// `*mut TypedArray<T>` flat-struct carrier needs a per-`T` dispatcher
449/// (one impl per element width: `*const StringObj`, `*const DecimalObj`,
450/// `TypedObjectPtr`, char, etc.) plus a parallel element-kind discriminator
451/// in the marshal layer to know which `T` the slot was constructed for.
452/// The element discriminator already exists at the VM level
453/// (`stamp_elem_type` at `crates/shape-vm/src/executor/v2_handlers/array.rs`)
454/// but isn't yet exposed at the marshal-`FromSlot` boundary.
455///
456/// Adding this dispatcher is the Round 2 `Vec<Arc<HeapValue>>` rewire
457/// follow-up; pairs with the `from_typed_array_<T>` constructor wave at
458/// `crates/shape-value/src/slot.rs:142` and the matching ckpt-6 JIT FFI
459/// String/Decimal build work. Until that lands, the marshal-FromSlot panics
460/// with a structured error pointing at the follow-up — any stdlib body
461/// declaring `Vec<Arc<HeapValue>>` and reaching this from_slot is currently
462/// dead at the marshal boundary (consistent with V3-S5 ckpt-5's wholesale
463/// `HeapValue::TypedArray` outer-arm deletion).
464impl FromSlot for Vec<Arc<shape_value::heap_value::HeapValue>> {
465    const NATIVE_KIND: NativeKind =
466        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
467    #[inline]
468    fn from_slot(_bits: u64) -> Self {
469        panic!(
470            "FromSlot<Vec<Arc<HeapValue>>>: V3-S5 ckpt-5-prime²c SURFACE — \
471             the polymorphic Vec<Arc<HeapValue>> marshal path needs a \
472             per-element-T dispatcher over the v2-raw *mut TypedArray<T> \
473             carrier. Round 2 `Vec<Arc<HeapValue>>` rewire follow-up \
474             (pairs with from_typed_array_<T> constructor wave). \
475             ADR-006 §2.7.24 Q25.A SUPERSEDED."
476        )
477    }
478}
479
480/// Project a `Vec<Arc<String>>` into a `NativeKind::Ptr(HeapKind::TypedArray)`
481/// slot whose payload is `*mut TypedArray<*const StringObj>`. V3-S5
482/// ckpt-5-prime²c (2026-05-15): rewritten for the v2-raw flat-struct
483/// carrier — each input `Arc<String>` is allocated as a fresh `StringObj`
484/// with refcount=1, and the per-element pointers are packed into a new
485/// `TypedArray<*const StringObj>`. The slot takes ownership of the
486/// resulting raw pointer (refcount discipline goes through `v2_retain` /
487/// `v2_release` per `HeapHeader`).
488impl ToSlot for Vec<Arc<String>> {
489    const NATIVE_KIND: NativeKind =
490        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
491    #[inline]
492    fn to_slot(self) -> u64 {
493        let elems: Vec<*const shape_value::v2::string_obj::StringObj> = self
494            .into_iter()
495            .map(|s| {
496                shape_value::v2::string_obj::StringObj::new(s.as_str())
497                    as *const shape_value::v2::string_obj::StringObj
498            })
499            .collect();
500        let arr = shape_value::v2::typed_array::TypedArray::<
501            *const shape_value::v2::string_obj::StringObj,
502        >::from_slice(&elems);
503        arr as usize as u64
504    }
505}
506
507/// Project a `Vec<Arc<HeapValue>>` into a `NativeKind::Ptr(HeapKind::TypedArray)`
508/// slot.
509///
510/// V3-S5 ckpt-5-prime²c (2026-05-15) SURFACE-AND-STOP: same per-element-T
511/// dispatcher gap as the `FromSlot<Vec<Arc<HeapValue>>>` reader above.
512/// The pre-deletion `build_specialized_from_heap_arcs` helper dispatched
513/// each `HeapValue` arm into the matching `TypedArrayData::*` variant; the
514/// new flat-struct carrier needs to pick the matching
515/// `TypedArray<T>::from_slice` instantiation per element kind and
516/// stamp the element discriminator before push. Pairs with the
517/// Round 2 follow-up cited in the FromSlot impl.
518impl ToSlot for Vec<Arc<shape_value::heap_value::HeapValue>> {
519    const NATIVE_KIND: NativeKind =
520        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
521    #[inline]
522    fn to_slot(self) -> u64 {
523        panic!(
524            "ToSlot<Vec<Arc<HeapValue>>>: V3-S5 ckpt-5-prime²c SURFACE — \
525             polymorphic Vec<Arc<HeapValue>> projection needs a per-element-T \
526             dispatcher over the v2-raw *mut TypedArray<T> carrier (mirror \
527             of the deleted build_specialized_from_heap_arcs). Round 2 \
528             `Vec<Arc<HeapValue>>` rewire follow-up. ADR-006 §2.7.24 Q25.A \
529             SUPERSEDED."
530        )
531    }
532}
533
534// ────── HashMap FromSlot impls (Stage C P1(b), 2026-05-07) ──────
535//
536// Stage C HashMap-marshal P1(b) per supervisor sign-off
537// (`docs/defections.md` 2026-05-07 HashMap-marshal entry +
538// audit-grounded correction subsection).
539//
540// Two `FromSlot` impls cover the dynamic-keys consumer surface (8 of 9
541// stdlib body cases per Audit-1):
542//
543//   `Vec<(Arc<String>, Arc<String>)>`     — string-string maps (csv.parse_records,
544//                                            csv.stringify_records, http inner
545//                                            headers, xml attributes)
546//   `Vec<(Arc<String>, Arc<HeapValue>)>`  — polymorphic-value maps (json
547//                                            Json::Object, yaml, toml, msgpack,
548//                                            xml node, http options arg)
549//
550// `NATIVE_KIND` stays `Ptr(HeapKind::HashMap)` for both — the value-element
551// width discriminator lives in the body-side Rust type (option ε pattern),
552// not in slot bits or `NativeKind`. Same consistency-check residual as
553// Phase 2d Array's `Vec<Arc<String>>`/`Vec<Arc<HeapValue>>` impls: the
554// in-body pattern match panics on a wrong inner-element shape (currently
555// any HashMap stores `Arc<HeapValue>` values; the string-string variant
556// pattern-matches each value as `HeapValue::String(_)` and unwraps).
557//
558// **No `ToSlot` impls in this commit** per supervisor instruction. Body
559// returns of `ConcreteReturn::HashMapStringString` /
560// `ConcreteReturn::HashMapStringHeapValue` are projected via the
561// shape-vm dispatcher's `ConcreteReturn → slot push` path (shape-vm
562// cleanup workstream territory, not Stage C scope). Adding `ToSlot`
563// impls now would create dead-at-marshal-layer trait surface (per X4
564// finding) AND specifically refused for `HashMapData` per supervisor
565// sign-off ("no direct ToSlot for HashMapData; route through
566// ConcreteReturn::HashMapStringHeapValue dispatch").
567
568/// Read a `Vec<(Arc<String>, Arc<String>)>` from a
569/// `NativeKind::Ptr(HeapKind::HashMap)` slot.
570///
571/// Body-type pattern: bodies declaring `args: Vec<(Arc<String>, Arc<String>)>`
572/// receive an owned pair-list with insertion order preserved. Each value is
573/// expected to be `HeapValue::String(_)`; mismatch panics as the
574/// spec-permitted consistency check (`docs/runtime-v2-spec.md`).
575impl FromSlot for Vec<(Arc<String>, Arc<String>)> {
576    const NATIVE_KIND: NativeKind =
577        NativeKind::Ptr(shape_value::HeapKind::HashMap);
578    #[inline]
579    fn from_slot(bits: u64) -> Self {
580        let ptr = bits as *const shape_value::HeapValue;
581        // SAFETY: see Vec<u8>::from_slot above — slot bits were proven by
582        // the dispatcher to point to a valid `Arc<HeapValue>`.
583        unsafe {
584            Arc::increment_strong_count(ptr);
585            let arc_hv = Arc::from_raw(ptr);
586            match &*arc_hv {
587                shape_value::HeapValue::HashMap(kref) => {
588                    // Wave 2 Round 3b C2-joint ckpt-4 (2026-05-14): per-V
589                    // walk for HashMap<string, string> (V=String). Other V
590                    // variants panic — the marshal contract says caller
591                    // declared a string-valued map; non-string V is a
592                    // construction-side type error.
593                    use shape_value::heap_value::HashMapKindedRef;
594                    match kref {
595                        HashMapKindedRef::String(arc) => {
596                            let n = arc.len();
597                            let mut out: Vec<(Arc<String>, Arc<String>)> =
598                                Vec::with_capacity(n);
599                            for i in 0..n {
600                                let key = unsafe {
601                                    let ptr = shape_value::v2::typed_array::TypedArray::get_unchecked(
602                                        arc.keys, i as u32,
603                                    );
604                                    Arc::new(
605                                        shape_value::v2::string_obj::StringObj::as_str(ptr)
606                                            .to_owned(),
607                                    )
608                                };
609                                let val = unsafe {
610                                    let v_ptr: *const shape_value::v2::string_obj::StringObj =
611                                        *(*arc.values).data.add(i);
612                                    Arc::new(
613                                        shape_value::v2::string_obj::StringObj::as_str(v_ptr)
614                                            .to_owned(),
615                                    )
616                                };
617                                out.push((key, val));
618                            }
619                            out
620                        }
621                        other => panic!(
622                            "FromSlot<Vec<(Arc<String>, Arc<String>)>>: HashMap V \
623                             variant {:?} not supported — marshal contract requires \
624                             V=String. ADR-006 §2.7.24 Q25.B SUPERSEDED.",
625                            other.values_kind()
626                        ),
627                    }
628                }
629                other => panic!(
630                    "FromSlot<Vec<(Arc<String>, Arc<String>)>>: slot bits decoded to \
631                     HeapValue::{:?}, not HashMap. Marshal kind contract violated by caller.",
632                    other.kind()
633                ),
634            }
635        }
636    }
637}
638
639/// Read a `Vec<(Arc<String>, Arc<HeapValue>)>` from a
640/// `NativeKind::Ptr(HeapKind::HashMap)` slot.
641///
642/// Body-type pattern: bodies declaring
643/// `args: Vec<(Arc<String>, Arc<HeapValue>)>` receive an owned pair-list
644/// with insertion order preserved and polymorphic-typed values. Each
645/// element is an opaque `Arc<HeapValue>`; the body is responsible for
646/// pattern-matching the inner kind per the option ε contract. No
647/// element-kind constraint at the marshal boundary.
648impl FromSlot for Vec<(Arc<String>, Arc<shape_value::heap_value::HeapValue>)> {
649    const NATIVE_KIND: NativeKind =
650        NativeKind::Ptr(shape_value::HeapKind::HashMap);
651    #[inline]
652    fn from_slot(bits: u64) -> Self {
653        let ptr = bits as *const shape_value::HeapValue;
654        // SAFETY: see Vec<u8>::from_slot above.
655        unsafe {
656            Arc::increment_strong_count(ptr);
657            let arc_hv = Arc::from_raw(ptr);
658            match &*arc_hv {
659                shape_value::HeapValue::HashMap(kref) => {
660                    // Wave 2 Round 3b C2-joint ckpt-4 (2026-05-14): per-V
661                    // walk → `Vec<(Arc<String>, Arc<HeapValue>)>` for the
662                    // polymorphic-valued marshal path. Each per-V slot
663                    // projects into the canonical `Arc<HeapValue>` arm.
664                    use shape_value::heap_value::{HashMapKindedRef, HeapValue};
665                    let n = kref.len();
666                    let mut out: Vec<(Arc<String>, Arc<HeapValue>)> = Vec::with_capacity(n);
667                    let keys_ptr = match kref {
668                        HashMapKindedRef::I64(arc) => arc.keys,
669                        HashMapKindedRef::F64(arc) => arc.keys,
670                        HashMapKindedRef::Bool(arc) => arc.keys,
671                        HashMapKindedRef::Char(arc) => arc.keys,
672                        HashMapKindedRef::String(arc) => arc.keys,
673                        HashMapKindedRef::Decimal(arc) => arc.keys,
674                        HashMapKindedRef::TypedObject(arc) => arc.keys,
675                        HashMapKindedRef::TraitObject(arc) => arc.keys,
676                        HashMapKindedRef::HashMap(arc) => arc.keys,
677                    };
678                    for i in 0..n {
679                        let key = unsafe {
680                            let ptr = shape_value::v2::typed_array::TypedArray::get_unchecked(
681                                keys_ptr, i as u32,
682                            );
683                            Arc::new(
684                                shape_value::v2::string_obj::StringObj::as_str(ptr)
685                                    .to_owned(),
686                            )
687                        };
688                        let value: Arc<HeapValue> = match kref {
689                            HashMapKindedRef::I64(arc) => {
690                                let v: i64 = unsafe { *(*arc.values).data.add(i) };
691                                Arc::new(HeapValue::BigInt(Arc::new(v)))
692                            }
693                            HashMapKindedRef::F64(_) => {
694                                panic!(
695                                    "FromSlot<Vec<(Arc<String>, Arc<HeapValue>)>>: \
696                                     HashMap<string, number> has no canonical \
697                                     HeapValue arm (number is inline-scalar). \
698                                     Marshal contract violation."
699                                );
700                            }
701                            HashMapKindedRef::Bool(_) => {
702                                panic!(
703                                    "FromSlot<Vec<(Arc<String>, Arc<HeapValue>)>>: \
704                                     HashMap<string, bool> has no canonical \
705                                     HeapValue arm (bool is inline-scalar). \
706                                     Marshal contract violation."
707                                );
708                            }
709                            HashMapKindedRef::Char(arc) => {
710                                let v: char = unsafe { *(*arc.values).data.add(i) };
711                                Arc::new(HeapValue::Char(v))
712                            }
713                            HashMapKindedRef::String(arc) => {
714                                let ptr: *const shape_value::v2::string_obj::StringObj =
715                                    unsafe { *(*arc.values).data.add(i) };
716                                let s = unsafe {
717                                    shape_value::v2::string_obj::StringObj::as_str(ptr)
718                                        .to_owned()
719                                };
720                                Arc::new(HeapValue::String(Arc::new(s)))
721                            }
722                            HashMapKindedRef::Decimal(arc) => {
723                                let ptr: *const shape_value::v2::decimal_obj::DecimalObj =
724                                    unsafe { *(*arc.values).data.add(i) };
725                                let d = unsafe { (*ptr).value };
726                                Arc::new(HeapValue::Decimal(Arc::new(d)))
727                            }
728                            HashMapKindedRef::TypedObject(arc) => {
729                                let elem: &shape_value::heap_value::TypedObjectPtr =
730                                    unsafe { &*(*arc.values).data.add(i) };
731                                Arc::new(HeapValue::TypedObject(elem.clone()))
732                            }
733                            HashMapKindedRef::TraitObject(_) => {
734                                panic!(
735                                    "FromSlot<Vec<(Arc<String>, Arc<HeapValue>)>>: \
736                                     HashMap<string, TraitObject> marshal not yet \
737                                     wired (HeapValue::TraitObject arm exists but \
738                                     payload kind dispatch is its own cluster)."
739                                );
740                            }
741                            HashMapKindedRef::HashMap(arc) => {
742                                // Recursive carrier (Wave N hashmap-value-v-arm
743                                // follow-up, cluster-2 closure-wave-C,
744                                // 2026-05-16). Each inner element is itself a
745                                // HashMapKindedRef; wrap as a fresh
746                                // HeapValue::HashMap. The inner Arc is
747                                // share-cloned (Arc::clone on
748                                // HashMapKindedRef::clone — single refcount
749                                // bump on the inner Arc<HashMapData<V_inner>>).
750                                // Per outer `unsafe` block at line 655; no
751                                // inner unsafe wrapper needed.
752                                let inner_ref: &HashMapKindedRef =
753                                    &*(*arc.values).data.add(i);
754                                Arc::new(HeapValue::HashMap(inner_ref.clone()))
755                            }
756                        };
757                        out.push((key, value));
758                    }
759                    out
760                }
761                other => panic!(
762                    "FromSlot<Vec<(Arc<String>, Arc<HeapValue>)>>: slot bits decoded to \
763                     HeapValue::{:?}, not HashMap. Marshal kind contract violated by caller.",
764                    other.kind()
765                ),
766            }
767        }
768    }
769}
770
771// ────── typed-array Arc<Vec<T>> FromSlot/ToSlot (Migration shape (a)) ──────
772//
773// V3-S5 ckpt-5-prime²c (2026-05-15) — supervisor 2026-05-15 Migration shape
774// (a) RATIFIED. The prior `Arc<AlignedTypedBuffer>` / `Arc<TypedBuffer<i64>>`
775// / `Arc<TypedBuffer<u8>>` zero-copy section is rewritten on the new
776// `*mut TypedArray<T>` flat-struct carrier shape. The pre-migration
777// per-storage-variant body-type map:
778//
779//   TypedArrayData::F64 ↔ Arc<AlignedTypedBuffer>   → Arc<Vec<f64>>
780//   TypedArrayData::I64 ↔ Arc<TypedBuffer<i64>>     → Arc<Vec<i64>>
781//   TypedArrayData::U8  ↔ Arc<TypedBuffer<u8>>      → Arc<Vec<u8>>
782//
783// `NATIVE_KIND` stays `Ptr(HeapKind::TypedArray)` for all three — the
784// element-width discriminator lives in the body-side Rust type (option ε
785// pattern), not in slot bits or `NativeKind`. Each `from_slot` impl reads
786// the slot's raw `*mut TypedArray<T>`, materializes a `Vec<T>` by copying
787// from `TypedArray::as_slice`, and wraps in `Arc::new`. The body accesses
788// `&[T]` via `Arc::deref` → `Vec<T>`'s `Deref<Target=[T]>` impl — same
789// API surface as the prior `Arc<AlignedTypedBuffer>` / `Arc<TypedBuffer<T>>`
790// for the 39 migrated intrinsics in ckpt-5-prime²b (zero body adaptation).
791//
792// Owns-clone semantics (full element copy at the marshal boundary): a
793// later wave can revisit zero-copy by switching the body parameter type
794// to `*const TypedArray<T>` and exposing `TypedArray::<T>::as_slice` to
795// stdlib bodies; deferred per `docs/defections.md` zero-copy follow-on
796// subsection (now-superseded — the v2-raw flat-struct carrier means
797// AlignedVec SIMD-alignment is at the v2/typed_array level itself, not
798// at a wrapper).
799
800/// Read an `Arc<Vec<f64>>` from a `NativeKind::Ptr(HeapKind::TypedArray)`
801/// slot whose payload is `*mut TypedArray<f64>`. V3-S5 ckpt-5-prime²c
802/// Migration shape (a) — replaces the pre-migration
803/// `FromSlot for Arc<AlignedTypedBuffer>` entry.
804impl FromSlot for Arc<Vec<f64>> {
805    const NATIVE_KIND: NativeKind =
806        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
807    #[inline]
808    fn from_slot(bits: u64) -> Self {
809        // SAFETY: NATIVE_KIND::Ptr(HeapKind::TypedArray) + body-declared
810        // element type Arc<Vec<f64>> pins the slot bits to a live
811        // *mut TypedArray<f64>.
812        let arr = bits as usize as *const shape_value::v2::typed_array::TypedArray<f64>;
813        if arr.is_null() {
814            return Arc::new(Vec::new());
815        }
816        unsafe {
817            Arc::new(
818                shape_value::v2::typed_array::TypedArray::<f64>::as_slice(arr).to_vec(),
819            )
820        }
821    }
822}
823
824/// Project an `Arc<Vec<f64>>` into a `NativeKind::Ptr(HeapKind::TypedArray)`
825/// slot whose payload is `*mut TypedArray<f64>`. V3-S5 ckpt-5-prime²c
826/// Migration shape (a) — replaces the pre-migration
827/// `ToSlot for Arc<AlignedTypedBuffer>` entry.
828impl ToSlot for Arc<Vec<f64>> {
829    const NATIVE_KIND: NativeKind =
830        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
831    #[inline]
832    fn to_slot(self) -> u64 {
833        let arr = shape_value::v2::typed_array::TypedArray::<f64>::from_slice(self.as_slice());
834        arr as usize as u64
835    }
836}
837
838/// Read an `Arc<Vec<i64>>` from a `NativeKind::Ptr(HeapKind::TypedArray)`
839/// slot whose payload is `*mut TypedArray<i64>`. V3-S5 ckpt-5-prime²c
840/// Migration shape (a) — replaces the pre-migration
841/// `FromSlot for Arc<TypedBuffer<i64>>` entry.
842impl FromSlot for Arc<Vec<i64>> {
843    const NATIVE_KIND: NativeKind =
844        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
845    #[inline]
846    fn from_slot(bits: u64) -> Self {
847        // SAFETY: see Arc<Vec<f64>>::from_slot above.
848        let arr = bits as usize as *const shape_value::v2::typed_array::TypedArray<i64>;
849        if arr.is_null() {
850            return Arc::new(Vec::new());
851        }
852        unsafe {
853            Arc::new(
854                shape_value::v2::typed_array::TypedArray::<i64>::as_slice(arr).to_vec(),
855            )
856        }
857    }
858}
859
860/// Project an `Arc<Vec<i64>>` into a `NativeKind::Ptr(HeapKind::TypedArray)`
861/// slot whose payload is `*mut TypedArray<i64>`. V3-S5 ckpt-5-prime²c
862/// Migration shape (a).
863impl ToSlot for Arc<Vec<i64>> {
864    const NATIVE_KIND: NativeKind =
865        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
866    #[inline]
867    fn to_slot(self) -> u64 {
868        let arr = shape_value::v2::typed_array::TypedArray::<i64>::from_slice(self.as_slice());
869        arr as usize as u64
870    }
871}
872
873/// Read an `Arc<Vec<u8>>` from a `NativeKind::Ptr(HeapKind::TypedArray)`
874/// slot whose payload is `*mut TypedArray<u8>`. V3-S5 ckpt-5-prime²c
875/// Migration shape (a) — replaces the pre-migration
876/// `FromSlot for Arc<TypedBuffer<u8>>` entry.
877///
878/// Note: the pre-migration Bool-vs-U8 Rust-type-collision residual carries
879/// forward — `Array<bool>` lowers to `*mut TypedArray<u8>` per the v2
880/// carrier shape (bool is stored as u8 with the dispatch-level Bool stamp
881/// at `stamp_elem_type`). A body declaring `Arc<Vec<u8>>` and being handed
882/// an `Array<bool>` slot will read raw bytes correctly but cannot
883/// distinguish "Array<u8>" from "Array<bool>" at this boundary. Resolution
884/// when a Bool consumer surfaces.
885impl FromSlot for Arc<Vec<u8>> {
886    const NATIVE_KIND: NativeKind =
887        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
888    #[inline]
889    fn from_slot(bits: u64) -> Self {
890        // SAFETY: see Arc<Vec<f64>>::from_slot above.
891        let arr = bits as usize as *const shape_value::v2::typed_array::TypedArray<u8>;
892        if arr.is_null() {
893            return Arc::new(Vec::new());
894        }
895        unsafe {
896            Arc::new(
897                shape_value::v2::typed_array::TypedArray::<u8>::as_slice(arr).to_vec(),
898            )
899        }
900    }
901}
902
903/// Project an `Arc<Vec<u8>>` into a `NativeKind::Ptr(HeapKind::TypedArray)`
904/// slot whose payload is `*mut TypedArray<u8>`. V3-S5 ckpt-5-prime²c
905/// Migration shape (a).
906impl ToSlot for Arc<Vec<u8>> {
907    const NATIVE_KIND: NativeKind =
908        NativeKind::Ptr(shape_value::HeapKind::TypedArray);
909    #[inline]
910    fn to_slot(self) -> u64 {
911        let arr = shape_value::v2::typed_array::TypedArray::<u8>::from_slice(self.as_slice());
912        arr as usize as u64
913    }
914}
915
916// ─────────────────────── per-arity register helpers ───────────────────────
917
918/// Body type stored in the typed registry: takes raw `&[u64]` slots and
919/// returns a [`TypedReturn`]. Constructed only by the typed
920/// `register_typed_fn_N` helpers, which type-check the body's actual
921/// Rust signature against `FromSlot` for each arg.
922type TypedInvoke = Arc<
923    dyn for<'ctx> Fn(&[u64], &ModuleContext<'ctx>) -> Result<TypedReturn, String>
924        + Send
925        + Sync,
926>;
927
928/// Register a 0-arg native function whose body takes only the
929/// `ModuleContext` and returns a [`TypedReturn`].
930pub fn register_typed_fn_0<F>(
931    module: &mut crate::module_exports::ModuleExports,
932    name: impl Into<String>,
933    description: impl Into<String>,
934    return_type: crate::typed_module_exports::ConcreteType,
935    body: F,
936) where
937    F: for<'ctx> Fn(&ModuleContext<'ctx>) -> Result<TypedReturn, String>
938        + Send
939        + Sync
940        + 'static,
941{
942    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
943        if !slots.is_empty() {
944            return Err(MarshalError::ArgCount {
945                expected: 0,
946                got: slots.len(),
947            }
948            .into());
949        }
950        body(ctx)
951    });
952    install(module, name, description, vec![], return_type, vec![], invoke);
953}
954
955/// Register a 1-arg native function. The body's `P0` parameter type
956/// declares the typed contract via [`FromSlot::NATIVE_KIND`] — there is
957/// no separate kind annotation to keep in sync.
958pub fn register_typed_fn_1<F, P0>(
959    module: &mut crate::module_exports::ModuleExports,
960    name: impl Into<String>,
961    description: impl Into<String>,
962    param_name: impl Into<String>,
963    param_type_name: impl Into<String>,
964    return_type: crate::typed_module_exports::ConcreteType,
965    body: F,
966) where
967    F: for<'ctx> Fn(P0, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
968        + Send
969        + Sync
970        + 'static,
971    P0: FromSlot + Send + Sync + 'static,
972{
973    let arg_kinds = vec![P0::NATIVE_KIND];
974    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
975        if slots.len() != 1 {
976            return Err(MarshalError::ArgCount {
977                expected: 1,
978                got: slots.len(),
979            }
980            .into());
981        }
982        let p0 = P0::from_slot(slots[0]);
983        body(p0, ctx)
984    });
985    let params = vec![crate::module_exports::ModuleParam {
986        name: param_name.into(),
987        type_name: param_type_name.into(),
988        required: true,
989        ..Default::default()
990    }];
991    install(
992        module,
993        name,
994        description,
995        params,
996        return_type,
997        arg_kinds,
998        invoke,
999    );
1000}
1001
1002/// Register a 2-arg native function.
1003pub fn register_typed_fn_2<F, P0, P1>(
1004    module: &mut crate::module_exports::ModuleExports,
1005    name: impl Into<String>,
1006    description: impl Into<String>,
1007    param_names: [(&str, &str); 2],
1008    return_type: crate::typed_module_exports::ConcreteType,
1009    body: F,
1010) where
1011    F: for<'ctx> Fn(P0, P1, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1012        + Send
1013        + Sync
1014        + 'static,
1015    P0: FromSlot + Send + Sync + 'static,
1016    P1: FromSlot + Send + Sync + 'static,
1017{
1018    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND];
1019    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1020        if slots.len() != 2 {
1021            return Err(MarshalError::ArgCount {
1022                expected: 2,
1023                got: slots.len(),
1024            }
1025            .into());
1026        }
1027        let p0 = P0::from_slot(slots[0]);
1028        let p1 = P1::from_slot(slots[1]);
1029        body(p0, p1, ctx)
1030    });
1031    let params = param_names
1032        .iter()
1033        .map(|(name, ty)| crate::module_exports::ModuleParam {
1034            name: (*name).to_string(),
1035            type_name: (*ty).to_string(),
1036            required: true,
1037            ..Default::default()
1038        })
1039        .collect();
1040    install(
1041        module,
1042        name,
1043        description,
1044        params,
1045        return_type,
1046        arg_kinds,
1047        invoke,
1048    );
1049}
1050
1051/// Register a 3-arg native function.
1052pub fn register_typed_fn_3<F, P0, P1, P2>(
1053    module: &mut crate::module_exports::ModuleExports,
1054    name: impl Into<String>,
1055    description: impl Into<String>,
1056    param_names: [(&str, &str); 3],
1057    return_type: crate::typed_module_exports::ConcreteType,
1058    body: F,
1059) where
1060    F: for<'ctx> Fn(P0, P1, P2, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1061        + Send
1062        + Sync
1063        + 'static,
1064    P0: FromSlot + Send + Sync + 'static,
1065    P1: FromSlot + Send + Sync + 'static,
1066    P2: FromSlot + Send + Sync + 'static,
1067{
1068    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND, P2::NATIVE_KIND];
1069    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1070        if slots.len() != 3 {
1071            return Err(MarshalError::ArgCount {
1072                expected: 3,
1073                got: slots.len(),
1074            }
1075            .into());
1076        }
1077        let p0 = P0::from_slot(slots[0]);
1078        let p1 = P1::from_slot(slots[1]);
1079        let p2 = P2::from_slot(slots[2]);
1080        body(p0, p1, p2, ctx)
1081    });
1082    let params = param_names
1083        .iter()
1084        .map(|(name, ty)| crate::module_exports::ModuleParam {
1085            name: (*name).to_string(),
1086            type_name: (*ty).to_string(),
1087            required: true,
1088            ..Default::default()
1089        })
1090        .collect();
1091    install(
1092        module,
1093        name,
1094        description,
1095        params,
1096        return_type,
1097        arg_kinds,
1098        invoke,
1099    );
1100}
1101
1102// ─────────────── per-arity `_full` register helpers (optional-arg) ───────────
1103//
1104// Mirror `register_typed_fn_N` but take `[ModuleParam; N]` directly instead
1105// of `[(&str, &str); N]`. This lets per-param `required: bool` and
1106// `default_snippet: Option<String>` flow through to the schema-introspection
1107// layer and the compiler-side default-arg insertion path
1108// (`crates/shape-vm/src/compiler/functions_foreign.rs:433`,
1109// `statements.rs:540`). Bodies stay typed — the dispatcher always sees N
1110// typed args because the compiler synthesizes any missing trailing optional
1111// before emitting the call.
1112//
1113// On-record marshal-API extension per `docs/defections.md` 2026-05-06
1114// `marshal-optional-args`. Considered + rejected: option 2 (sentinel values
1115// inline — W-series shape at marshal-API level) and option 3 (defer with
1116// user-facing Shape signature regression on canonical I/O).
1117
1118/// Register a 1-arg native function with full param spec (per-arg
1119/// `required` + `default_snippet`).
1120pub fn register_typed_fn_1_full<F, P0>(
1121    module: &mut crate::module_exports::ModuleExports,
1122    name: impl Into<String>,
1123    description: impl Into<String>,
1124    params: [crate::module_exports::ModuleParam; 1],
1125    return_type: crate::typed_module_exports::ConcreteType,
1126    body: F,
1127) where
1128    F: for<'ctx> Fn(P0, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1129        + Send
1130        + Sync
1131        + 'static,
1132    P0: FromSlot + Send + Sync + 'static,
1133{
1134    let arg_kinds = vec![P0::NATIVE_KIND];
1135    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1136        if slots.len() != 1 {
1137            return Err(MarshalError::ArgCount {
1138                expected: 1,
1139                got: slots.len(),
1140            }
1141            .into());
1142        }
1143        let p0 = P0::from_slot(slots[0]);
1144        body(p0, ctx)
1145    });
1146    install(
1147        module,
1148        name,
1149        description,
1150        params.into_iter().collect(),
1151        return_type,
1152        arg_kinds,
1153        invoke,
1154    );
1155}
1156
1157/// Register a 2-arg native function with full param spec.
1158pub fn register_typed_fn_2_full<F, P0, P1>(
1159    module: &mut crate::module_exports::ModuleExports,
1160    name: impl Into<String>,
1161    description: impl Into<String>,
1162    params: [crate::module_exports::ModuleParam; 2],
1163    return_type: crate::typed_module_exports::ConcreteType,
1164    body: F,
1165) where
1166    F: for<'ctx> Fn(P0, P1, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1167        + Send
1168        + Sync
1169        + 'static,
1170    P0: FromSlot + Send + Sync + 'static,
1171    P1: FromSlot + Send + Sync + 'static,
1172{
1173    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND];
1174    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1175        if slots.len() != 2 {
1176            return Err(MarshalError::ArgCount {
1177                expected: 2,
1178                got: slots.len(),
1179            }
1180            .into());
1181        }
1182        let p0 = P0::from_slot(slots[0]);
1183        let p1 = P1::from_slot(slots[1]);
1184        body(p0, p1, ctx)
1185    });
1186    install(
1187        module,
1188        name,
1189        description,
1190        params.into_iter().collect(),
1191        return_type,
1192        arg_kinds,
1193        invoke,
1194    );
1195}
1196
1197/// Register a 3-arg native function with full param spec.
1198pub fn register_typed_fn_3_full<F, P0, P1, P2>(
1199    module: &mut crate::module_exports::ModuleExports,
1200    name: impl Into<String>,
1201    description: impl Into<String>,
1202    params: [crate::module_exports::ModuleParam; 3],
1203    return_type: crate::typed_module_exports::ConcreteType,
1204    body: F,
1205) where
1206    F: for<'ctx> Fn(P0, P1, P2, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1207        + Send
1208        + Sync
1209        + 'static,
1210    P0: FromSlot + Send + Sync + 'static,
1211    P1: FromSlot + Send + Sync + 'static,
1212    P2: FromSlot + Send + Sync + 'static,
1213{
1214    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND, P2::NATIVE_KIND];
1215    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1216        if slots.len() != 3 {
1217            return Err(MarshalError::ArgCount {
1218                expected: 3,
1219                got: slots.len(),
1220            }
1221            .into());
1222        }
1223        let p0 = P0::from_slot(slots[0]);
1224        let p1 = P1::from_slot(slots[1]);
1225        let p2 = P2::from_slot(slots[2]);
1226        body(p0, p1, p2, ctx)
1227    });
1228    install(
1229        module,
1230        name,
1231        description,
1232        params.into_iter().collect(),
1233        return_type,
1234        arg_kinds,
1235        invoke,
1236    );
1237}
1238
1239// ─────────────── per-arity register helpers — arities 4/5/6 (N2 extension) ──
1240//
1241// Per-arity parallel-impl extension to support intrinsics with > 3 typed args.
1242// Mechanical mirror of arities 0..3 above; no new architectural surface — no
1243// dyn, no parametric NativeKind, no rename-to-less-suspicious-name. Same
1244// per-arity pattern as `marshal-optional-args`'s `_full` extension precedent.
1245//
1246// On-record marshal-API extension per `docs/defections.md` 2026-05-07
1247// intrinsics-typed-CC entry's N2 sub-decision queue subsection (queue
1248// item #6, supervisor sign-off relayed via team-lead). Sync-only at first
1249// landing per consumer pattern (stochastic gbm/ou_process synchronous);
1250// async _N variants deferred until consumer-driven need.
1251
1252/// Register a 4-arg native function with positional `(name, type)` param spec.
1253pub fn register_typed_fn_4<F, P0, P1, P2, P3>(
1254    module: &mut crate::module_exports::ModuleExports,
1255    name: impl Into<String>,
1256    description: impl Into<String>,
1257    param_names: [(&str, &str); 4],
1258    return_type: crate::typed_module_exports::ConcreteType,
1259    body: F,
1260) where
1261    F: for<'ctx> Fn(P0, P1, P2, P3, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1262        + Send
1263        + Sync
1264        + 'static,
1265    P0: FromSlot + Send + Sync + 'static,
1266    P1: FromSlot + Send + Sync + 'static,
1267    P2: FromSlot + Send + Sync + 'static,
1268    P3: FromSlot + Send + Sync + 'static,
1269{
1270    let arg_kinds = vec![
1271        P0::NATIVE_KIND,
1272        P1::NATIVE_KIND,
1273        P2::NATIVE_KIND,
1274        P3::NATIVE_KIND,
1275    ];
1276    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1277        if slots.len() != 4 {
1278            return Err(MarshalError::ArgCount {
1279                expected: 4,
1280                got: slots.len(),
1281            }
1282            .into());
1283        }
1284        let p0 = P0::from_slot(slots[0]);
1285        let p1 = P1::from_slot(slots[1]);
1286        let p2 = P2::from_slot(slots[2]);
1287        let p3 = P3::from_slot(slots[3]);
1288        body(p0, p1, p2, p3, ctx)
1289    });
1290    let params = param_names
1291        .iter()
1292        .map(|(name, ty)| crate::module_exports::ModuleParam {
1293            name: (*name).to_string(),
1294            type_name: (*ty).to_string(),
1295            required: true,
1296            ..Default::default()
1297        })
1298        .collect();
1299    install(
1300        module,
1301        name,
1302        description,
1303        params,
1304        return_type,
1305        arg_kinds,
1306        invoke,
1307    );
1308}
1309
1310/// Register a 5-arg native function with positional `(name, type)` param spec.
1311pub fn register_typed_fn_5<F, P0, P1, P2, P3, P4>(
1312    module: &mut crate::module_exports::ModuleExports,
1313    name: impl Into<String>,
1314    description: impl Into<String>,
1315    param_names: [(&str, &str); 5],
1316    return_type: crate::typed_module_exports::ConcreteType,
1317    body: F,
1318) where
1319    F: for<'ctx> Fn(P0, P1, P2, P3, P4, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1320        + Send
1321        + Sync
1322        + 'static,
1323    P0: FromSlot + Send + Sync + 'static,
1324    P1: FromSlot + Send + Sync + 'static,
1325    P2: FromSlot + Send + Sync + 'static,
1326    P3: FromSlot + Send + Sync + 'static,
1327    P4: FromSlot + Send + Sync + 'static,
1328{
1329    let arg_kinds = vec![
1330        P0::NATIVE_KIND,
1331        P1::NATIVE_KIND,
1332        P2::NATIVE_KIND,
1333        P3::NATIVE_KIND,
1334        P4::NATIVE_KIND,
1335    ];
1336    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1337        if slots.len() != 5 {
1338            return Err(MarshalError::ArgCount {
1339                expected: 5,
1340                got: slots.len(),
1341            }
1342            .into());
1343        }
1344        let p0 = P0::from_slot(slots[0]);
1345        let p1 = P1::from_slot(slots[1]);
1346        let p2 = P2::from_slot(slots[2]);
1347        let p3 = P3::from_slot(slots[3]);
1348        let p4 = P4::from_slot(slots[4]);
1349        body(p0, p1, p2, p3, p4, ctx)
1350    });
1351    let params = param_names
1352        .iter()
1353        .map(|(name, ty)| crate::module_exports::ModuleParam {
1354            name: (*name).to_string(),
1355            type_name: (*ty).to_string(),
1356            required: true,
1357            ..Default::default()
1358        })
1359        .collect();
1360    install(
1361        module,
1362        name,
1363        description,
1364        params,
1365        return_type,
1366        arg_kinds,
1367        invoke,
1368    );
1369}
1370
1371/// Register a 6-arg native function with positional `(name, type)` param spec.
1372pub fn register_typed_fn_6<F, P0, P1, P2, P3, P4, P5>(
1373    module: &mut crate::module_exports::ModuleExports,
1374    name: impl Into<String>,
1375    description: impl Into<String>,
1376    param_names: [(&str, &str); 6],
1377    return_type: crate::typed_module_exports::ConcreteType,
1378    body: F,
1379) where
1380    F: for<'ctx> Fn(P0, P1, P2, P3, P4, P5, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1381        + Send
1382        + Sync
1383        + 'static,
1384    P0: FromSlot + Send + Sync + 'static,
1385    P1: FromSlot + Send + Sync + 'static,
1386    P2: FromSlot + Send + Sync + 'static,
1387    P3: FromSlot + Send + Sync + 'static,
1388    P4: FromSlot + Send + Sync + 'static,
1389    P5: FromSlot + Send + Sync + 'static,
1390{
1391    let arg_kinds = vec![
1392        P0::NATIVE_KIND,
1393        P1::NATIVE_KIND,
1394        P2::NATIVE_KIND,
1395        P3::NATIVE_KIND,
1396        P4::NATIVE_KIND,
1397        P5::NATIVE_KIND,
1398    ];
1399    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1400        if slots.len() != 6 {
1401            return Err(MarshalError::ArgCount {
1402                expected: 6,
1403                got: slots.len(),
1404            }
1405            .into());
1406        }
1407        let p0 = P0::from_slot(slots[0]);
1408        let p1 = P1::from_slot(slots[1]);
1409        let p2 = P2::from_slot(slots[2]);
1410        let p3 = P3::from_slot(slots[3]);
1411        let p4 = P4::from_slot(slots[4]);
1412        let p5 = P5::from_slot(slots[5]);
1413        body(p0, p1, p2, p3, p4, p5, ctx)
1414    });
1415    let params = param_names
1416        .iter()
1417        .map(|(name, ty)| crate::module_exports::ModuleParam {
1418            name: (*name).to_string(),
1419            type_name: (*ty).to_string(),
1420            required: true,
1421            ..Default::default()
1422        })
1423        .collect();
1424    install(
1425        module,
1426        name,
1427        description,
1428        params,
1429        return_type,
1430        arg_kinds,
1431        invoke,
1432    );
1433}
1434
1435/// Register a 4-arg native function with full param spec.
1436pub fn register_typed_fn_4_full<F, P0, P1, P2, P3>(
1437    module: &mut crate::module_exports::ModuleExports,
1438    name: impl Into<String>,
1439    description: impl Into<String>,
1440    params: [crate::module_exports::ModuleParam; 4],
1441    return_type: crate::typed_module_exports::ConcreteType,
1442    body: F,
1443) where
1444    F: for<'ctx> Fn(P0, P1, P2, P3, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1445        + Send
1446        + Sync
1447        + 'static,
1448    P0: FromSlot + Send + Sync + 'static,
1449    P1: FromSlot + Send + Sync + 'static,
1450    P2: FromSlot + Send + Sync + 'static,
1451    P3: FromSlot + Send + Sync + 'static,
1452{
1453    let arg_kinds = vec![
1454        P0::NATIVE_KIND,
1455        P1::NATIVE_KIND,
1456        P2::NATIVE_KIND,
1457        P3::NATIVE_KIND,
1458    ];
1459    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1460        if slots.len() != 4 {
1461            return Err(MarshalError::ArgCount {
1462                expected: 4,
1463                got: slots.len(),
1464            }
1465            .into());
1466        }
1467        let p0 = P0::from_slot(slots[0]);
1468        let p1 = P1::from_slot(slots[1]);
1469        let p2 = P2::from_slot(slots[2]);
1470        let p3 = P3::from_slot(slots[3]);
1471        body(p0, p1, p2, p3, ctx)
1472    });
1473    install(
1474        module,
1475        name,
1476        description,
1477        params.into_iter().collect(),
1478        return_type,
1479        arg_kinds,
1480        invoke,
1481    );
1482}
1483
1484/// Register a 5-arg native function with full param spec.
1485pub fn register_typed_fn_5_full<F, P0, P1, P2, P3, P4>(
1486    module: &mut crate::module_exports::ModuleExports,
1487    name: impl Into<String>,
1488    description: impl Into<String>,
1489    params: [crate::module_exports::ModuleParam; 5],
1490    return_type: crate::typed_module_exports::ConcreteType,
1491    body: F,
1492) where
1493    F: for<'ctx> Fn(P0, P1, P2, P3, P4, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1494        + Send
1495        + Sync
1496        + 'static,
1497    P0: FromSlot + Send + Sync + 'static,
1498    P1: FromSlot + Send + Sync + 'static,
1499    P2: FromSlot + Send + Sync + 'static,
1500    P3: FromSlot + Send + Sync + 'static,
1501    P4: FromSlot + Send + Sync + 'static,
1502{
1503    let arg_kinds = vec![
1504        P0::NATIVE_KIND,
1505        P1::NATIVE_KIND,
1506        P2::NATIVE_KIND,
1507        P3::NATIVE_KIND,
1508        P4::NATIVE_KIND,
1509    ];
1510    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1511        if slots.len() != 5 {
1512            return Err(MarshalError::ArgCount {
1513                expected: 5,
1514                got: slots.len(),
1515            }
1516            .into());
1517        }
1518        let p0 = P0::from_slot(slots[0]);
1519        let p1 = P1::from_slot(slots[1]);
1520        let p2 = P2::from_slot(slots[2]);
1521        let p3 = P3::from_slot(slots[3]);
1522        let p4 = P4::from_slot(slots[4]);
1523        body(p0, p1, p2, p3, p4, ctx)
1524    });
1525    install(
1526        module,
1527        name,
1528        description,
1529        params.into_iter().collect(),
1530        return_type,
1531        arg_kinds,
1532        invoke,
1533    );
1534}
1535
1536/// Register a 6-arg native function with full param spec.
1537pub fn register_typed_fn_6_full<F, P0, P1, P2, P3, P4, P5>(
1538    module: &mut crate::module_exports::ModuleExports,
1539    name: impl Into<String>,
1540    description: impl Into<String>,
1541    params: [crate::module_exports::ModuleParam; 6],
1542    return_type: crate::typed_module_exports::ConcreteType,
1543    body: F,
1544) where
1545    F: for<'ctx> Fn(P0, P1, P2, P3, P4, P5, &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1546        + Send
1547        + Sync
1548        + 'static,
1549    P0: FromSlot + Send + Sync + 'static,
1550    P1: FromSlot + Send + Sync + 'static,
1551    P2: FromSlot + Send + Sync + 'static,
1552    P3: FromSlot + Send + Sync + 'static,
1553    P4: FromSlot + Send + Sync + 'static,
1554    P5: FromSlot + Send + Sync + 'static,
1555{
1556    let arg_kinds = vec![
1557        P0::NATIVE_KIND,
1558        P1::NATIVE_KIND,
1559        P2::NATIVE_KIND,
1560        P3::NATIVE_KIND,
1561        P4::NATIVE_KIND,
1562        P5::NATIVE_KIND,
1563    ];
1564    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
1565        if slots.len() != 6 {
1566            return Err(MarshalError::ArgCount {
1567                expected: 6,
1568                got: slots.len(),
1569            }
1570            .into());
1571        }
1572        let p0 = P0::from_slot(slots[0]);
1573        let p1 = P1::from_slot(slots[1]);
1574        let p2 = P2::from_slot(slots[2]);
1575        let p3 = P3::from_slot(slots[3]);
1576        let p4 = P4::from_slot(slots[4]);
1577        let p5 = P5::from_slot(slots[5]);
1578        body(p0, p1, p2, p3, p4, p5, ctx)
1579    });
1580    install(
1581        module,
1582        name,
1583        description,
1584        params.into_iter().collect(),
1585        return_type,
1586        arg_kinds,
1587        invoke,
1588    );
1589}
1590
1591/// Internal helper: install a fully-prepared typed function entry into a
1592/// module's typed registry plus its schema-only entry.
1593fn install(
1594    module: &mut crate::module_exports::ModuleExports,
1595    name: impl Into<String>,
1596    description: impl Into<String>,
1597    params: Vec<crate::module_exports::ModuleParam>,
1598    return_type: crate::typed_module_exports::ConcreteType,
1599    arg_kinds: Vec<NativeKind>,
1600    invoke: TypedInvoke,
1601) {
1602    use crate::module_exports::ModuleFunction;
1603    use crate::typed_module_exports::TypedModuleFunction;
1604
1605    let name = name.into();
1606    let arg_types: Vec<String> = params.iter().map(|p| p.type_name.clone()).collect();
1607    let return_type_str = return_type.shape_type_name();
1608    module.add_schema_only(
1609        name.clone(),
1610        ModuleFunction {
1611            description: description.into(),
1612            params,
1613            return_type: Some(return_type_str),
1614        },
1615    );
1616    module.typed_exports_mut().functions.insert(
1617        name,
1618        TypedModuleFunction {
1619            invoke,
1620            return_type,
1621            arg_types,
1622            arg_kinds,
1623        },
1624    );
1625}
1626
1627// ─────────────────────── async per-arity register helpers ───────────────────────
1628//
1629// Async typed registration mirrors the sync `register_typed_fn_N` family
1630// with two structural differences enforced by the existing
1631// `TypedModuleAsyncFunction` shape (see `typed_module_exports.rs`):
1632//
1633// 1. **No `&ModuleContext`.** `ModuleContext` borrows from the VM and
1634//    cannot cross await points. Permission gating must happen
1635//    synchronously upstream of the dispatch site, not inside the async
1636//    body. (This matches the pre-bulldozer convention used by
1637//    `stdlib_io::async_file_ops` and `stdlib::http`.)
1638// 2. **Body returns `Future + Send + 'static`.** The wrapper boxes and
1639//    pins the future so the synchronous dispatch path can block on it.
1640//
1641// No new architectural decisions — the `TypedModuleAsyncFunction`
1642// struct is the contract; these helpers are the per-arity adapters.
1643
1644type TypedAsyncInvoke = Arc<
1645    dyn Fn(
1646            Vec<u64>,
1647        )
1648            -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<TypedReturn, String>> + Send>>
1649        + Send
1650        + Sync,
1651>;
1652
1653/// Register a 1-arg async native function. Body returns a `Future`; the
1654/// dispatcher blocks on it at the call boundary.
1655pub fn register_typed_async_fn_1<F, Fut, P0>(
1656    module: &mut crate::module_exports::ModuleExports,
1657    name: impl Into<String>,
1658    description: impl Into<String>,
1659    param_name: impl Into<String>,
1660    param_type_name: impl Into<String>,
1661    return_type: crate::typed_module_exports::ConcreteType,
1662    body: F,
1663) where
1664    F: Fn(P0) -> Fut + Send + Sync + Clone + 'static,
1665    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
1666    P0: FromSlot + Send + Sync + 'static,
1667{
1668    let arg_kinds = vec![P0::NATIVE_KIND];
1669    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
1670        if slots.len() != 1 {
1671            let err = MarshalError::ArgCount {
1672                expected: 1,
1673                got: slots.len(),
1674            };
1675            return Box::pin(async move { Err(err.into()) });
1676        }
1677        let p0 = P0::from_slot(slots[0]);
1678        let body = body.clone();
1679        Box::pin(async move { body(p0).await })
1680    });
1681    let params = vec![crate::module_exports::ModuleParam {
1682        name: param_name.into(),
1683        type_name: param_type_name.into(),
1684        required: true,
1685        ..Default::default()
1686    }];
1687    install_async(module, name, description, params, return_type, arg_kinds, invoke);
1688}
1689
1690/// Register a 2-arg async native function.
1691pub fn register_typed_async_fn_2<F, Fut, P0, P1>(
1692    module: &mut crate::module_exports::ModuleExports,
1693    name: impl Into<String>,
1694    description: impl Into<String>,
1695    param_names: [(&str, &str); 2],
1696    return_type: crate::typed_module_exports::ConcreteType,
1697    body: F,
1698) where
1699    F: Fn(P0, P1) -> Fut + Send + Sync + Clone + 'static,
1700    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
1701    P0: FromSlot + Send + Sync + 'static,
1702    P1: FromSlot + Send + Sync + 'static,
1703{
1704    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND];
1705    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
1706        if slots.len() != 2 {
1707            let err = MarshalError::ArgCount {
1708                expected: 2,
1709                got: slots.len(),
1710            };
1711            return Box::pin(async move { Err(err.into()) });
1712        }
1713        let p0 = P0::from_slot(slots[0]);
1714        let p1 = P1::from_slot(slots[1]);
1715        let body = body.clone();
1716        Box::pin(async move { body(p0, p1).await })
1717    });
1718    let params = param_names
1719        .iter()
1720        .map(|(name, ty)| crate::module_exports::ModuleParam {
1721            name: (*name).to_string(),
1722            type_name: (*ty).to_string(),
1723            required: true,
1724            ..Default::default()
1725        })
1726        .collect();
1727    install_async(module, name, description, params, return_type, arg_kinds, invoke);
1728}
1729
1730/// Register a 3-arg async native function.
1731pub fn register_typed_async_fn_3<F, Fut, P0, P1, P2>(
1732    module: &mut crate::module_exports::ModuleExports,
1733    name: impl Into<String>,
1734    description: impl Into<String>,
1735    param_names: [(&str, &str); 3],
1736    return_type: crate::typed_module_exports::ConcreteType,
1737    body: F,
1738) where
1739    F: Fn(P0, P1, P2) -> Fut + Send + Sync + Clone + 'static,
1740    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
1741    P0: FromSlot + Send + Sync + 'static,
1742    P1: FromSlot + Send + Sync + 'static,
1743    P2: FromSlot + Send + Sync + 'static,
1744{
1745    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND, P2::NATIVE_KIND];
1746    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
1747        if slots.len() != 3 {
1748            let err = MarshalError::ArgCount {
1749                expected: 3,
1750                got: slots.len(),
1751            };
1752            return Box::pin(async move { Err(err.into()) });
1753        }
1754        let p0 = P0::from_slot(slots[0]);
1755        let p1 = P1::from_slot(slots[1]);
1756        let p2 = P2::from_slot(slots[2]);
1757        let body = body.clone();
1758        Box::pin(async move { body(p0, p1, p2).await })
1759    });
1760    let params = param_names
1761        .iter()
1762        .map(|(name, ty)| crate::module_exports::ModuleParam {
1763            name: (*name).to_string(),
1764            type_name: (*ty).to_string(),
1765            required: true,
1766            ..Default::default()
1767        })
1768        .collect();
1769    install_async(module, name, description, params, return_type, arg_kinds, invoke);
1770}
1771
1772// ──────────── async per-arity `_full` register helpers (optional-arg) ────────
1773//
1774// Mirror the sync `_full` family for async. See the sync block above for
1775// rationale (`docs/defections.md` 2026-05-06 `marshal-optional-args`).
1776
1777/// Register a 1-arg async native function with full param spec.
1778pub fn register_typed_async_fn_1_full<F, Fut, P0>(
1779    module: &mut crate::module_exports::ModuleExports,
1780    name: impl Into<String>,
1781    description: impl Into<String>,
1782    params: [crate::module_exports::ModuleParam; 1],
1783    return_type: crate::typed_module_exports::ConcreteType,
1784    body: F,
1785) where
1786    F: Fn(P0) -> Fut + Send + Sync + Clone + 'static,
1787    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
1788    P0: FromSlot + Send + Sync + 'static,
1789{
1790    let arg_kinds = vec![P0::NATIVE_KIND];
1791    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
1792        if slots.len() != 1 {
1793            let err = MarshalError::ArgCount {
1794                expected: 1,
1795                got: slots.len(),
1796            };
1797            return Box::pin(async move { Err(err.into()) });
1798        }
1799        let p0 = P0::from_slot(slots[0]);
1800        let body = body.clone();
1801        Box::pin(async move { body(p0).await })
1802    });
1803    install_async(
1804        module,
1805        name,
1806        description,
1807        params.into_iter().collect(),
1808        return_type,
1809        arg_kinds,
1810        invoke,
1811    );
1812}
1813
1814/// Register a 2-arg async native function with full param spec.
1815pub fn register_typed_async_fn_2_full<F, Fut, P0, P1>(
1816    module: &mut crate::module_exports::ModuleExports,
1817    name: impl Into<String>,
1818    description: impl Into<String>,
1819    params: [crate::module_exports::ModuleParam; 2],
1820    return_type: crate::typed_module_exports::ConcreteType,
1821    body: F,
1822) where
1823    F: Fn(P0, P1) -> Fut + Send + Sync + Clone + 'static,
1824    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
1825    P0: FromSlot + Send + Sync + 'static,
1826    P1: FromSlot + Send + Sync + 'static,
1827{
1828    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND];
1829    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
1830        if slots.len() != 2 {
1831            let err = MarshalError::ArgCount {
1832                expected: 2,
1833                got: slots.len(),
1834            };
1835            return Box::pin(async move { Err(err.into()) });
1836        }
1837        let p0 = P0::from_slot(slots[0]);
1838        let p1 = P1::from_slot(slots[1]);
1839        let body = body.clone();
1840        Box::pin(async move { body(p0, p1).await })
1841    });
1842    install_async(
1843        module,
1844        name,
1845        description,
1846        params.into_iter().collect(),
1847        return_type,
1848        arg_kinds,
1849        invoke,
1850    );
1851}
1852
1853/// Register a 3-arg async native function with full param spec.
1854pub fn register_typed_async_fn_3_full<F, Fut, P0, P1, P2>(
1855    module: &mut crate::module_exports::ModuleExports,
1856    name: impl Into<String>,
1857    description: impl Into<String>,
1858    params: [crate::module_exports::ModuleParam; 3],
1859    return_type: crate::typed_module_exports::ConcreteType,
1860    body: F,
1861) where
1862    F: Fn(P0, P1, P2) -> Fut + Send + Sync + Clone + 'static,
1863    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
1864    P0: FromSlot + Send + Sync + 'static,
1865    P1: FromSlot + Send + Sync + 'static,
1866    P2: FromSlot + Send + Sync + 'static,
1867{
1868    let arg_kinds = vec![P0::NATIVE_KIND, P1::NATIVE_KIND, P2::NATIVE_KIND];
1869    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
1870        if slots.len() != 3 {
1871            let err = MarshalError::ArgCount {
1872                expected: 3,
1873                got: slots.len(),
1874            };
1875            return Box::pin(async move { Err(err.into()) });
1876        }
1877        let p0 = P0::from_slot(slots[0]);
1878        let p1 = P1::from_slot(slots[1]);
1879        let p2 = P2::from_slot(slots[2]);
1880        let body = body.clone();
1881        Box::pin(async move { body(p0, p1, p2).await })
1882    });
1883    install_async(
1884        module,
1885        name,
1886        description,
1887        params.into_iter().collect(),
1888        return_type,
1889        arg_kinds,
1890        invoke,
1891    );
1892}
1893
1894fn install_async(
1895    module: &mut crate::module_exports::ModuleExports,
1896    name: impl Into<String>,
1897    description: impl Into<String>,
1898    params: Vec<crate::module_exports::ModuleParam>,
1899    return_type: crate::typed_module_exports::ConcreteType,
1900    arg_kinds: Vec<NativeKind>,
1901    invoke: TypedAsyncInvoke,
1902) {
1903    use crate::module_exports::ModuleFunction;
1904    use crate::typed_module_exports::TypedModuleAsyncFunction;
1905
1906    let name = name.into();
1907    let arg_types: Vec<String> = params.iter().map(|p| p.type_name.clone()).collect();
1908    let return_type_str = return_type.shape_type_name();
1909    module.add_schema_only(
1910        name.clone(),
1911        ModuleFunction {
1912            description: description.into(),
1913            params,
1914            return_type: Some(return_type_str),
1915        },
1916    );
1917    module.typed_exports_mut().async_functions.insert(
1918        name,
1919        TypedModuleAsyncFunction {
1920            invoke,
1921            return_type,
1922            arg_types,
1923            arg_kinds,
1924        },
1925    );
1926}
1927
1928// ─────────────────── variadic register helpers (ADR-006 §2.7.4) ───────────────────
1929//
1930// Per ADR-006 §2.7.4 (stdlib registration ruling), the variadic
1931// `register_typed_function` / `register_typed_async_function` helpers
1932// are re-introduced at the [`KindedSlot`] shape. Per-arity helpers
1933// remain the preferred path when the function arity is fixed; the
1934// variadic helpers exist for the genuine §2.7.1.4 dispatch-slice case
1935// (functions with optional / variadic arguments — json/msgpack/toml/
1936// yaml/stdlib_time bodies that take optional `pretty?: bool`,
1937// `iterations?: int`, etc.).
1938//
1939// The variadic body signature is
1940// `Fn(&[KindedSlot], &ModuleContext) -> Result<TypedReturn, String>`,
1941// matching the §2.7.1.4 dispatch-slice contract. The `arg_kinds` field
1942// of [`TypedModuleFunction`] is left as a per-param-position table
1943// derived from the registered `ModuleParam` slice (each slot is
1944// declared `NativeKind::Bool` placeholder for the variadic case;
1945// dispatch reads bits and bundles them as `KindedSlot` carriers
1946// regardless of the placeholder kind, since the body interprets the
1947// slots itself per its variadic contract).
1948
1949use crate::typed_module_exports::TypedModuleFunction;
1950use shape_value::KindedSlot;
1951
1952/// Body signature for a [`register_typed_function`] caller.
1953///
1954/// Variadic — the body inspects the slot slice itself rather than
1955/// declaring a per-arg type at registration. Used by stdlib functions
1956/// with optional / overload-shaped arguments (json.stringify's optional
1957/// `pretty`, time.benchmark's optional `iterations`, etc.). For
1958/// fixed-arity functions, prefer [`register_typed_fn_N`].
1959pub type VariadicTypedBody = dyn for<'ctx> Fn(
1960        &[KindedSlot],
1961        &ModuleContext<'ctx>,
1962    ) -> Result<TypedReturn, String>
1963    + Send
1964    + Sync;
1965
1966/// Register a native function whose body inspects a variadic
1967/// [`KindedSlot`] slice.
1968///
1969/// Per ADR-006 §2.7.4 ruling, the variadic helper is the §2.7.1.4
1970/// dispatch-slice case — `KindedSlot` is the right carrier because the
1971/// kind-per-position is determined by the registered `ModuleParam`
1972/// schema, not by `FromSlot` constraints on the body's Rust signature.
1973/// Conversion from raw `&[u64]` to `&[KindedSlot]` happens inside the
1974/// runtime-side wrapper installed below; the body sees the typed
1975/// carrier directly.
1976pub fn register_typed_function<F>(
1977    module: &mut crate::module_exports::ModuleExports,
1978    name: impl Into<String>,
1979    description: impl Into<String>,
1980    params: Vec<crate::module_exports::ModuleParam>,
1981    return_type: crate::typed_module_exports::ConcreteType,
1982    body: F,
1983) where
1984    F: for<'ctx> Fn(&[KindedSlot], &ModuleContext<'ctx>) -> Result<TypedReturn, String>
1985        + Send
1986        + Sync
1987        + 'static,
1988{
1989    use crate::module_exports::ModuleFunction;
1990
1991    let name = name.into();
1992    let arg_types: Vec<String> = params.iter().map(|p| p.type_name.clone()).collect();
1993    // Variadic registration: `arg_kinds` is a placeholder schema. The
1994    // dispatcher constructs `KindedSlot`s by pairing each slot with the
1995    // declared `NativeKind` from the typed registry — for variadic
1996    // bodies the kind-per-position is the body's contract, not the
1997    // dispatcher's. Phase 2c wires per-position `NativeKind` derivation
1998    // from the schema annotations.
1999    let arg_kinds: Vec<NativeKind> = params.iter().map(|_| NativeKind::Bool).collect();
2000    let return_type_str = return_type.shape_type_name();
2001
2002    let body = Arc::new(body);
2003    let invoke: TypedInvoke = Arc::new(move |slots, ctx| {
2004        // Phase 1.B variadic shim: read each raw u64 slot as a
2005        // placeholder `KindedSlot::Bool`. The body is responsible for
2006        // interpreting the slot bits per its own contract (which is the
2007        // pre-bulldozer behaviour — variadic bodies always inspected
2008        // their args). Phase 2c lands proper per-position kind
2009        // threading from the registered schema.
2010        let kinded: Vec<KindedSlot> = slots
2011            .iter()
2012            .map(|&bits| {
2013                KindedSlot::new(
2014                    shape_value::ValueSlot::from_raw(bits),
2015                    NativeKind::Bool,
2016                )
2017            })
2018            .collect();
2019        body(&kinded, ctx)
2020    });
2021
2022    module.add_schema_only(
2023        name.clone(),
2024        ModuleFunction {
2025            description: description.into(),
2026            params,
2027            return_type: Some(return_type_str),
2028        },
2029    );
2030    module.typed_exports_mut().functions.insert(
2031        name,
2032        TypedModuleFunction {
2033            invoke,
2034            return_type,
2035            arg_types,
2036            arg_kinds,
2037        },
2038    );
2039}
2040
2041/// Body signature for a [`register_typed_async_function`] caller.
2042///
2043/// Variadic — same shape as [`VariadicTypedBody`] but returning a
2044/// `Future`. No `&ModuleContext` (the borrow cannot cross await
2045/// points); permission gating must happen synchronously upstream.
2046pub type VariadicTypedAsyncBody<Fut> =
2047    dyn Fn(Vec<KindedSlot>) -> Fut + Send + Sync;
2048
2049/// Register an async native function whose body inspects a variadic
2050/// [`KindedSlot`] vector.
2051pub fn register_typed_async_function<F, Fut>(
2052    module: &mut crate::module_exports::ModuleExports,
2053    name: impl Into<String>,
2054    description: impl Into<String>,
2055    params: Vec<crate::module_exports::ModuleParam>,
2056    return_type: crate::typed_module_exports::ConcreteType,
2057    body: F,
2058) where
2059    F: Fn(Vec<KindedSlot>) -> Fut + Send + Sync + Clone + 'static,
2060    Fut: std::future::Future<Output = Result<TypedReturn, String>> + Send + 'static,
2061{
2062    use crate::module_exports::ModuleFunction;
2063    use crate::typed_module_exports::TypedModuleAsyncFunction;
2064
2065    let name = name.into();
2066    let arg_types: Vec<String> = params.iter().map(|p| p.type_name.clone()).collect();
2067    let arg_kinds: Vec<NativeKind> = params.iter().map(|_| NativeKind::Bool).collect();
2068    let return_type_str = return_type.shape_type_name();
2069
2070    let invoke: TypedAsyncInvoke = Arc::new(move |slots: Vec<u64>| {
2071        let kinded: Vec<KindedSlot> = slots
2072            .into_iter()
2073            .map(|bits| {
2074                KindedSlot::new(
2075                    shape_value::ValueSlot::from_raw(bits),
2076                    NativeKind::Bool,
2077                )
2078            })
2079            .collect();
2080        let body = body.clone();
2081        Box::pin(async move { body(kinded).await })
2082    });
2083
2084    module.add_schema_only(
2085        name.clone(),
2086        ModuleFunction {
2087            description: description.into(),
2088            params,
2089            return_type: Some(return_type_str),
2090        },
2091    );
2092    module.typed_exports_mut().async_functions.insert(
2093        name,
2094        TypedModuleAsyncFunction {
2095            invoke,
2096            return_type,
2097            arg_types,
2098            arg_kinds,
2099        },
2100    );
2101}