Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/pack-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub use hash::{
HASH_U8,
};
pub use parse::{parse_value, ParseError};
pub use value::{FromValue, Value, ValueType};
pub use value::{FromValue, KnownValueType, Value, ValueType};

// Re-export derive macro when feature is enabled
#[cfg(feature = "derive")]
Expand Down
219 changes: 164 additions & 55 deletions crates/pack-abi/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,26 +402,147 @@ impl From<&str> for Value {
}
}

impl<T: Into<Value>> From<Vec<T>> for Value {
// ============================================================================
// KnownValueType — compile-time ValueType for a Rust type.
// ============================================================================
//
// The runtime `infer_type` only works on a concrete value; an empty
// `Vec<T>` or `None: Option<T>` carries no information. To encode an
// empty `Vec<Binding>` correctly (as `list<binding>`, not the default
// `list<s32>`), we need T's ValueType at the type level. Types implement
// `KnownValueType` to declare it — primitives statically, records via
// the `#[derive(GraphValue)]` macro.

/// Compile-time `ValueType` for a Rust type. Used by `From<Vec<T>>` and
/// `From<Option<T>>` so that empty containers still encode with the
/// correct element/inner type.
pub trait KnownValueType {
fn known_value_type() -> ValueType;
}

macro_rules! known_primitive {
($t:ty, $vt:expr) => {
impl KnownValueType for $t {
fn known_value_type() -> ValueType {
$vt
}
}
};
}
known_primitive!(bool, ValueType::Bool);
known_primitive!(u8, ValueType::U8);
known_primitive!(u16, ValueType::U16);
known_primitive!(u32, ValueType::U32);
known_primitive!(u64, ValueType::U64);
known_primitive!(i8, ValueType::S8);
known_primitive!(i16, ValueType::S16);
known_primitive!(i32, ValueType::S32);
known_primitive!(i64, ValueType::S64);
known_primitive!(f32, ValueType::F32);
known_primitive!(f64, ValueType::F64);
known_primitive!(char, ValueType::Char);
known_primitive!(String, ValueType::String);

impl<T: KnownValueType> KnownValueType for Vec<T> {
fn known_value_type() -> ValueType {
ValueType::List(Box::new(T::known_value_type()))
}
}

impl<T: KnownValueType> KnownValueType for Option<T> {
fn known_value_type() -> ValueType {
ValueType::Option(Box::new(T::known_value_type()))
}
}

impl<T: KnownValueType, E: KnownValueType> KnownValueType for core::result::Result<T, E> {
fn known_value_type() -> ValueType {
ValueType::Result {
ok: Box::new(T::known_value_type()),
err: Box::new(E::known_value_type()),
}
}
}

impl<T: KnownValueType> KnownValueType for Box<T> {
fn known_value_type() -> ValueType {
T::known_value_type()
}
}

impl KnownValueType for () {
fn known_value_type() -> ValueType {
ValueType::Tuple(Vec::new())
}
}

/// `Value` is dynamic — its actual ValueType is only known at runtime
/// (`Value::infer_type`). The compile-time fallback is `String`, matching
/// the previous `PackType for Value` behavior. Prefer concrete types when
/// you need accurate static typing.
impl KnownValueType for Value {
fn known_value_type() -> ValueType {
ValueType::String
}
}

impl<A: KnownValueType> KnownValueType for (A,) {
fn known_value_type() -> ValueType {
ValueType::Tuple(alloc::vec![A::known_value_type()])
}
}

impl<A: KnownValueType, B: KnownValueType> KnownValueType for (A, B) {
fn known_value_type() -> ValueType {
ValueType::Tuple(alloc::vec![A::known_value_type(), B::known_value_type()])
}
}

impl<A: KnownValueType, B: KnownValueType, C: KnownValueType> KnownValueType for (A, B, C) {
fn known_value_type() -> ValueType {
ValueType::Tuple(alloc::vec![
A::known_value_type(),
B::known_value_type(),
C::known_value_type()
])
}
}

impl<A: KnownValueType, B: KnownValueType, C: KnownValueType, D: KnownValueType> KnownValueType
for (A, B, C, D)
{
fn known_value_type() -> ValueType {
ValueType::Tuple(alloc::vec![
A::known_value_type(),
B::known_value_type(),
C::known_value_type(),
D::known_value_type()
])
}
}

// ============================================================================
// From implementations for collections — use KnownValueType for the
// element/inner type so empty values still carry correct type info.
// ============================================================================

impl<T: Into<Value> + KnownValueType> From<Vec<T>> for Value {
fn from(v: Vec<T>) -> Self {
let items: Vec<Value> = v.into_iter().map(Into::into).collect();
// Infer elem_type from first item, default to S32
let elem_type = items
.first()
.map(|v| v.infer_type())
.unwrap_or(ValueType::S32);
Value::List { elem_type, items }
Value::List {
elem_type: T::known_value_type(),
items,
}
}
}

impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
impl<T: Into<Value> + KnownValueType, const N: usize> From<[T; N]> for Value {
fn from(v: [T; N]) -> Self {
let items: Vec<Value> = v.into_iter().map(Into::into).collect();
let elem_type = items
.first()
.map(|v| v.infer_type())
.unwrap_or(ValueType::S32);
Value::List { elem_type, items }
Value::List {
elem_type: T::known_value_type(),
items,
}
}
}

Expand Down Expand Up @@ -452,17 +573,12 @@ impl<T: TryFrom<Value, Error = ConversionError>, const N: usize> TryFrom<Value>
}
}

impl<T: Into<Value>> From<Option<T>> for Value {
impl<T: Into<Value> + KnownValueType> From<Option<T>> for Value {
fn from(v: Option<T>) -> Self {
let (inner_type, value) = match v {
Some(x) => {
let val: Value = x.into();
let ty = val.infer_type();
(ty, Some(Box::new(val)))
}
None => (ValueType::S32, None), // Default type for None
};
Value::Option { inner_type, value }
Value::Option {
inner_type: T::known_value_type(),
value: v.map(|x| Box::new(x.into())),
}
}
}

Expand Down Expand Up @@ -663,14 +779,13 @@ impl<T: TryFrom<Value, Error = ConversionError>> TryFrom<Value> for Vec<T> {
}
}

impl<T: Into<Value> + Ord> From<BTreeSet<T>> for Value {
impl<T: Into<Value> + KnownValueType + Ord> From<BTreeSet<T>> for Value {
fn from(v: BTreeSet<T>) -> Self {
let items: Vec<Value> = v.into_iter().map(Into::into).collect();
let elem_type = items
.first()
.map(|v| v.infer_type())
.unwrap_or(ValueType::S32);
Value::List { elem_type, items }
Value::List {
elem_type: T::known_value_type(),
items,
}
}
}

Expand All @@ -690,17 +805,18 @@ impl<T: TryFrom<Value, Error = ConversionError> + Ord> TryFrom<Value> for BTreeS
}
}

impl<K: Into<Value> + Ord, V: Into<Value>> From<BTreeMap<K, V>> for Value {
impl<K: Into<Value> + KnownValueType + Ord, V: Into<Value> + KnownValueType> From<BTreeMap<K, V>>
for Value
{
fn from(v: BTreeMap<K, V>) -> Self {
let items: Vec<Value> = v
.into_iter()
.map(|(k, v)| Value::Tuple(Vec::from([k.into(), v.into()])))
.collect();
let elem_type = items
.first()
.map(|v| v.infer_type())
.unwrap_or(ValueType::S32);
Value::List { elem_type, items }
Value::List {
elem_type: ValueType::Tuple(alloc::vec![K::known_value_type(), V::known_value_type()]),
items,
}
}
}

Expand Down Expand Up @@ -949,27 +1065,20 @@ impl<
// Result conversions (now using Value::Result directly)
// ============================================================================

impl<T: Into<Value>, E: Into<Value>> From<core::result::Result<T, E>> for Value {
impl<T: Into<Value> + KnownValueType, E: Into<Value> + KnownValueType>
From<core::result::Result<T, E>> for Value
{
fn from(r: core::result::Result<T, E>) -> Self {
match r {
Ok(v) => {
let val: Value = v.into();
let ok_type = val.infer_type();
Value::Result {
ok_type,
err_type: ValueType::String, // Default error type
value: Ok(Box::new(val)),
}
}
Err(e) => {
let val: Value = e.into();
let err_type = val.infer_type();
Value::Result {
ok_type: ValueType::S32, // Default ok type
err_type,
value: Err(Box::new(val)),
}
}
let ok_type = T::known_value_type();
let err_type = E::known_value_type();
let value = match r {
Ok(v) => Ok(Box::new(v.into())),
Err(e) => Err(Box::new(e.into())),
};
Value::Result {
ok_type,
err_type,
value,
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions crates/pack-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ fn derive_struct(
}
}
}

impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
fn known_value_type() -> #krate::ValueType {
#krate::ValueType::Record(
#krate::__private::String::from(#type_name_str)
)
}
}
}
}
Fields::Unnamed(fields) => {
Expand All @@ -219,6 +227,7 @@ fn derive_struct(
}).collect();

let field_count = fields.unnamed.len();
let field_types: Vec<_> = fields.unnamed.iter().map(|f| &f.ty).collect();

quote! {
impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
Expand Down Expand Up @@ -251,6 +260,14 @@ fn derive_struct(
}
}
}

impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
fn known_value_type() -> #krate::ValueType {
#krate::ValueType::Tuple(#krate::__private::vec![
#(<#field_types as #krate::KnownValueType>::known_value_type()),*
])
}
}
}
}
Fields::Unit => {
Expand Down Expand Up @@ -282,6 +299,12 @@ fn derive_struct(
}
}
}

impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
fn known_value_type() -> #krate::ValueType {
#krate::ValueType::Tuple(#krate::__private::vec![])
}
}
}
}
}
Expand Down Expand Up @@ -500,6 +523,12 @@ fn derive_enum(
}
}
}

impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
fn known_value_type() -> #krate::ValueType {
#krate::ValueType::Variant(#krate::__private::String::from(#type_name_str))
}
}
}
}

Expand Down
23 changes: 15 additions & 8 deletions crates/pack-guest-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,20 +406,27 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
Ok(output.into())
}
} else if param_names.len() == 1 {
// Single typed parameter - extract from value directly
// Single typed parameter. Theater always wraps inputs in a Tuple, so
// a 1-param function receives Tuple([arg]) — unwrap before converting.
// Fall back to a direct try_into() for callers that pass the value
// unwrapped.
let param_name = &param_names[0];
let param_type = &param_types[0];
quote! {
// Extract single typed parameter
let #param_name: #param_type = match value.try_into() {
Ok(v) => v,
Err(_) => return Err("failed to convert parameter"),
let #param_name: #param_type = match value {
packr_guest::Value::Tuple(mut items) if items.len() == 1 => {
match items.remove(0).try_into() {
Ok(v) => v,
Err(_) => return Err("failed to convert parameter"),
}
}
other => match other.try_into() {
Ok(v) => v,
Err(_) => return Err("failed to convert parameter"),
},
};

// Call user's function
let output = #inner_fn_name(#param_name);

// Convert output to Value
Ok(output.into())
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/pack-guest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub extern crate alloc;
pub use packr_guest_macros::{export, import, import_from, pack_types, wit, world};

// Re-export useful types from pack-abi
pub use packr_abi::{decode, encode, ConversionError, FromValue, Value, ValueType};
pub use packr_abi::{decode, encode, ConversionError, FromValue, KnownValueType, Value, ValueType};

// Re-export derive macro
#[cfg(feature = "derive")]
Expand Down
Loading
Loading