rill_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::parse::Error;
5use syn::spanned::Spanned;
6use syn::{parse_macro_input, DeriveInput, Field, Ident};
7
8#[proc_macro_derive(TracerOpts)]
9pub fn tracer_opts(input: TokenStream) -> TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11    match impl_tracer_opts(&input) {
12        Ok(output) => output,
13        Err(error) => error.to_compile_error().into(),
14    }
15}
16
17fn impl_tracer_opts(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
18    let data = match &ast.data {
19        syn::Data::Struct(data) => match &data.fields {
20            syn::Fields::Named(fields) => {
21                let ident = &ast.ident;
22                let mut methods = Vec::new();
23                for field in fields.named.iter() {
24                    let ident = field.ident.as_ref().ok_or_else(|| {
25                        Error::new(
26                            ast.span(),
27                            "TracerOpts is not supported fields of tuple structs",
28                        )
29                    })?;
30                    match extract_opt_type(field) {
31                        Some((cont, ty)) if cont == "Option" => {
32                            methods.push(quote! {
33                                pub fn #ident(mut self, value: impl Into<#ty>) -> Self {
34                                    self.#ident = Some(value.into());
35                                    self
36                                }
37                            });
38                            let set_ident =
39                                Ident::new(&format!("set_{}", ident), Span::call_site());
40                            methods.push(quote! {
41                                pub fn #set_ident(&mut self, value: impl Into<#ty>) -> &mut Self {
42                                    self.#ident = Some(value.into());
43                                    self
44                                }
45                            });
46                        }
47                        Some((cont, ty)) if cont == "Vec" => {
48                            methods.push(quote! {
49                                pub fn #ident<T>(mut self, values: impl IntoIterator<Item = T>) -> Self
50                                where
51                                    #ty: From<T>,
52                                {
53                                    self.#ident.extend(values.into_iter().map(<#ty>::from));
54                                    self
55                                }
56                            });
57                            let single = ident.to_string();
58                            let mut chars = single.chars();
59                            chars.next_back();
60                            let single_ident = Ident::new(chars.as_str(), Span::call_site());
61                            methods.push(quote! {
62                                pub fn #single_ident(mut self, value: impl Into<#ty>) -> Self {
63                                    self.#ident.push(value.into());
64                                    self
65                                }
66                            });
67                        }
68                        _ => {
69                            // Not optional fields skipped, because they can be
70                            // collections and they have to be handeled separately.
71                            // For example, by providing methods link `add_item`.
72                        }
73                    }
74                }
75                quote! {
76                    impl #ident {
77                        #( #methods )*
78                    }
79                }
80            }
81            syn::Fields::Unnamed(_) => {
82                return Err(Error::new(
83                    ast.span(),
84                    "TracerOpts is not supported for tuple structs",
85                ))
86            }
87            syn::Fields::Unit => {
88                return Err(Error::new(
89                    ast.span(),
90                    "TracerOpts is not supported for unit structs",
91                ))
92            }
93        },
94        syn::Data::Enum(_) => {
95            return Err(Error::new(
96                ast.span(),
97                "TracerOpts is not supported for enums",
98            ))
99        }
100        syn::Data::Union(_) => {
101            return Err(Error::new(
102                ast.span(),
103                "TracerOpts is not supported for unions",
104            ))
105        }
106    };
107    Ok(data.into())
108}
109
110fn extract_opt_type(field: &Field) -> Option<(&syn::Ident, &syn::Type)> {
111    let path = if let syn::Type::Path(type_path) = &field.ty {
112        if type_path.qself.is_some() {
113            return None;
114        } else {
115            &type_path.path
116        }
117    } else {
118        return None;
119    };
120    let segment = path.segments.last()?;
121    /*
122    if segment.ident != "Option" {
123        return None;
124    }
125    */
126    let generic_params =
127        if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments {
128            generic_params
129        } else {
130            return None;
131        };
132    if let syn::GenericArgument::Type(ty) = generic_params.args.first()? {
133        Some((&segment.ident, ty))
134    } else {
135        None
136    }
137}