1use rmpv::Value as Rmp;
49use shape_runtime::type_schema::{FieldType, TypeSchema, TypeSchemaRegistry};
50use shape_value::heap_value::{HeapKind, HeapValue, TypedObjectPtr};
51use shape_value::{KindedSlot, NativeKind, TypedObjectStorage, ValueSlot, VMError};
52use std::sync::Arc;
53
54pub fn marshal_args(
66 args: &[KindedSlot],
67 schemas: &TypeSchemaRegistry,
68) -> Result<Vec<u8>, VMError> {
69 let mut values = Vec::with_capacity(args.len());
70 for arg in args {
71 values.push(kinded_slot_to_msgpack(arg, schemas)?);
72 }
73 let arr = Rmp::Array(values);
74 let mut buf = Vec::new();
75 rmpv::encode::write_value(&mut buf, &arr).map_err(|e| {
76 VMError::RuntimeError(format!("Failed to marshal foreign function args: {}", e))
77 })?;
78 Ok(buf)
79}
80
81fn kinded_slot_to_msgpack(
89 slot: &KindedSlot,
90 schemas: &TypeSchemaRegistry,
91) -> Result<Rmp, VMError> {
92 let bits = slot.raw();
93 match slot.kind() {
94 NativeKind::Int64 => Ok(Rmp::Integer((bits as i64).into())),
96 NativeKind::Int32 => Ok(Rmp::Integer((bits as i32 as i64).into())),
97 NativeKind::Int16 => Ok(Rmp::Integer((bits as i16 as i64).into())),
98 NativeKind::Int8 => Ok(Rmp::Integer((bits as i8 as i64).into())),
99 NativeKind::UInt64 => Ok(Rmp::Integer((bits).into())),
100 NativeKind::UInt32 => Ok(Rmp::Integer(((bits as u32) as u64).into())),
101 NativeKind::UInt16 => Ok(Rmp::Integer(((bits as u16) as u64).into())),
102 NativeKind::UInt8 => Ok(Rmp::Integer(((bits as u8) as u64).into())),
103 NativeKind::IntSize => Ok(Rmp::Integer((bits as isize as i64).into())),
104 NativeKind::UIntSize => Ok(Rmp::Integer((bits as u64).into())),
105 NativeKind::Float64 => Ok(Rmp::F64(f64::from_bits(bits))),
106 NativeKind::Float32 => Ok(Rmp::F32(f32::from_bits(bits as u32))),
107 NativeKind::Char => {
108 let cp = bits as u32;
109 match char::from_u32(cp) {
110 Some(c) => Ok(Rmp::String(c.to_string().into())),
111 None => Err(VMError::RuntimeError(format!(
112 "foreign_marshal: invalid char codepoint {cp:#x}"
113 ))),
114 }
115 }
116 NativeKind::Bool => Ok(Rmp::Boolean(bits != 0)),
117 NativeKind::Null => Ok(Rmp::Nil),
118
119 NativeKind::String => {
121 if bits == 0 {
122 return Ok(Rmp::Nil);
123 }
124 let s: &str = unsafe {
129 let arc_ptr = bits as *const String;
130 (*arc_ptr).as_str()
131 };
132 Ok(Rmp::String(s.into()))
133 }
134 NativeKind::StringV2 => {
135 if bits == 0 {
136 return Ok(Rmp::Nil);
137 }
138 let ptr = bits as *const shape_value::v2::string_obj::StringObj;
143 let s = unsafe { shape_value::v2::string_obj::StringObj::as_str(ptr) };
144 Ok(Rmp::String(s.into()))
145 }
146 NativeKind::DecimalV2 => {
147 if bits == 0 {
148 return Ok(Rmp::Nil);
149 }
150 let ptr = bits as *const shape_value::v2::decimal_obj::DecimalObj;
151 let d = unsafe { shape_value::v2::decimal_obj::DecimalObj::value(ptr) };
152 Ok(Rmp::String(d.to_string().into()))
156 }
157
158 NativeKind::NullableInt64
160 | NativeKind::NullableInt32
161 | NativeKind::NullableInt16
162 | NativeKind::NullableInt8
163 | NativeKind::NullableUInt64
164 | NativeKind::NullableUInt32
165 | NativeKind::NullableUInt16
166 | NativeKind::NullableUInt8
167 | NativeKind::NullableIntSize
168 | NativeKind::NullableUIntSize
169 | NativeKind::NullableFloat64 => Err(VMError::NotImplemented(format!(
170 "foreign_marshal: Nullable scalar kind {:?} has no FFI wire \
171 projection yet (W17-foreign-ffi follow-up — same sentinel-rule \
172 gap as snapshot W17-snapshot-nullable).",
173 slot.kind()
174 ))),
175
176 NativeKind::Ptr(heap_kind) => heap_slot_to_msgpack(bits, heap_kind, schemas),
178 }
179}
180
181fn heap_slot_to_msgpack(
192 bits: u64,
193 heap_kind: HeapKind,
194 schemas: &TypeSchemaRegistry,
195) -> Result<Rmp, VMError> {
196 if bits == 0 {
197 return Ok(Rmp::Nil);
198 }
199 match heap_kind {
200 HeapKind::String => unsafe {
201 let arc = Arc::<String>::from_raw(bits as *const String);
204 let s = (*arc).clone();
205 let _ = Arc::into_raw(arc); Ok(Rmp::String(s.into()))
207 },
208 HeapKind::BigInt => unsafe {
209 let arc = Arc::<i64>::from_raw(bits as *const i64);
210 let v = *arc;
211 let _ = Arc::into_raw(arc);
212 Ok(Rmp::Integer(v.into()))
213 },
214 HeapKind::Decimal => unsafe {
215 let arc =
216 Arc::<rust_decimal::Decimal>::from_raw(bits as *const rust_decimal::Decimal);
217 let v = *arc;
218 let _ = Arc::into_raw(arc);
219 Ok(Rmp::String(v.to_string().into()))
220 },
221 HeapKind::Char => {
222 let cp = bits as u32;
225 match char::from_u32(cp) {
226 Some(c) => Ok(Rmp::String(c.to_string().into())),
227 None => Err(VMError::RuntimeError(format!(
228 "foreign_marshal: HeapKind::Char invalid codepoint {cp:#x}"
229 ))),
230 }
231 }
232 HeapKind::TypedObject => {
233 let ptr = bits as *const TypedObjectStorage;
242 let storage: &TypedObjectStorage = unsafe { &*ptr };
243 typed_object_storage_to_msgpack(storage, schemas)
244 }
245 other => Err(VMError::NotImplemented(format!(
253 "foreign_marshal: HeapKind::{other:?} has no FFI wire \
254 projection yet (W17-foreign-ffi follow-up). The audit \
255 §2.3 bounds the W17 round to typed-Arc payloads; rarer \
256 heap kinds (HashMap, HashSet, …) land per FFI demand."
257 ))),
258 }
259}
260
261fn typed_object_storage_to_msgpack(
271 storage: &TypedObjectStorage,
272 schemas: &TypeSchemaRegistry,
273) -> Result<Rmp, VMError> {
274 let schema = schemas.get_by_id(storage.schema_id as u32).ok_or_else(|| {
275 VMError::RuntimeError(format!(
276 "foreign_marshal: schema ID {} not found in registry",
277 storage.schema_id
278 ))
279 })?;
280
281 let mut entries = Vec::with_capacity(schema.fields.len());
282 let use_field_kinds = !storage.field_kinds.is_empty();
283 for (i, field) in schema.fields.iter().enumerate() {
284 let slot_bits = storage.slots[i].raw();
285 let kind: NativeKind = if use_field_kinds && i < storage.field_kinds.len() {
286 storage.field_kinds[i]
287 } else {
288 field.field_type.to_native_kind().map_err(|e| {
293 VMError::NotImplemented(format!(
294 "foreign_marshal: schema field '{}' has no static \
295 NativeKind projection ({e}); legacy schema lacks \
296 a parallel field-kinds track. W17-foreign-ffi \
297 follow-up.",
298 field.name
299 ))
300 })?
301 };
302 let borrowed = KindedSlot::new(ValueSlot::from_raw(slot_bits), kind);
306 let value = kinded_slot_to_msgpack(&borrowed, schemas);
307 std::mem::forget(borrowed);
308 let value = value?;
309 entries.push((Rmp::String(field.wire_name().to_string().into()), value));
310 }
311 Ok(Rmp::Map(entries))
312}
313
314pub fn unmarshal_result(
328 bytes: &[u8],
329 return_type: &str,
330 schema_id: Option<u32>,
331 schemas: &TypeSchemaRegistry,
332) -> Result<KindedSlot, VMError> {
333 if bytes.is_empty() {
334 return Ok(KindedSlot::none());
335 }
336 let mut cursor = std::io::Cursor::new(bytes);
337 let value: Rmp = rmpv::decode::read_value(&mut cursor).map_err(|e| {
338 VMError::RuntimeError(format!(
339 "Failed to unmarshal foreign function result: {}",
340 e
341 ))
342 })?;
343 let inner_type = strip_result_wrapper(return_type);
344 msgpack_to_kinded_slot(&value, inner_type, schema_id, schemas)
345}
346
347fn msgpack_to_kinded_slot(
350 val: &Rmp,
351 target: &str,
352 schema_id: Option<u32>,
353 schemas: &TypeSchemaRegistry,
354) -> Result<KindedSlot, VMError> {
355 if matches!(val, Rmp::Nil) {
357 if target == "none" || target == "Unit" || target == "()" {
358 return Ok(KindedSlot::none());
359 }
360 return Err(marshal_error(format!("expected {}, got None", target)));
361 }
362
363 match target {
364 "int" | "Int" => match val {
365 Rmp::Integer(i) => Ok(KindedSlot::from_int(
366 i.as_i64()
367 .or_else(|| i.as_u64().map(|n| n as i64))
368 .ok_or_else(|| marshal_error("integer out of range"))?,
369 )),
370 _ => Err(marshal_error(format!(
371 "expected int, got {}",
372 msgpack_type_name(val)
373 ))),
374 },
375 "float" | "number" | "Number" | "Float" => match val {
376 Rmp::F64(f) => Ok(KindedSlot::from_number(*f)),
377 Rmp::F32(f) => Ok(KindedSlot::from_number(*f as f64)),
378 Rmp::Integer(i) => {
379 let n = i
380 .as_i64()
381 .map(|n| n as f64)
382 .or_else(|| i.as_u64().map(|n| n as f64))
383 .ok_or_else(|| marshal_error("integer out of range for float coercion"))?;
384 Ok(KindedSlot::from_number(n))
385 }
386 _ => Err(marshal_error(format!(
387 "expected {}, got {}",
388 target,
389 msgpack_type_name(val)
390 ))),
391 },
392 "string" | "String" => match val {
393 Rmp::String(s) => {
394 let s = s.as_str().ok_or_else(|| {
395 marshal_error("string contains invalid UTF-8")
396 })?;
397 Ok(KindedSlot::from_string(s))
398 }
399 _ => Err(marshal_error(format!(
400 "expected string, got {}",
401 msgpack_type_name(val)
402 ))),
403 },
404 "bool" | "Bool" => match val {
405 Rmp::Boolean(b) => Ok(KindedSlot::from_bool(*b)),
406 _ => Err(marshal_error(format!(
407 "expected bool, got {}",
408 msgpack_type_name(val)
409 ))),
410 },
411 "none" | "Unit" | "()" => Err(marshal_error(format!(
412 "expected {}, got {}",
413 target,
414 msgpack_type_name(val)
415 ))),
416
417 s if (s.starts_with("Vec<") || s.starts_with("Array<")) && s.ends_with('>') => {
419 let prefix_len = if s.starts_with("Vec<") { 4 } else { 6 };
420 let _elem_type = &s[prefix_len..s.len() - 1];
421 Err(VMError::NotImplemented(format!(
427 "foreign_marshal::unmarshal_result: Array<T> return type \
428 ({s}) is a V3-S5 follow-up (per-element-kind TypedArray<T> \
429 monomorphization). Forms with object-of-array shapes \
430 (TypedObject containing Array<T> fields) similarly defer."
431 )))
432 }
433
434 s if s.starts_with('{') && s.ends_with('}') => match val {
436 Rmp::Map(entries) => match schema_id {
437 Some(sid) => marshal_typed_object_from_msgpack(entries, sid, schemas),
438 None => Err(VMError::NotImplemented(format!(
439 "foreign_marshal: object-typed return ({s}) lacks a \
440 registered schema_id; ad-hoc field-set inference \
441 is a follow-up."
442 ))),
443 },
444 _ => Err(marshal_error(format!(
445 "expected object, got {}",
446 msgpack_type_name(val)
447 ))),
448 },
449
450 _ if schema_id.is_some() => match val {
452 Rmp::Map(entries) => {
453 marshal_typed_object_from_msgpack(entries, schema_id.unwrap(), schemas)
454 }
455 _ => Err(marshal_error(format!(
456 "expected object for type '{}', got {}",
457 target,
458 msgpack_type_name(val)
459 ))),
460 },
461
462 _ => Err(VMError::NotImplemented(format!(
464 "foreign_marshal::unmarshal_result: return type '{target}' \
465 has no kind oracle (no schema_id, not a primitive). The \
466 §2.7.5 producer-side proof discipline refuses Bool-default \
467 fallback for unknown declared types."
468 ))),
469 }
470}
471
472fn marshal_typed_object_from_msgpack(
482 entries: &[(Rmp, Rmp)],
483 schema_id: u32,
484 schemas: &TypeSchemaRegistry,
485) -> Result<KindedSlot, VMError> {
486 let schema = schemas.get_by_id(schema_id).ok_or_else(|| {
487 VMError::RuntimeError(format!(
488 "FFI marshal: schema ID {} not found in registry",
489 schema_id
490 ))
491 })?;
492
493 let mut name_to_value: std::collections::HashMap<&str, &Rmp> =
495 std::collections::HashMap::with_capacity(entries.len());
496 for (k, v) in entries {
497 if let Rmp::String(s) = k {
498 if let Some(name) = s.as_str() {
499 name_to_value.insert(name, v);
500 }
501 }
502 }
503
504 let field_count = schema.fields.len();
505 let mut slots: Vec<ValueSlot> = Vec::with_capacity(field_count);
506 let mut field_kinds: Vec<NativeKind> = Vec::with_capacity(field_count);
507 let mut heap_mask: u64 = 0;
508
509 for (i, field) in schema.fields.iter().enumerate() {
510 let wire_name = field.wire_name();
511 let val = name_to_value.get(wire_name);
512 let (slot, kind) = build_field_slot(val, &field.field_type, &field.name, schemas)?;
513 if is_heap_kind(kind) {
514 heap_mask |= 1u64 << i;
515 }
516 slots.push(slot);
517 field_kinds.push(kind);
518 let _ = i; }
520
521 let ptr = TypedObjectStorage::_new(
522 schema_id as u64,
523 slots.into_boxed_slice(),
524 heap_mask,
525 Arc::from(field_kinds.into_boxed_slice()),
526 );
527 Ok(KindedSlot::new(
528 ValueSlot::from_typed_object_raw(ptr),
529 NativeKind::Ptr(HeapKind::TypedObject),
530 ))
531}
532
533fn is_heap_kind(kind: NativeKind) -> bool {
538 matches!(
539 kind,
540 NativeKind::String
541 | NativeKind::StringV2
542 | NativeKind::DecimalV2
543 | NativeKind::Ptr(_)
544 )
545}
546
547fn build_field_slot(
551 val: Option<&&Rmp>,
552 field_type: &FieldType,
553 field_name: &str,
554 schemas: &TypeSchemaRegistry,
555) -> Result<(ValueSlot, NativeKind), VMError> {
556 match field_type {
557 FieldType::I64 => {
558 let n = val
559 .and_then(|v| match v {
560 Rmp::Integer(i) => i.as_i64(),
561 _ => None,
562 })
563 .unwrap_or(0);
564 Ok((ValueSlot::from_int(n), NativeKind::Int64))
565 }
566 FieldType::F64 | FieldType::Decimal => {
567 let f = val
571 .and_then(|v| match v {
572 Rmp::F64(f) => Some(*f),
573 Rmp::F32(f) => Some(*f as f64),
574 Rmp::Integer(i) => i.as_i64().map(|n| n as f64),
575 Rmp::String(s) => {
576 s.as_str()
579 .and_then(|s| s.parse::<rust_decimal::Decimal>().ok())
580 .and_then(|d| {
581 use rust_decimal::prelude::ToPrimitive;
582 d.to_f64()
583 })
584 }
585 _ => None,
586 })
587 .unwrap_or(0.0);
588 Ok((ValueSlot::from_number(f), NativeKind::Float64))
589 }
590 FieldType::Bool => {
591 let b = val
592 .and_then(|v| match v {
593 Rmp::Boolean(b) => Some(*b),
594 _ => None,
595 })
596 .unwrap_or(false);
597 Ok((ValueSlot::from_bool(b), NativeKind::Bool))
598 }
599 FieldType::String => {
600 let s = val
601 .and_then(|v| match v {
602 Rmp::String(s) => s.as_str().map(|s| s.to_string()),
603 _ => None,
604 })
605 .unwrap_or_default();
606 Ok((
607 ValueSlot::from_string_arc(Arc::new(s)),
608 NativeKind::String,
609 ))
610 }
611 FieldType::I8 | FieldType::U8 | FieldType::I16 | FieldType::U16
612 | FieldType::I32 | FieldType::U32 | FieldType::U64
613 | FieldType::Timestamp => {
614 let n = val
615 .and_then(|v| match v {
616 Rmp::Integer(i) => i.as_i64(),
617 _ => None,
618 })
619 .unwrap_or(0);
620 let kind = field_type.to_native_kind().map_err(|e| {
621 VMError::RuntimeError(format!(
622 "foreign_marshal: width-int field '{field_name}': {e}"
623 ))
624 })?;
625 Ok((ValueSlot::from_int(n), kind))
628 }
629 FieldType::Object(type_name) => {
630 let nested_schema: TypeSchema = schemas
632 .get(type_name)
633 .ok_or_else(|| {
634 VMError::RuntimeError(format!(
635 "foreign_marshal: nested object field '{field_name}': \
636 type '{type_name}' not in registry"
637 ))
638 })?
639 .clone();
640 let map_entries = val.and_then(|v| match v {
641 Rmp::Map(m) => Some(m.as_slice()),
642 _ => None,
643 });
644 let nested_slot = match map_entries {
645 Some(entries) => {
646 marshal_typed_object_from_msgpack(entries, nested_schema.id, schemas)?
647 }
648 None => KindedSlot::none(),
649 };
650 let bits = nested_slot.raw();
654 let kind = nested_slot.kind();
655 std::mem::forget(nested_slot);
656 Ok((ValueSlot::from_raw(bits), kind))
657 }
658 FieldType::Array(_)
659 | FieldType::Option(_)
660 | FieldType::HashMap { .. }
661 | FieldType::Set(_)
662 | FieldType::Any => Err(VMError::NotImplemented(format!(
663 "foreign_marshal: field '{field_name}' of type {:?} has no \
664 FFI unmarshal projection yet (W17-foreign-ffi follow-up). \
665 Container kinds (Array, HashMap, Set, Option) defer to \
666 V3-S5 / W17.3-4 territory.",
667 field_type
668 ))),
669 }
670}
671
672fn strip_result_wrapper(s: &str) -> &str {
678 if s.starts_with("Result<") && s.ends_with('>') {
679 &s[7..s.len() - 1]
680 } else {
681 s
682 }
683}
684
685fn marshal_error(msg: impl Into<String>) -> VMError {
686 VMError::RuntimeError(msg.into())
687}
688
689fn msgpack_type_name(val: &Rmp) -> &'static str {
690 match val {
691 Rmp::Nil => "nil",
692 Rmp::Boolean(_) => "bool",
693 Rmp::Integer(_) => "int",
694 Rmp::F32(_) | Rmp::F64(_) => "float",
695 Rmp::String(_) => "string",
696 Rmp::Array(_) => "array",
697 Rmp::Map(_) => "map",
698 Rmp::Binary(_) => "binary",
699 Rmp::Ext(_, _) => "ext",
700 }
701}
702
703#[allow(dead_code)]
708fn _unused_imports_keepalive(_hv: &HeapValue, _tp: &TypedObjectPtr) {}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use shape_runtime::type_schema::{FieldType, TypeSchemaRegistry};
714
715 #[test]
716 fn marshal_scalar_args_roundtrips_through_msgpack() {
717 let schemas = TypeSchemaRegistry::new();
718 let args = vec![
719 KindedSlot::from_int(42),
720 KindedSlot::from_number(3.14),
721 KindedSlot::from_bool(true),
722 KindedSlot::from_string("hello"),
723 ];
724 let bytes = marshal_args(&args, &schemas).expect("marshal must succeed");
725 let mut cursor = std::io::Cursor::new(bytes.as_slice());
726 let decoded = rmpv::decode::read_value(&mut cursor).expect("valid msgpack");
727 let arr = decoded.as_array().expect("outer array");
728 assert_eq!(arr.len(), 4);
729 assert_eq!(arr[0].as_i64(), Some(42));
730 assert!(matches!(arr[1], Rmp::F64(f) if (f - 3.14).abs() < 1e-9));
731 assert_eq!(arr[2].as_bool(), Some(true));
732 assert_eq!(arr[3].as_str(), Some("hello"));
733 }
734
735 #[test]
736 fn unmarshal_int_result_produces_int64_kind() {
737 let schemas = TypeSchemaRegistry::new();
738 let arr = Rmp::Integer(42i64.into());
739 let mut bytes = Vec::new();
740 rmpv::encode::write_value(&mut bytes, &arr).unwrap();
741 let slot = unmarshal_result(&bytes, "int", None, &schemas).expect("unmarshal");
742 assert_eq!(slot.kind(), NativeKind::Int64);
743 assert_eq!(slot.as_i64(), Some(42));
744 }
745
746 #[test]
747 fn unmarshal_string_result_produces_string_kind() {
748 let schemas = TypeSchemaRegistry::new();
749 let v = Rmp::String("hi".into());
750 let mut bytes = Vec::new();
751 rmpv::encode::write_value(&mut bytes, &v).unwrap();
752 let slot = unmarshal_result(&bytes, "string", None, &schemas).expect("unmarshal");
753 assert_eq!(slot.kind(), NativeKind::String);
754 assert_eq!(slot.as_str(), Some("hi"));
755 }
756
757 #[test]
758 fn unmarshal_result_strips_result_wrapper() {
759 let schemas = TypeSchemaRegistry::new();
760 let v = Rmp::Boolean(true);
761 let mut bytes = Vec::new();
762 rmpv::encode::write_value(&mut bytes, &v).unwrap();
763 let slot = unmarshal_result(&bytes, "Result<bool>", None, &schemas).expect("unmarshal");
764 assert_eq!(slot.kind(), NativeKind::Bool);
765 assert_eq!(slot.as_bool(), Some(true));
766 }
767
768 #[test]
769 fn unmarshal_empty_bytes_returns_none_slot() {
770 let schemas = TypeSchemaRegistry::new();
771 let slot = unmarshal_result(&[], "int", None, &schemas).expect("empty-bytes branch");
772 assert_eq!(slot.kind(), NativeKind::Null);
773 }
774
775 #[test]
776 fn unmarshal_wire_vs_declared_mismatch_surfaces_structured_error() {
777 let schemas = TypeSchemaRegistry::new();
778 let v = Rmp::Boolean(true);
779 let mut bytes = Vec::new();
780 rmpv::encode::write_value(&mut bytes, &v).unwrap();
781 let err = unmarshal_result(&bytes, "int", None, &schemas).unwrap_err();
782 assert!(matches!(err, VMError::RuntimeError(_)));
784 }
785}