macron_impl_from/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3//! See the documentation here [macron documentation](https://docs.rs/macron)
4
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8
9/// The implementation of trait From
10#[proc_macro_derive(From, attributes(from))]
11pub fn impl_from(input: TokenStream) -> TokenStream {
12    let syn::DeriveInput { ident, data, attrs, .. } = syn::parse_macro_input!(input as syn::DeriveInput);
13    
14    let global_impls = read_attr_values(&attrs, None)
15        .into_iter()
16        .map(|AttrValue { ty, expr }| {
17            quote! {
18                impl ::std::convert::From<#ty> for #ident {
19                    fn from(value: #ty) -> Self {
20                        #expr
21                    }
22                }
23            }
24        });
25    
26    match data {
27        // impl Struct:
28        syn::Data::Struct(_st) => {
29            quote! {
30                #(
31                    #global_impls
32                )*
33            }.into()
34        },
35
36        // impl Enum:
37        syn::Data::Enum(en) => {
38            let var_impls = en.variants
39                .iter()
40                .map(|syn::Variant { ident: var_ident, attrs, fields, .. }| {
41                    let var_impls = read_attr_values(&attrs, Some(&fields))
42                        .into_iter()
43                        .map(|AttrValue { ty, expr }| {
44                            let output = match &fields {
45                                syn::Fields::Named(_) => quote! { Self::#var_ident { #expr } },
46                                syn::Fields::Unnamed(_) => quote! { Self::#var_ident(#expr) },
47                                syn::Fields::Unit => quote! { Self::#var_ident }
48                            };
49                            
50                            quote! {
51                                impl ::std::convert::From<#ty> for #ident {
52                                    fn from(value: #ty) -> Self {
53                                        #output
54                                    }
55                                }
56                            }
57                        });
58
59                    quote! { #(#var_impls)* }
60                });
61            
62            quote! {
63                #(
64                    #global_impls
65                )*
66
67                #(
68                    #var_impls
69                )*
70            }.into()
71        },
72
73        _ => panic!("Expected a 'struct' or 'enum'")
74    }
75}
76
77// Reads an attribute values
78fn read_attr_values(attrs: &[syn::Attribute], fields: Option<&syn::Fields>) -> Vec<AttrValue> {
79    attrs
80        .iter()
81        .filter(|attr| attr.path().is_ident("from"))
82        .map(|attr| {
83            match &attr.meta {
84                syn::Meta::List(list) => {
85                    list
86                        .parse_args()
87                        .expect("Expected the attribute format like this '#[from(Type, \"a code..\")]'")
88                },
89
90                syn::Meta::Path(_) if fields.is_some() => {
91                    let fields = fields.unwrap();
92                    if fields.len() != 1 { panic!("Expected the one variant argument for the short attribute '#[from]'") }
93
94                    let field = fields.iter().next().unwrap();
95                    
96                    AttrValue {
97                        ty: field.ty.clone(),
98                        expr: syn::parse_str( &if let Some(ident) = &field.ident { format!("{ident}: value") }else{ format!("value")} ).unwrap()
99                    }
100                },
101                
102                _ => panic!("Expected the attribute format like this '#[from(Type, \"a code..\")]'")
103            }
104        })
105        .collect::<Vec<_>>()
106}
107
108struct AttrValue {
109    pub ty: syn::Type,
110    pub expr: TokenStream2
111}
112
113impl syn::parse::Parse for AttrValue {
114    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
115        let ty: syn::Type = input.parse()?;
116        
117        input.parse::<syn::token::Comma>()?;
118        
119        let expr_s: syn::LitStr = input.parse()?;
120        let expr: TokenStream2 = syn::parse_str(&expr_s.value())?;
121        
122        Ok(AttrValue {
123            ty,
124            expr,
125        })
126    }
127}