crabstructor/
lib.rs

1
2extern crate proc_macro;
3use proc_macro::{TokenStream};
4use quote::{quote, ToTokens};
5use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprPath, Lit, Meta, Type, TypePath};
6
7#[proc_macro_derive(Constructor, attributes(init, new, from))]
8pub fn constructor_derive(input: TokenStream) -> TokenStream {
9    let input = parse_macro_input!(input as DeriveInput);
10    let struct_name = input.ident;
11    let generics = input.generics;
12
13    let fields = match input.data {
14        Data::Struct(data_struct) => data_struct.fields,
15        _ => panic!("Error by get struct fields")
16    };
17
18    let mut args = vec![];
19    let mut inits = vec![];
20    let mut assignments = vec![];
21
22    for field in fields.iter() {
23        let field_name = field.ident.clone().unwrap();
24        let field_type = field.ty.clone();
25        
26        let mut default_value: Option<Value> = None;
27
28        
29        
30        for attr in &field.attrs {
31            if attr.path().is_ident("init") {
32                if let Meta::List(meta_list) = &attr.meta {
33                    let inner_token_stream = TokenStream::from(meta_list.tokens.clone());
34                    if let Some(lit) = get_lit_from_token_stream(inner_token_stream) {
35                        default_value = Some(Value::Lit {lit})
36                    }
37                    if let Ok(ident) =  syn::parse::<syn::Ident>(TokenStream::from(meta_list.tokens.clone())) {
38                        default_value = match ident.to_string().as_str() {
39                            "default" => Some(Value::TokenStream {token_stream: quote! { #field_type::default() }.into() } ),
40                            "as_str" => {
41                                args.push(quote! {#field_name: &str});
42                                Some(Value::TokenStream {token_stream: quote! { #field_type::from(#field_name) }.into() })
43                            },
44                            _ => panic!("Unknown ident")
45                        }
46                    }
47                }
48                
49            }
50            if attr.path().is_ident("new") { get_initializers(&attr.meta, &mut args, &field_type, &mut default_value, Func::New) }
51            if attr.path().is_ident("from") { get_initializers(&attr.meta, &mut args, &field_type, &mut default_value, Func::From) }
52        }
53
54        if let Some(value) = default_value  {
55            match value {
56                Value::Lit { lit }=> {
57                    if let Lit::Str(..) = &lit {
58                        inits.push(quote! {#field_name: #lit.into()});
59                    }
60                    else {
61                        inits.push(quote! {#field_name: #lit});
62                    }
63                }
64                Value::TokenStream { token_stream } => {
65                    inits.push(quote! {#field_name: #token_stream});
66                }
67            }
68
69
70        }
71        else {
72            args.push(quote! {#field_name: #field_type});
73            assignments.push(quote! {#field_name})
74        }
75    }
76
77
78    let constructor = quote! {
79        impl #generics #struct_name #generics {
80            pub fn new(#(#args,)*) -> Self {
81                Self {
82                    #(#assignments,)*
83                    #(#inits,)*
84                }
85            }
86        }
87    };
88    constructor.into()
89}
90
91fn get_lit_from_token_stream(token_stream: TokenStream) -> Option<Lit> {
92    if let Ok(lit) = syn::parse::<Lit>(token_stream) {
93        return Some(lit)
94    }
95    None
96}
97
98fn get_initializers(meta: &Meta, args: &mut Vec<proc_macro2::TokenStream>, field_type: &Type, default_value: &mut Option<Value>, func: Func) {
99    if let Meta::List(meta_list) = meta {
100        let mut new_args: Vec<proc_macro2::TokenStream> = vec![];
101        let inner_token_stream = TokenStream::from(meta_list.tokens.clone());
102        if let Some(lit) = get_lit_from_token_stream(inner_token_stream) {
103            if let Lit::Str(..) = &lit {
104                new_args.push(quote! { #lit.into() }.into());
105            }
106            else {
107                new_args.push(quote! { #lit }.into());
108            }
109
110        }
111        if let Ok(field) = syn::parse::<syn::FieldValue>(TokenStream::from(meta_list.tokens.clone())) {
112            let member = field.member.to_token_stream();
113
114            if let Expr::Path(ExprPath {path, ..}) = field.expr {
115                let path_seg = &path.segments[0];
116                let ident = &path_seg.ident;
117                new_args.push(quote! { #member }.into());
118                args.push(quote! {  #member: #ident }.into());
119            }
120
121        }
122        if let Type::Path(TypePath{path, ..}) = &field_type {
123            let path_seg = &path.segments[0];
124            let ident = &path_seg.ident;
125            match func {
126                Func::New =>  *default_value = Some(Value::TokenStream {token_stream: quote! { #ident::new(#(#new_args),*) }.into() }),
127                Func::From => *default_value = Some(Value::TokenStream {token_stream: quote! { #ident::from(#(#new_args),*) }.into() }),
128
129            }
130        }
131    }
132}
133
134enum Value {
135    Lit{lit: Lit},
136    TokenStream{token_stream: proc_macro2::TokenStream}
137}
138
139enum Func {
140    New,
141    From
142}