engineer_derive/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3
4use darling::{util::Flag, FromDeriveInput, FromField, FromMeta, ToTokens};
5use proc_macro::TokenStream;
6
7use quote::{format_ident, quote};
8use syn::{parse_macro_input, DeriveInput, GenericArgument, Path, PathArguments, Type};
9
10#[derive(Debug, Clone, FromMeta)]
11struct Retype {
12    to: String,
13    #[darling(rename = "re")]
14    restore: String,
15}
16
17impl Retype {
18    fn new(to: &str, restore: &str) -> Self {
19        Self {
20            to: to.to_string(),
21            restore: restore.to_string(),
22        }
23    }
24}
25
26#[derive(Debug, Clone, FromMeta)]
27struct GlobRetype {
28    from: String,
29    to: String,
30    #[darling(rename = "re")]
31    restore: String,
32}
33
34#[derive(FromField, Clone, Debug)]
35#[darling(attributes(engineer), forward_attrs(allow, doc, cfg))]
36struct EngineerField {
37    ident: Option<syn::Ident>,
38    ty: syn::Type,
39
40    default_value: Option<String>,
41    retype: Option<Retype>,
42
43    default: Flag,
44    /// Shorthand for `retype(to = "impl Into<String>", re = ".into()")`,
45    str_retype: Flag,
46}
47
48impl EngineerField {
49    fn apply_shorthands(&mut self) {
50        if self.str_retype.is_present() {
51            self.retype = Some(Retype::new("impl Into<String>", ".into()"))
52        }
53
54        if self.default.is_present() {
55            // if type_is_option(&self.ty) {
56            //     self.default_value = Some("Some(Default::default())".to_string())
57            // } else {
58            self.default_value = Some("Default::default()".to_string())
59            // }
60        }
61
62        if self.default_value.is_some() {
63            self.default = Flag::present()
64        }
65    }
66
67    fn is_option(&self) -> bool {
68        type_is_option(&self.ty) || self.default.is_present()
69    }
70
71    fn is_retyped(&self) -> bool {
72        self.retype.is_some()
73    }
74
75    fn retyped(&self) -> proc_macro2::TokenStream {
76        match &self.retype {
77            Some(retype) => retype.to.parse().unwrap(),
78            None => {
79                let ty = if type_is_option(&self.ty) {
80                    extract_type_from_option(&self.ty).unwrap()
81                } else {
82                    &self.ty
83                };
84
85                quote!(#ty)
86            }
87        }
88    }
89
90    fn restorer(&self) -> proc_macro2::TokenStream {
91        match &self.retype {
92            Some(retype) => retype.restore.parse().unwrap(),
93            None => Default::default(),
94        }
95    }
96
97    fn as_struct_field(&self) -> proc_macro2::TokenStream {
98        let name = &self.ident;
99        let ty = &self.ty;
100
101        quote!(#name: #ty,)
102    }
103
104    fn as_struct_setter(&self) -> proc_macro2::TokenStream {
105        let name = &self.ident;
106        let mut name_ts = quote!(#name);
107
108        if self.is_retyped() {
109            let restore = self.restorer();
110            quote!( : #name #restore).to_tokens(&mut name_ts)
111        }
112
113        name_ts
114    }
115
116    fn as_func_argument(&self) -> proc_macro2::TokenStream {
117        let name = &self.ident;
118        let ty = self.retyped();
119
120        quote!(#name: #ty,)
121    }
122}
123
124#[derive(FromDeriveInput, Clone, Debug)]
125#[darling(
126    attributes(engineer),
127    supports(struct_named),
128    forward_attrs(allow, doc, cfg)
129)]
130struct EngineerOptions {
131    ident: syn::Ident,
132    vis: syn::Visibility,
133    data: darling::ast::Data<darling::util::Ignored, EngineerField>,
134
135    #[darling(rename = "engineer_name")]
136    engineer_name_arg: Option<String>,
137    #[darling(rename = "builder_func")]
138    builder_func_arg: Option<String>,
139
140    #[darling(multiple, rename = "retype")]
141    retypes: Vec<GlobRetype>,
142    str_retype: Flag,
143    new: Flag,
144
145    #[darling(skip)]
146    fields_ref: Option<Vec<EngineerField>>,
147    #[darling(skip)]
148    engineer_name: Option<syn::Ident>,
149    #[darling(skip)]
150    builder_func: Option<syn::Ident>,
151}
152
153impl EngineerOptions {
154    fn from_derive_input_delegate(input: &DeriveInput) -> Result<EngineerOptions, darling::Error> {
155        let mut s = Self::from_derive_input(input)?
156            .apply_self_shorthands()
157            .apply_global_retypes()
158            .apply_fields_shorthands()
159            .set_custom_fields();
160
161        s.set_fields_ref();
162
163        Ok(s)
164    }
165
166    fn apply_self_shorthands(mut self) -> Self {
167        if self.str_retype.is_present() {
168            self.retypes.push(GlobRetype {
169                from: "String".to_string(),
170                to: "impl Into<String>".to_string(),
171                restore: ".into()".to_string(),
172            })
173        }
174
175        if self.new.is_present() {
176            self.builder_func_arg = Some("new".to_string());
177        }
178
179        self
180    }
181
182    fn apply_global_retypes(mut self) -> Self {
183        self.data = self.data.map_struct_fields(|mut f| {
184            let ty_str = if type_is_option(&f.ty) {
185                extract_type_from_option(&f.ty).unwrap()
186            } else {
187                &f.ty
188            }
189            .to_token_stream()
190            .to_string();
191
192            for r in &self.retypes {
193                if ty_str == r.from {
194                    f.retype = Some(Retype {
195                        to: r.to.clone(),
196                        restore: r.restore.clone(),
197                    });
198                }
199            }
200
201            f
202        });
203
204        self
205    }
206
207    fn apply_fields_shorthands(mut self) -> Self {
208        self.data = self.data.map_struct_fields(|mut f| {
209            f.apply_shorthands();
210            f
211        });
212
213        self
214    }
215
216    fn set_custom_fields(mut self) -> Self {
217        self.engineer_name = format_ident!(
218            "{}",
219            self.engineer_name_arg
220                .clone()
221                .unwrap_or(format!("{}Engineer", self.ident))
222        )
223        .into();
224
225        self.builder_func = format_ident!(
226            "{}",
227            self.builder_func_arg
228                .clone()
229                .unwrap_or_else(|| "engineer".to_string())
230        )
231        .into();
232
233        self
234    }
235
236    fn set_fields_ref(&mut self) {
237        self.fields_ref = Some(self.data.clone().take_struct().unwrap().fields);
238    }
239
240    /// The name of Engineer struct.
241    fn engineer_name(&self) -> &Option<proc_macro2::Ident> {
242        &self.engineer_name
243    }
244
245    /// The name of function that builds Engineer struct from main struct.
246    fn builder_name(&self) -> &Option<proc_macro2::Ident> {
247        &self.builder_func
248    }
249
250    fn fields_ref(&self) -> &Vec<EngineerField> {
251        self.fields_ref.as_ref().unwrap()
252    }
253}
254
255trait FieldsHelpers<'e>: Iterator<Item = &'e EngineerField> + Sized {
256    fn filter_normals(self) -> Vec<&'e EngineerField> {
257        self.filter(|f| !f.is_option()).collect()
258    }
259
260    fn filter_options(self) -> Vec<&'e EngineerField> {
261        self.filter(|f| f.is_option()).collect()
262    }
263
264    fn map_names(self) -> Vec<&'e Option<syn::Ident>> {
265        self.map(|f| &f.ident).collect()
266    }
267
268    fn map_types(self) -> Vec<&'e Type> {
269        self.map(|f| &f.ty).collect()
270    }
271}
272
273impl<'e, T> FieldsHelpers<'e> for T where T: Iterator<Item = &'e EngineerField> {}
274
275struct EngineerStructDefinition<'e>(&'e EngineerOptions);
276
277impl<'e> EngineerStructDefinition<'e> {
278    fn name(&self) -> &Option<proc_macro2::Ident> {
279        self.0.engineer_name()
280    }
281
282    fn struct_definition(&self) -> proc_macro2::TokenStream {
283        let struct_name = &self.0.ident;
284        let vis = &self.0.vis;
285        let engineer_name = &self.0.engineer_name();
286
287        let fields = self.0.fields_ref();
288        let struct_fields = fields.iter().map(|f| f.as_struct_field());
289        let names = fields.iter().map(|f| &f.ident);
290
291        quote! {
292            #vis struct #engineer_name {
293                #(
294                    #struct_fields
295                )*
296            }
297
298            impl Builder<#struct_name> for #engineer_name {
299                fn done(self) -> #struct_name {
300                    #struct_name {
301                        #(
302                            #names: self.#names,
303                        )*
304                    }
305                }
306            }
307
308            impl From<#engineer_name> for #struct_name
309            {
310                fn from(value: #engineer_name) -> Self {
311                    value.done()
312                }
313            }
314        }
315    }
316
317    fn new_func(&self) -> proc_macro2::TokenStream {
318        let engineer_name = self.name();
319        let vis = &self.0.vis;
320
321        let fields = self.0.fields_ref();
322        let nrm_fields = fields.iter().filter_normals();
323
324        let opt_names = fields.iter().filter(|f| f.is_option()).map(|f| &f.ident);
325        let opt_values = fields
326            .iter()
327            .filter(|f| f.is_option())
328            .map(|f| match &f.default_value {
329                Some(sec) => {
330                    let t = sec.parse::<proc_macro2::TokenStream>().unwrap();
331
332                    if type_is_option(&f.ty) {
333                        quote!(Some(#t))
334                    } else {
335                        quote!(#t)
336                    }
337                }
338                _ => {
339                    if type_is_option(&f.ty) {
340                        quote!(None)
341                    } else {
342                        quote!(Default::default())
343                    }
344                }
345            });
346
347        let func_args = nrm_fields.iter().map(|f| f.as_func_argument());
348        let struct_setters = nrm_fields.iter().map(|f| f.as_struct_setter());
349
350        quote! {
351            #vis fn new(#(#func_args)*) -> Self {
352                #engineer_name {
353                    #(
354                        #struct_setters,
355                    )*
356
357                    #(
358                        #opt_names: #opt_values,
359                    )*
360                }
361            }
362        }
363    }
364
365    fn opt_setters(&self) -> proc_macro2::TokenStream {
366        let vis = &self.0.vis;
367
368        let fields = self.0.fields_ref();
369        let opt_fields = fields.iter().filter(|f| f.is_option());
370        let opt_names = opt_fields.clone().map(|f| &f.ident);
371
372        let opt_types = opt_fields.clone().map(|f| f.retyped());
373        let opt_restores = opt_fields.map(|f| f.restorer());
374
375        quote! {
376            #(
377                #vis fn #opt_names(mut self, #opt_names: #opt_types) -> Self {
378                    self.#opt_names = (#opt_names #opt_restores).into();
379                    self
380                }
381            )*
382        }
383    }
384
385    fn struct_impl(&self) -> proc_macro2::TokenStream {
386        let engineer_name = &self.name();
387        let new_func = self.new_func();
388        let opt_setters = self.opt_setters();
389
390        quote! {
391            impl #engineer_name {
392                #new_func
393                #opt_setters
394            }
395        }
396    }
397}
398
399impl<'e> quote::ToTokens for EngineerStructDefinition<'e> {
400    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
401        let struct_definition = self.struct_definition();
402        let struct_impl = self.struct_impl();
403
404        quote! {
405            #struct_definition
406            #struct_impl
407        }
408        .to_tokens(tokens)
409    }
410}
411
412struct StructImpl<'e>(&'e EngineerOptions);
413
414impl<'e> StructImpl<'e> {
415    fn builder_func(&self) -> proc_macro2::TokenStream {
416        let engineer_name = self.0.engineer_name();
417        let builder_name = self.0.builder_name();
418        let vis = &self.0.vis;
419
420        let fields = self.0.fields_ref();
421        let nrm_fields = fields.iter().filter(|f| !f.is_option());
422        let nrm_names = nrm_fields.clone().map(|f| &f.ident);
423
424        let func_args = nrm_fields.clone().map(|f| f.as_func_argument());
425
426        quote! {
427            #vis fn #builder_name(#(#func_args)*) -> #engineer_name {
428                <#engineer_name>::new(#(#nrm_names,)*)
429            }
430        }
431    }
432}
433
434impl<'e> quote::ToTokens for StructImpl<'e> {
435    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
436        let name = &self.0.ident;
437        let builder_func = self.builder_func();
438
439        quote! { impl #name { #builder_func } }.to_tokens(tokens)
440    }
441}
442
443struct TraitImpl<'e>(&'e EngineerOptions);
444
445impl<'e> quote::ToTokens for TraitImpl<'e> {
446    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
447        let name = &self.0.ident;
448        let engineer_name = &self.0.engineer_name();
449
450        let fields = self.0.fields_ref();
451        let nrm_fields = fields.iter().filter(|f| !f.is_option());
452
453        let nrm_count = nrm_fields.clone().count();
454        let opt_count = fields.len() - nrm_count;
455        let nrm_fields_types = nrm_fields.map(|f| &f.ty);
456
457        let members = (0..nrm_count).map(|f| {
458            format!("required.{}", f)
459                .parse::<proc_macro2::TokenStream>()
460                .unwrap()
461        });
462
463        quote! {
464            impl Engineer for #name {
465                const NORMAL_FIELDS: usize = #nrm_count;
466                const OPTIONAL_FIELDS: usize = #opt_count;
467
468                type Builder = #engineer_name;
469                type Params = (#(#nrm_fields_types,)*);
470
471                fn builder(required: Self::Params) -> Self::Builder {
472                    #engineer_name::new(#(#members,)*)
473                }
474            }
475        }
476        .to_tokens(tokens);
477    }
478}
479
480#[proc_macro_derive(Engineer, attributes(engineer))]
481pub fn engineer(input: TokenStream) -> TokenStream {
482    // Parse the input tokens into a syntax tree
483    let input = parse_macro_input!(input as DeriveInput);
484    let engineer_opts = EngineerOptions::from_derive_input_delegate(&input).unwrap();
485
486    let engineer_struct_definition = EngineerStructDefinition(&engineer_opts);
487    let struct_impl = StructImpl(&engineer_opts);
488    let trait_impl = TraitImpl(&engineer_opts);
489
490    // Build the output, possibly using quasi-quotation
491    let expanded = quote! {
492        #engineer_struct_definition
493        #struct_impl
494        #trait_impl
495    };
496
497    // Hand the output tokens back to the compiler
498    TokenStream::from(expanded)
499}
500
501fn type_is_option(ty: &Type) -> bool {
502    match ty {
503        Type::Path(path_type) => {
504            path_type.path.leading_colon.is_none()
505                && path_type.path.segments.len() == 1
506                && path_type.path.segments.iter().next().unwrap().ident == "Option"
507        }
508        _ => false,
509    }
510}
511
512fn path_is_option(path: &Path) -> bool {
513    path.leading_colon.is_none()
514        && path.segments.len() == 1
515        && path.segments.iter().next().unwrap().ident == "Option"
516}
517
518fn extract_type_from_option(ty: &Type) -> Option<&Type> {
519    match ty {
520        Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {
521            // Get the first segment of the path (there is only one, in fact: "Option"):
522            let type_params = &typepath.path.segments.iter().next().unwrap().arguments;
523            // It should have only on angle-bracketed param ("<String>"):
524            let generic_arg = match type_params {
525                PathArguments::AngleBracketed(params) => Some(params.args.iter().next().unwrap()),
526                _ => None,
527            }?;
528            // This argument must be a type:
529            match generic_arg {
530                GenericArgument::Type(ty) => Some(ty),
531                _ => None,
532            }
533        }
534        _ => None,
535    }
536}