1use proc_macro::TokenStream;
38use quote::{format_ident, quote};
39use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields, Meta};
40
41fn 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 for part in tokens.split(',') {
54 let part = part.trim();
55 if let Some(rest) = part.strip_prefix("crate") {
56 let rest = rest.trim();
57 if let Some(rest) = rest.strip_prefix('=') {
58 let rest = rest.trim();
59 if rest.len() >= 2 && rest.starts_with('"') && rest.ends_with('"') {
60 let path_str = &rest[1..rest.len() - 1];
61 let path: syn::Path =
63 syn::parse_str(path_str).expect("Invalid crate path");
64 return quote! { #path };
65 }
66 }
67 }
68 }
69 }
70 }
71 }
72 quote! { packr_abi }
74}
75
76#[proc_macro_derive(GraphValue, attributes(graph))]
116pub fn derive_graph_value(input: TokenStream) -> TokenStream {
117 let input = parse_macro_input!(input as DeriveInput);
118 let crate_path = get_crate_path(&input.attrs);
119
120 let expanded = match &input.data {
121 Data::Struct(data) => derive_struct(&input, data, &crate_path),
122 Data::Enum(data) => derive_enum(&input, data, &crate_path),
123 Data::Union(_) => {
124 return syn::Error::new_spanned(&input, "GraphValue cannot be derived for unions")
125 .to_compile_error()
126 .into();
127 }
128 };
129
130 expanded.into()
131}
132
133fn has_forward_compatible(attrs: &[Attribute]) -> bool {
142 for attr in attrs {
143 if attr.path().is_ident("graph") {
144 if let Meta::List(list) = &attr.meta {
145 let tokens = list.tokens.to_string();
146 if tokens.split(',').any(|t| t.trim() == "forward_compatible") {
147 return true;
148 }
149 }
150 }
151 }
152 false
153}
154
155fn augmented_generics(generics: &syn::Generics, krate: &proc_macro2::TokenStream) -> syn::Generics {
174 let mut generics = generics.clone();
175 let type_idents: Vec<syn::Ident> = generics.type_params().map(|tp| tp.ident.clone()).collect();
176 if type_idents.is_empty() {
177 return generics;
178 }
179 let where_clause = generics.make_where_clause();
180 for ident in type_idents {
181 where_clause
182 .predicates
183 .push(syn::parse_quote!(#ident: ::core::convert::Into<#krate::Value>));
184 where_clause.predicates.push(syn::parse_quote!(
185 #ident: #krate::__private::TryFrom<#krate::Value, Error = #krate::ConversionError>
186 ));
187 where_clause
188 .predicates
189 .push(syn::parse_quote!(#ident: #krate::KnownValueType));
190 }
191 generics
192}
193
194fn box_inner(ty: &syn::Type) -> Option<&syn::Type> {
197 let syn::Type::Path(tp) = ty else {
198 return None;
199 };
200 let seg = tp.path.segments.last()?;
201 if seg.ident != "Box" {
202 return None;
203 }
204 let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
205 return None;
206 };
207 args.args.iter().find_map(|a| match a {
208 syn::GenericArgument::Type(t) => Some(t),
209 _ => None,
210 })
211}
212
213fn decode_field(
221 field_type: &syn::Type,
222 value: proc_macro2::TokenStream,
223 krate: &proc_macro2::TokenStream,
224) -> proc_macro2::TokenStream {
225 if let Some(inner) = box_inner(field_type) {
226 quote! {
227 <#inner as #krate::FromValue>::from_value(#value).map(#krate::__private::Box::new)
228 }
229 } else {
230 quote! {
231 <#field_type as #krate::FromValue>::from_value(#value)
232 }
233 }
234}
235
236fn derive_struct(
237 input: &DeriveInput,
238 data: &syn::DataStruct,
239 krate: &proc_macro2::TokenStream,
240) -> proc_macro2::TokenStream {
241 let name = &input.ident;
242 let generics = augmented_generics(&input.generics, krate);
243 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
244 let forward_compatible = has_forward_compatible(&input.attrs);
245
246 match &data.fields {
247 Fields::Named(fields) => {
248 let field_from_value: Vec<_> = fields
250 .named
251 .iter()
252 .map(|f| {
253 let field_name = f.ident.as_ref().unwrap();
254 let field_name_str =
255 get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
256 let field_type = &f.ty;
257 let decode = decode_field(field_type, quote! { field_value }, krate);
258 if forward_compatible {
259 quote! {
262 #field_name: match fields.iter()
263 .find(|(name, _)| name == #field_name_str)
264 .map(|(_, v)| v.clone())
265 {
266 #krate::__private::Some(field_value) =>
267 #decode
268 .map_err(|e| #krate::ConversionError::FieldError(
269 #krate::__private::String::from(#field_name_str),
270 #krate::__private::Box::new(e)
271 ))?,
272 #krate::__private::None =>
273 <#field_type as ::core::default::Default>::default(),
274 }
275 }
276 } else {
277 quote! {
278 #field_name: {
279 let field_value = fields.iter()
280 .find(|(name, _)| name == #field_name_str)
281 .map(|(_, v)| v.clone())
282 .ok_or_else(|| #krate::ConversionError::MissingField(
283 #krate::__private::String::from(#field_name_str)
284 ))?;
285 #decode
286 .map_err(|e| #krate::ConversionError::FieldError(
287 #krate::__private::String::from(#field_name_str),
288 #krate::__private::Box::new(e)
289 ))?
290 }
291 }
292 }
293 })
294 .collect();
295
296 let field_count = fields.named.len();
297
298 let field_accessors: Vec<_> = fields
300 .named
301 .iter()
302 .map(|f| {
303 let field_name = f.ident.as_ref().unwrap();
304 let field_name_str =
305 get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
306 quote! {
307 (
308 #krate::__private::String::from(#field_name_str),
309 ::core::convert::Into::<#krate::Value>::into(value.#field_name)
310 )
311 }
312 })
313 .collect();
314
315 let type_name_str = name.to_string();
316
317 let count_check = if forward_compatible {
321 quote! {}
322 } else {
323 quote! {
324 if fields.len() != #field_count {
325 return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
326 expected: #field_count,
327 got: fields.len(),
328 });
329 }
330 }
331 };
332
333 quote! {
334 impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
335 fn from(value: #name #ty_generics) -> #krate::Value {
336 #krate::Value::Record {
337 type_name: #krate::__private::String::from(#type_name_str),
338 fields: #krate::__private::vec![
339 #(#field_accessors),*
340 ],
341 }
342 }
343 }
344
345 impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
346 type Error = #krate::ConversionError;
347
348 fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, #krate::ConversionError> {
349 match value {
350 #krate::Value::Record { fields, .. } => {
351 #count_check
352 #krate::__private::Ok(Self {
353 #(#field_from_value),*
354 })
355 }
356 other => #krate::__private::Err(#krate::ConversionError::ExpectedRecord(
357 #krate::__private::format!("{:?}", other)
358 )),
359 }
360 }
361 }
362
363 impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
364 fn known_value_type() -> #krate::ValueType {
365 #krate::ValueType::Record(
366 #krate::__private::String::from(#type_name_str)
367 )
368 }
369 }
370 }
371 }
372 Fields::Unnamed(fields) => {
373 let field_indices: Vec<_> = (0..fields.unnamed.len()).map(syn::Index::from).collect();
375
376 let field_from_value: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, f)| {
377 let field_type = &f.ty;
378 if forward_compatible {
379 let decode = decode_field(field_type, quote! { field_value }, krate);
383 quote! {
384 match fields.get(#i).cloned() {
385 #krate::__private::Some(field_value) =>
386 #decode
387 .map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?,
388 #krate::__private::None =>
389 <#field_type as ::core::default::Default>::default(),
390 }
391 }
392 } else {
393 let decode = decode_field(
394 field_type,
395 quote! { fields.get(#i).cloned().ok_or_else(|| #krate::ConversionError::MissingIndex(#i))? },
396 krate,
397 );
398 quote! {
399 #decode.map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?
400 }
401 }
402 }).collect();
403
404 let field_count = fields.unnamed.len();
405 let field_types: Vec<_> = fields.unnamed.iter().map(|f| &f.ty).collect();
406
407 let count_check = if forward_compatible {
408 quote! {}
409 } else {
410 quote! {
411 if fields.len() != #field_count {
412 return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
413 expected: #field_count,
414 got: fields.len(),
415 });
416 }
417 }
418 };
419
420 quote! {
421 impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
422 fn from(value: #name #ty_generics) -> #krate::Value {
423 #krate::Value::Tuple(#krate::__private::vec![
424 #(::core::convert::Into::<#krate::Value>::into(value.#field_indices)),*
425 ])
426 }
427 }
428
429 impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
430 type Error = #krate::ConversionError;
431
432 fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, #krate::ConversionError> {
433 match value {
434 #krate::Value::Tuple(fields) => {
435 #count_check
436 #krate::__private::Ok(Self(
437 #(#field_from_value),*
438 ))
439 }
440 other => #krate::__private::Err(#krate::ConversionError::ExpectedTuple(
441 #krate::__private::format!("{:?}", other)
442 )),
443 }
444 }
445 }
446
447 impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
448 fn known_value_type() -> #krate::ValueType {
449 #krate::ValueType::Tuple(#krate::__private::vec![
450 #(<#field_types as #krate::KnownValueType>::known_value_type()),*
451 ])
452 }
453 }
454 }
455 }
456 Fields::Unit => {
457 quote! {
459 impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
460 fn from(_: #name #ty_generics) -> #krate::Value {
461 #krate::Value::Tuple(#krate::__private::vec![])
462 }
463 }
464
465 impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
466 type Error = #krate::ConversionError;
467
468 fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, #krate::ConversionError> {
469 match value {
470 #krate::Value::Tuple(fields) if fields.is_empty() => {
471 #krate::__private::Ok(Self)
472 }
473 #krate::Value::Tuple(fields) => {
474 #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
475 expected: 0,
476 got: fields.len(),
477 })
478 }
479 other => #krate::__private::Err(#krate::ConversionError::ExpectedTuple(
480 #krate::__private::format!("{:?}", other)
481 )),
482 }
483 }
484 }
485
486 impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
487 fn known_value_type() -> #krate::ValueType {
488 #krate::ValueType::Tuple(#krate::__private::vec![])
489 }
490 }
491 }
492 }
493 }
494}
495
496fn derive_enum(
497 input: &DeriveInput,
498 data: &syn::DataEnum,
499 krate: &proc_macro2::TokenStream,
500) -> proc_macro2::TokenStream {
501 let name = &input.ident;
502 let type_name_str = name.to_string();
503 let generics = augmented_generics(&input.generics, krate);
504 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
505
506 let to_value_arms: Vec<_> = data
508 .variants
509 .iter()
510 .enumerate()
511 .map(|(default_tag, variant)| {
512 let variant_name = &variant.ident;
513 let case_name_str = variant_name.to_string();
514 let tag = get_tag(&variant.attrs).unwrap_or(default_tag);
515
516 match &variant.fields {
517 Fields::Named(fields) => {
518 let field_names: Vec<_> = fields
519 .named
520 .iter()
521 .map(|f| f.ident.as_ref().unwrap())
522 .collect();
523 let field_to_value: Vec<_> = fields
525 .named
526 .iter()
527 .map(|f| {
528 let field_name = f.ident.as_ref().unwrap();
529 let field_name_str =
530 get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
531 quote! {
532 (
533 #krate::__private::String::from(#field_name_str),
534 ::core::convert::Into::<#krate::Value>::into(#field_name)
535 )
536 }
537 })
538 .collect();
539
540 quote! {
541 #name::#variant_name { #(#field_names),* } => {
542 #krate::Value::Variant {
543 type_name: #krate::__private::String::from(#type_name_str),
544 case_name: #krate::__private::String::from(#case_name_str),
545 tag: #tag,
546 payload: #krate::__private::vec![
547 #krate::Value::Record {
548 type_name: #krate::__private::String::from(#case_name_str),
549 fields: #krate::__private::vec![#(#field_to_value),*],
550 }
551 ],
552 }
553 }
554 }
555 }
556 Fields::Unnamed(fields) => {
557 let field_names: Vec<_> = (0..fields.unnamed.len())
558 .map(|i| format_ident!("f{}", i))
559 .collect();
560
561 quote! {
563 #name::#variant_name(#(#field_names),*) => {
564 #krate::Value::Variant {
565 type_name: #krate::__private::String::from(#type_name_str),
566 case_name: #krate::__private::String::from(#case_name_str),
567 tag: #tag,
568 payload: #krate::__private::vec![
569 #(::core::convert::Into::<#krate::Value>::into(#field_names)),*
570 ],
571 }
572 }
573 }
574 }
575 Fields::Unit => {
576 quote! {
577 #name::#variant_name => {
578 #krate::Value::Variant {
579 type_name: #krate::__private::String::from(#type_name_str),
580 case_name: #krate::__private::String::from(#case_name_str),
581 tag: #tag,
582 payload: #krate::__private::vec![],
583 }
584 }
585 }
586 }
587 }
588 })
589 .collect();
590
591 let from_value_arms: Vec<_> = data.variants.iter().enumerate().map(|(default_tag, variant)| {
593 let variant_name = &variant.ident;
594 let tag = get_tag(&variant.attrs).unwrap_or(default_tag);
595
596 match &variant.fields {
597 Fields::Named(fields) => {
598 let field_from_value: Vec<_> = fields.named.iter().map(|f| {
599 let field_name = f.ident.as_ref().unwrap();
600 let field_name_str = get_rename(&f.attrs).unwrap_or_else(|| field_name.to_string());
601 let field_type = &f.ty;
602 let decode = decode_field(field_type, quote! { field_value }, krate);
603 quote! {
604 #field_name: {
605 let field_value = record_fields.iter()
606 .find(|(name, _)| name == #field_name_str)
607 .map(|(_, v)| v.clone())
608 .ok_or_else(|| #krate::ConversionError::MissingField(
609 #krate::__private::String::from(#field_name_str)
610 ))?;
611 #decode
612 .map_err(|e| #krate::ConversionError::FieldError(
613 #krate::__private::String::from(#field_name_str),
614 #krate::__private::Box::new(e)
615 ))?
616 }
617 }
618 }).collect();
619
620 quote! {
621 #tag => {
622 if payload.len() != 1 {
624 return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
625 expected: 1,
626 got: payload.len(),
627 });
628 }
629 match &payload[0] {
630 #krate::Value::Record { fields: record_fields, .. } => {
631 #krate::__private::Ok(#name::#variant_name {
632 #(#field_from_value),*
633 })
634 }
635 other => #krate::__private::Err(#krate::ConversionError::ExpectedRecord(
636 #krate::__private::format!("{:?}", other)
637 )),
638 }
639 }
640 }
641 }
642 Fields::Unnamed(fields) => {
643 let field_count = fields.unnamed.len();
644 let field_conversions: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, f)| {
645 let field_type = &f.ty;
646 let decode = decode_field(
647 field_type,
648 quote! { payload.get(#i).cloned().ok_or_else(|| #krate::ConversionError::MissingIndex(#i))? },
649 krate,
650 );
651 quote! {
652 #decode.map_err(|e| #krate::ConversionError::IndexError(#i, #krate::__private::Box::new(e)))?
653 }
654 }).collect();
655
656 quote! {
657 #tag => {
658 if payload.len() != #field_count {
659 return #krate::__private::Err(#krate::ConversionError::WrongFieldCount {
660 expected: #field_count,
661 got: payload.len(),
662 });
663 }
664 #krate::__private::Ok(#name::#variant_name(
665 #(#field_conversions),*
666 ))
667 }
668 }
669 }
670 Fields::Unit => {
671 quote! {
672 #tag => {
673 if !payload.is_empty() {
674 return #krate::__private::Err(#krate::ConversionError::UnexpectedPayload);
675 }
676 #krate::__private::Ok(#name::#variant_name)
677 }
678 }
679 }
680 }
681 }).collect();
682
683 let variant_count = data.variants.len();
684
685 quote! {
686 impl #impl_generics #krate::__private::From<#name #ty_generics> for #krate::Value #where_clause {
687 fn from(value: #name #ty_generics) -> #krate::Value {
688 match value {
689 #(#to_value_arms),*
690 }
691 }
692 }
693
694 impl #impl_generics #krate::__private::TryFrom<#krate::Value> for #name #ty_generics #where_clause {
695 type Error = #krate::ConversionError;
696
697 fn try_from(value: #krate::Value) -> #krate::__private::Result<Self, #krate::ConversionError> {
698 match value {
699 #krate::Value::Variant { tag, payload, .. } => {
700 match tag {
701 #(#from_value_arms),*
702 other => #krate::__private::Err(#krate::ConversionError::UnknownTag {
703 tag: other,
704 max: #variant_count,
705 }),
706 }
707 }
708 other => #krate::__private::Err(#krate::ConversionError::ExpectedVariant(
709 #krate::__private::format!("{:?}", other)
710 )),
711 }
712 }
713 }
714
715 impl #impl_generics #krate::KnownValueType for #name #ty_generics #where_clause {
716 fn known_value_type() -> #krate::ValueType {
717 #krate::ValueType::Variant(#krate::__private::String::from(#type_name_str))
718 }
719 }
720 }
721}
722
723fn get_rename(attrs: &[Attribute]) -> Option<String> {
725 for attr in attrs {
726 if attr.path().is_ident("graph") {
727 if let Meta::List(list) = &attr.meta {
728 let tokens = list.tokens.to_string();
729 if let Some(rest) = tokens.strip_prefix("rename") {
731 let rest = rest.trim();
732 if let Some(rest) = rest.strip_prefix('=') {
733 let rest = rest.trim();
734 if rest.starts_with('"') && rest.ends_with('"') {
735 return Some(rest[1..rest.len() - 1].to_string());
736 }
737 }
738 }
739 }
740 }
741 }
742 None
743}
744
745fn get_tag(attrs: &[Attribute]) -> Option<usize> {
747 for attr in attrs {
748 if attr.path().is_ident("graph") {
749 if let Meta::List(list) = &attr.meta {
750 let tokens = list.tokens.to_string();
751 if let Some(rest) = tokens.strip_prefix("tag") {
753 let rest = rest.trim();
754 if let Some(rest) = rest.strip_prefix('=') {
755 let rest = rest.trim();
756 return rest.parse().ok();
757 }
758 }
759 }
760 }
761 }
762 None
763}
764
765#[cfg(test)]
766mod tests {
767 use super::{get_crate_path, has_forward_compatible};
768
769 #[test]
773 fn combined_crate_and_forward_compatible() {
774 let attr: syn::Attribute =
775 syn::parse_quote!(#[graph(crate = "foo::bar", forward_compatible)]);
776 let attrs = [attr];
777 assert_eq!(
778 get_crate_path(&attrs).to_string(),
779 quote::quote!(foo::bar).to_string(),
780 "combined form must parse the crate path, not default to packr_abi"
781 );
782 assert!(has_forward_compatible(&attrs));
783 }
784
785 #[test]
786 fn crate_only_parses_and_no_flag() {
787 let attrs = [syn::parse_quote!(#[graph(crate = "foo::bar")])];
788 assert_eq!(
789 get_crate_path(&attrs).to_string(),
790 quote::quote!(foo::bar).to_string()
791 );
792 assert!(!has_forward_compatible(&attrs));
793 }
794
795 #[test]
796 fn flag_only_defaults_crate() {
797 let attrs = [syn::parse_quote!(#[graph(forward_compatible)])];
798 assert_eq!(
799 get_crate_path(&attrs).to_string(),
800 quote::quote!(packr_abi).to_string()
801 );
802 assert!(has_forward_compatible(&attrs));
803 }
804}