Skip to main content

packr_derive/
lib.rs

1//! Derive macros for pack-abi Value conversion.
2//!
3//! This crate provides `#[derive(GraphValue)]` which generates implementations
4//! of `From<T> for Value` and `TryFrom<Value> for T`.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use packr_abi::{GraphValue, Value};
10//!
11//! #[derive(GraphValue)]
12//! struct Point {
13//!     x: i64,
14//!     y: i64,
15//! }
16//!
17//! let point = Point { x: 10, y: 20 };
18//! let value: Value = point.into();
19//! let back: Point = value.try_into().unwrap();
20//! ```
21//!
22//! # Crate Path
23//!
24//! By default, the macro expects `packr_abi` to be in scope. For `no_std` guests
25//! using `packr_guest`, specify the crate path:
26//!
27//! ```ignore
28//! use packr_guest::GraphValue;
29//!
30//! #[derive(GraphValue)]
31//! #[graph(crate = "packr_guest::composite_abi")]
32//! struct MyState {
33//!     count: i32,
34//! }
35//! ```
36
37use proc_macro::TokenStream;
38use quote::{format_ident, quote};
39use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields, Meta};
40
41/// Extract the crate path from `#[graph(crate = "...")]` attribute.
42/// Defaults to `packr_abi` if not specified.
43fn get_crate_path(attrs: &[Attribute]) -> proc_macro2::TokenStream {
44    for attr in attrs {
45        if attr.path().is_ident("graph") {
46            if let Meta::List(list) = &attr.meta {
47                let tokens = list.tokens.to_string();
48                // Parse crate = "..."
49                if let Some(rest) = tokens.strip_prefix("crate") {
50                    let rest = rest.trim();
51                    if let Some(rest) = rest.strip_prefix('=') {
52                        let rest = rest.trim();
53                        if rest.starts_with('"') && rest.ends_with('"') {
54                            let path_str = &rest[1..rest.len() - 1];
55                            // Convert string path to token stream
56                            let path: syn::Path =
57                                syn::parse_str(path_str).expect("Invalid crate path");
58                            return quote! { #path };
59                        }
60                    }
61                }
62            }
63        }
64    }
65    // Default to packr_abi
66    quote! { packr_abi }
67}
68
69/// Derive macro for converting between Rust types and `Value`.
70///
71/// # Structs
72///
73/// Structs are converted to `Value::Record` with field names as keys.
74///
75/// ```ignore
76/// #[derive(GraphValue)]
77/// struct Person {
78///     name: String,
79///     age: i64,
80/// }
81/// ```
82///
83/// # Enums
84///
85/// Enums are converted to `Value::Variant` with the variant index as tag.
86///
87/// ```ignore
88/// #[derive(GraphValue)]
89/// enum Shape {
90///     Circle(f64),           // tag 0, payload = radius
91///     Rectangle(f64, f64),   // tag 1, payload = tuple(width, height)
92///     Point,                 // tag 2, no payload
93/// }
94/// ```
95///
96/// # Attributes
97///
98/// - `#[graph(crate = "path")]` - Specify the crate path (default: `packr_abi`)
99/// - `#[graph(rename = "name")]` - Use a different name for field/variant
100/// - `#[graph(tag = N)]` - Use explicit tag number for variant
101#[proc_macro_derive(GraphValue, attributes(graph))]
102pub fn derive_graph_value(input: TokenStream) -> TokenStream {
103    let input = parse_macro_input!(input as DeriveInput);
104    let crate_path = get_crate_path(&input.attrs);
105
106    let expanded = match &input.data {
107        Data::Struct(data) => derive_struct(&input, data, &crate_path),
108        Data::Enum(data) => derive_enum(&input, data, &crate_path),
109        Data::Union(_) => {
110            return syn::Error::new_spanned(&input, "GraphValue cannot be derived for unions")
111                .to_compile_error()
112                .into();
113        }
114    };
115
116    expanded.into()
117}
118
119fn derive_struct(
120    input: &DeriveInput,
121    data: &syn::DataStruct,
122    krate: &proc_macro2::TokenStream,
123) -> proc_macro2::TokenStream {
124    let name = &input.ident;
125    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
126
127    match &data.fields {
128        Fields::Named(fields) => {
129            // Generate TryFrom<Value> for T
130            let field_from_value: Vec<_> = fields.named.iter().map(|f| {
131                let field_name = f.ident.as_ref().unwrap();
132                let field_name_str = get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
133                let field_type = &f.ty;
134                quote! {
135                    #field_name: {
136                        let field_value = fields.iter()
137                            .find(|(name, _)| name == #field_name_str)
138                            .map(|(_, v)| v.clone())
139                            .ok_or_else(|| #krate::ConversionError::MissingField(
140                                #krate::__private::String::from(#field_name_str)
141                            ))?;
142                        <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(field_value)
143                            .map_err(|e| #krate::ConversionError::FieldError(
144                                #krate::__private::String::from(#field_name_str),
145                                #krate::__private::Box::new(e)
146                            ))?
147                    }
148                }
149            }).collect();
150
151            let field_count = fields.named.len();
152
153            // Generate field accessors for From impl
154            let field_accessors: Vec<_> = fields
155                .named
156                .iter()
157                .map(|f| {
158                    let field_name = f.ident.as_ref().unwrap();
159                    let field_name_str =
160                        get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
161                    quote! {
162                        (
163                            #krate::__private::String::from(#field_name_str),
164                            #krate::Value::from(value.#field_name)
165                        )
166                    }
167                })
168                .collect();
169
170            let type_name_str = name.to_string();
171
172            quote! {
173                impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
174                    fn from(value: #name #ty_generics) -> #krate::Value {
175                        #krate::Value::Record {
176                            type_name: #krate::__private::String::from(#type_name_str),
177                            fields: #krate::__private::vec![
178                                #(#field_accessors),*
179                            ],
180                        }
181                    }
182                }
183
184                impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
185                    type Error = #krate::ConversionError;
186
187                    fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
188                        match value {
189                            #krate::Value::Record { fields, .. } => {
190                                if fields.len() != #field_count {
191                                    return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
192                                        expected: #field_count,
193                                        got: fields.len(),
194                                    });
195                                }
196                                #krate::__private::Ok(Self {
197                                    #(#field_from_value),*
198                                })
199                            }
200                            other => #krate::__private::Err(#krate::ConversionError::ExpectedRecord(
201                                #krate::__private::format!("{:?}", other)
202                            )),
203                        }
204                    }
205                }
206
207                impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
208                    fn known_value_type() -> #krate::ValueType {
209                        #krate::ValueType::Record(
210                            #krate::__private::String::from(#type_name_str)
211                        )
212                    }
213                }
214            }
215        }
216        Fields::Unnamed(fields) => {
217            // Tuple struct -> Value::Tuple
218            let field_indices: Vec<_> = (0..fields.unnamed.len()).map(syn::Index::from).collect();
219
220            let field_from_value: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, f)| {
221                let field_type = &f.ty;
222                quote! {
223                    <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(
224                        fields.get(#i).cloned().ok_or_else(|| #krate::ConversionError::MissingIndex(#i))?
225                    ).map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?
226                }
227            }).collect();
228
229            let field_count = fields.unnamed.len();
230            let field_types: Vec<_> = fields.unnamed.iter().map(|f| &f.ty).collect();
231
232            quote! {
233                impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
234                    fn from(value: #name #ty_generics) -> #krate::Value {
235                        #krate::Value::Tuple(#krate::__private::vec![
236                            #(#krate::Value::from(value.#field_indices)),*
237                        ])
238                    }
239                }
240
241                impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
242                    type Error = #krate::ConversionError;
243
244                    fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
245                        match value {
246                            #krate::Value::Tuple(fields) => {
247                                if fields.len() != #field_count {
248                                    return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
249                                        expected: #field_count,
250                                        got: fields.len(),
251                                    });
252                                }
253                                #krate::__private::Ok(Self(
254                                    #(#field_from_value),*
255                                ))
256                            }
257                            other => #krate::__private::Err(#krate::ConversionError::ExpectedTuple(
258                                #krate::__private::format!("{:?}", other)
259                            )),
260                        }
261                    }
262                }
263
264                impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
265                    fn known_value_type() -> #krate::ValueType {
266                        #krate::ValueType::Tuple(#krate::__private::vec![
267                            #(<#field_types as #krate::KnownValueType>::known_value_type()),*
268                        ])
269                    }
270                }
271            }
272        }
273        Fields::Unit => {
274            // Unit struct -> Value::Tuple([])
275            quote! {
276                impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
277                    fn from(_: #name #ty_generics) -> #krate::Value {
278                        #krate::Value::Tuple(#krate::__private::vec![])
279                    }
280                }
281
282                impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
283                    type Error = #krate::ConversionError;
284
285                    fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
286                        match value {
287                            #krate::Value::Tuple(fields) if fields.is_empty() => {
288                                #krate::__private::Ok(Self)
289                            }
290                            #krate::Value::Tuple(fields) => {
291                                #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
292                                    expected: 0,
293                                    got: fields.len(),
294                                })
295                            }
296                            other => #krate::__private::Err(#krate::ConversionError::ExpectedTuple(
297                                #krate::__private::format!("{:?}", other)
298                            )),
299                        }
300                    }
301                }
302
303                impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
304                    fn known_value_type() -> #krate::ValueType {
305                        #krate::ValueType::Tuple(#krate::__private::vec![])
306                    }
307                }
308            }
309        }
310    }
311}
312
313fn derive_enum(
314    input: &DeriveInput,
315    data: &syn::DataEnum,
316    krate: &proc_macro2::TokenStream,
317) -> proc_macro2::TokenStream {
318    let name = &input.ident;
319    let type_name_str = name.to_string();
320    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
321
322    // Generate match arms for From<T> for Value
323    let to_value_arms: Vec<_> = data
324        .variants
325        .iter()
326        .enumerate()
327        .map(|(default_tag, variant)| {
328            let variant_name = &variant.ident;
329            let case_name_str = variant_name.to_string();
330            let tag = get_tag(&variant.attrs).unwrap_or(default_tag);
331
332            match &variant.fields {
333                Fields::Named(fields) => {
334                    let field_names: Vec<_> = fields
335                        .named
336                        .iter()
337                        .map(|f| f.ident.as_ref().unwrap())
338                        .collect();
339                    // For named fields, we wrap in a Record as the single payload element
340                    let field_to_value: Vec<_> = fields
341                        .named
342                        .iter()
343                        .map(|f| {
344                            let field_name = f.ident.as_ref().unwrap();
345                            let field_name_str =
346                                get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
347                            quote! {
348                                (
349                                    #krate::__private::String::from(#field_name_str),
350                                    #krate::Value::from(#field_name)
351                                )
352                            }
353                        })
354                        .collect();
355
356                    quote! {
357                        #name::#variant_name { #(#field_names),* } => {
358                            #krate::Value::Variant {
359                                type_name: #krate::__private::String::from(#type_name_str),
360                                case_name: #krate::__private::String::from(#case_name_str),
361                                tag: #tag,
362                                payload: #krate::__private::vec![
363                                    #krate::Value::Record {
364                                        type_name: #krate::__private::String::from(#case_name_str),
365                                        fields: #krate::__private::vec![#(#field_to_value),*],
366                                    }
367                                ],
368                            }
369                        }
370                    }
371                }
372                Fields::Unnamed(fields) => {
373                    let field_names: Vec<_> = (0..fields.unnamed.len())
374                        .map(|i| format_ident!("f{}", i))
375                        .collect();
376
377                    // Payload is a vec of all the field values
378                    quote! {
379                        #name::#variant_name(#(#field_names),*) => {
380                            #krate::Value::Variant {
381                                type_name: #krate::__private::String::from(#type_name_str),
382                                case_name: #krate::__private::String::from(#case_name_str),
383                                tag: #tag,
384                                payload: #krate::__private::vec![
385                                    #(#krate::Value::from(#field_names)),*
386                                ],
387                            }
388                        }
389                    }
390                }
391                Fields::Unit => {
392                    quote! {
393                        #name::#variant_name => {
394                            #krate::Value::Variant {
395                                type_name: #krate::__private::String::from(#type_name_str),
396                                case_name: #krate::__private::String::from(#case_name_str),
397                                tag: #tag,
398                                payload: #krate::__private::vec![],
399                            }
400                        }
401                    }
402                }
403            }
404        })
405        .collect();
406
407    // Generate match arms for TryFrom<Value> for T
408    let from_value_arms: Vec<_> = data.variants.iter().enumerate().map(|(default_tag, variant)| {
409        let variant_name = &variant.ident;
410        let tag = get_tag(&variant.attrs).unwrap_or(default_tag);
411
412        match &variant.fields {
413            Fields::Named(fields) => {
414                let field_from_value: Vec<_> = fields.named.iter().map(|f| {
415                    let field_name = f.ident.as_ref().unwrap();
416                    let field_name_str = get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
417                    let field_type = &f.ty;
418                    quote! {
419                        #field_name: {
420                            let field_value = record_fields.iter()
421                                .find(|(name, _)| name == #field_name_str)
422                                .map(|(_, v)| v.clone())
423                                .ok_or_else(|| #krate::ConversionError::MissingField(
424                                    #krate::__private::String::from(#field_name_str)
425                                ))?;
426                            <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(field_value)
427                                .map_err(|e| #krate::ConversionError::FieldError(
428                                    #krate::__private::String::from(#field_name_str),
429                                    #krate::__private::Box::new(e)
430                                ))?
431                        }
432                    }
433                }).collect();
434
435                quote! {
436                    #tag => {
437                        // For named fields, payload should contain a single Record
438                        if payload.len() != 1 {
439                            return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
440                                expected: 1,
441                                got: payload.len(),
442                            });
443                        }
444                        match &payload[0] {
445                            #krate::Value::Record { fields: record_fields, .. } => {
446                                #krate::__private::Ok(#name::#variant_name {
447                                    #(#field_from_value),*
448                                })
449                            }
450                            other => #krate::__private::Err(#krate::ConversionError::ExpectedRecord(
451                                #krate::__private::format!("{:?}", other)
452                            )),
453                        }
454                    }
455                }
456            }
457            Fields::Unnamed(fields) => {
458                let field_count = fields.unnamed.len();
459                let field_conversions: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, f)| {
460                    let field_type = &f.ty;
461                    quote! {
462                        <#field_type as #krate::__private::TryFrom<#krate::Value>>::try_from(
463                            payload.get(#i).cloned().ok_or_else(|| #krate::ConversionError::MissingIndex(#i))?
464                        ).map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?
465                    }
466                }).collect();
467
468                quote! {
469                    #tag => {
470                        if payload.len() != #field_count {
471                            return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
472                                expected: #field_count,
473                                got: payload.len(),
474                            });
475                        }
476                        #krate::__private::Ok(#name::#variant_name(
477                            #(#field_conversions),*
478                        ))
479                    }
480                }
481            }
482            Fields::Unit => {
483                quote! {
484                    #tag => {
485                        if !payload.is_empty() {
486                            return #krate::__private::Err(#krate::ConversionError::UnexpectedPayload);
487                        }
488                        #krate::__private::Ok(#name::#variant_name)
489                    }
490                }
491            }
492        }
493    }).collect();
494
495    let variant_count = data.variants.len();
496
497    quote! {
498        impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
499            fn from(value: #name #ty_generics) -> #krate::Value {
500                match value {
501                    #(#to_value_arms),*
502                }
503            }
504        }
505
506        impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
507            type Error = #krate::ConversionError;
508
509            fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, Self::Error> {
510                match value {
511                    #krate::Value::Variant { tag, payload, .. } => {
512                        match tag {
513                            #(#from_value_arms),*
514                            other => #krate::__private::Err(#krate::ConversionError::UnknownTag {
515                                tag: other,
516                                max: #variant_count,
517                            }),
518                        }
519                    }
520                    other => #krate::__private::Err(#krate::ConversionError::ExpectedVariant(
521                        #krate::__private::format!("{:?}", other)
522                    )),
523                }
524            }
525        }
526
527        impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
528            fn known_value_type() -> #krate::ValueType {
529                #krate::ValueType::Variant(#krate::__private::String::from(#type_name_str))
530            }
531        }
532    }
533}
534
535/// Extract `#[graph(rename = "...")]` attribute
536fn get_rename(attrs: &[Attribute]) -> Option<String> {
537    for attr in attrs {
538        if attr.path().is_ident("graph") {
539            if let Meta::List(list) = &attr.meta {
540                let tokens = list.tokens.to_string();
541                // Parse rename = "..."
542                if let Some(rest) = tokens.strip_prefix("rename") {
543                    let rest = rest.trim();
544                    if let Some(rest) = rest.strip_prefix('=') {
545                        let rest = rest.trim();
546                        if rest.starts_with('"') && rest.ends_with('"') {
547                            return Some(rest[1..rest.len() - 1].to_string());
548                        }
549                    }
550                }
551            }
552        }
553    }
554    None
555}
556
557/// Extract `#[graph(tag = N)]` attribute
558fn get_tag(attrs: &[Attribute]) -> Option<usize> {
559    for attr in attrs {
560        if attr.path().is_ident("graph") {
561            if let Meta::List(list) = &attr.meta {
562                let tokens = list.tokens.to_string();
563                // Parse tag = N
564                if let Some(rest) = tokens.strip_prefix("tag") {
565                    let rest = rest.trim();
566                    if let Some(rest) = rest.strip_prefix('=') {
567                        let rest = rest.trim();
568                        return rest.parse().ok();
569                    }
570                }
571            }
572        }
573    }
574    None
575}