simple_builder_macro/
lib.rs

1//! A procedural macro for creating a Builder object for any struct.
2//! This crate is meant to be used by the `simple-builder` crate and is not meant for direct consumption.
3
4use proc_macro2::{self, Span, TokenStream};
5use quote::quote;
6use std::vec::Vec;
7use syn::{
8    parse_macro_input, Attribute, DeriveInput, Field, Fields, GenericArgument, Ident, Path,
9    PathArguments, Type,
10};
11
12/// Macro that derives a Builder object for any given struct. E.g. `SomeType` -> `SomeTypeBuilder`.
13///
14/// Simple-Builder takes ownership of inputs and stores them in an `Option<T>` for each field. Fields
15/// that are marked `#[builder(required)]` will be part of the `new()` call so they're guaranteed
16/// to be set on the final object.
17///
18///
19/// # Example: Builder on a Simple Object
20/// ```
21/// # use simple_builder_macro::Builder;
22///
23/// // Debug, PartialEq, Eq are only for assertions
24/// #[derive(Debug, PartialEq, Eq, Builder)]
25/// struct Breakfast {
26///     #[builder(required)]
27///     pub coffee: i64, // coffee is required, and therefore not Option<T>
28///     pub toast: Option<i64>,
29///     pub eggs: Option<i64>,
30///     pub bacon: Option<i64>,
31/// }
32///
33/// pub fn main() {
34///     let desired_breakfast = Breakfast {
35///         coffee: 1,
36///         toast: None,
37///         eggs: Some(3),
38///         bacon: Some(2),
39///     };
40///
41///     // semantically equivalent to `Breakfast::builder(16)`
42///     let mut builder = BreakfastBuilder::new(16);
43///
44///     let breakfast = builder.eggs(3).bacon(2).build();
45///
46///     assert_eq!(desired_breakfast, breakfast);
47/// }
48/// ```
49///
50/// ## Attributes
51/// Builder supports attributes under `#[builder(...)]` on individual fields to carry metadata.
52/// At this time, the available attributes are:
53/// - required -- marks a field as required, meaning it can be `T` instead of `Option<T>` on the struct
54/// and will be an argument to the `StructBuilder::new()` or `Struct::builder()` methods.
55#[proc_macro_derive(Builder, attributes(builder))]
56pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
57    let ast: DeriveInput = parse_macro_input!(input);
58    let (vis, ident, generics) = (&ast.vis, &ast.ident, &ast.generics);
59    let builder_ident = Ident::new(&(ident.to_string() + "Builder"), Span::call_site());
60
61    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
62
63    let fields: &Fields = match ast.data {
64        syn::Data::Struct(ref s) => &s.fields,
65        _ => panic!("Can only derive Builder for structs."),
66    };
67
68    let named_fields: Vec<&Field> = fields
69        .iter()
70        .filter(|field| field.ident.is_some())
71        .collect();
72
73    let required_fields: Vec<&Field> = named_fields
74        .iter()
75        .filter_map(|field| {
76            if has_attr(field, "required") {
77                Some(*field)
78            } else {
79                None
80            }
81        })
82        .collect();
83
84    let optional_fields: Vec<&Field> = named_fields
85        .iter()
86        .filter_map(|field| {
87            if has_attr(field, "required") {
88                None
89            } else {
90                Some(*field)
91            }
92        })
93        .collect();
94
95    let builder_setter_methods: TokenStream = optional_fields
96        .iter()
97        .map(|field| {
98            let field_ident = &field.ident;
99            let field_ty = &field.ty;
100
101            let type_of_option = extract_type_from_option(field_ty);
102
103            quote! {
104                pub fn #field_ident(&mut self, #field_ident: #type_of_option) -> &mut Self {
105                    self.#field_ident = ::std::option::Option::Some(#field_ident);
106                    self
107                }
108            }
109        })
110        .collect();
111
112    let required_new_fields: TokenStream = required_fields
113        .iter()
114        .map(|field| {
115            let ident = &field.ident;
116            quote! {
117                #ident: ::std::option::Option::Some(#ident),
118            }
119        })
120        .collect();
121
122    let empty_new_fields: TokenStream = optional_fields
123        .iter()
124        .map(|field| {
125            let ident = &field.ident;
126            quote! {
127                #ident: None,
128            }
129        })
130        .collect();
131
132    let builder_required_fields: TokenStream = required_fields
133        .iter()
134        .map(|field| {
135            let ident = &field.ident;
136            let ty = &field.ty;
137            quote! {
138                #ident: ::std::option::Option<#ty>,
139            }
140        })
141        .collect();
142
143    let builder_optional_fields: TokenStream = optional_fields
144        .iter()
145        .map(|field| {
146            let ident = &field.ident;
147            let ty = &field.ty;
148            quote! {
149                #ident: #ty,
150            }
151        })
152        .collect();
153
154    let builder_struct_fields: TokenStream = builder_required_fields
155        .into_iter()
156        .chain(builder_optional_fields)
157        .collect();
158
159    let new_method_params: TokenStream = required_fields
160        .iter()
161        .map(|field| {
162            let (arg, ty) = (&field.ident, &field.ty);
163            quote! {
164                #arg: #ty,
165            }
166        })
167        .collect();
168
169    let build_fn_struct_fields: TokenStream = named_fields
170        .iter()
171        .map(|field| {
172            let is_required = has_attr(field, "required");
173
174            let ident = &field.ident;
175
176            if is_required {
177                // .expect() should be possible only when build is called twice, since these are required private fields set by `new`
178                quote! {
179                    #ident: self.#ident.take().expect("Option must be Some(T) for required fields. Builder may have already been consumed by calling `build`"),
180                }
181            } else {
182                quote! {
183                    #ident: self.#ident.take(),
184                }
185            }
186        })
187        .collect();
188
189    let struct_impl = quote! {
190        impl #impl_generics #ident #ty_generics #where_clause {
191            pub fn builder(#new_method_params) -> #builder_ident #ty_generics {
192                #builder_ident {
193                    #required_new_fields
194                    #empty_new_fields
195                }
196            }
197        }
198    };
199
200    let builder_struct = quote! {
201        #vis struct #builder_ident #ty_generics #where_clause {
202            #builder_struct_fields
203        }
204
205        impl #impl_generics #builder_ident #ty_generics #where_clause {
206
207            pub fn new(#new_method_params) -> #builder_ident #ty_generics {
208                #builder_ident {
209                    #required_new_fields
210                    #empty_new_fields
211                }
212            }
213
214            pub fn build(&mut self) -> #ident #ty_generics {
215                #ident {
216                    #build_fn_struct_fields
217                }
218            }
219
220            #builder_setter_methods
221        }
222    };
223
224    let output = quote! {
225        #struct_impl
226        #builder_struct
227    };
228
229    output.into()
230}
231
232fn has_attr(field: &Field, attr: &'static str) -> bool {
233    field.attrs.iter().any(|a| has_nested_attr(a, attr))
234}
235
236fn has_nested_attr(attr: &Attribute, name: &'static str) -> bool {
237    let mut has_attr: bool = false;
238
239    if attr.path().is_ident("builder") {
240        attr.parse_nested_meta(|m| {
241            if m.path.is_ident(name) {
242                has_attr = true;
243            }
244
245            Ok(())
246        })
247        .expect("Parsing nested meta within #[builder(...)] failed.");
248    }
249
250    has_attr
251}
252
253fn extract_type_from_option(ty: &Type) -> &Type {
254    fn path_is_option(path: &Path) -> bool {
255        path.leading_colon.is_none()
256            && path.segments.len() == 1
257            && path.segments.iter().next().unwrap().ident == "Option"
258    }
259
260    match ty {
261        Type::Path(type_path) if type_path.qself.is_none() && path_is_option(&type_path.path) => {
262            let type_params: &PathArguments = &(type_path.path.segments.first().unwrap()).arguments;
263
264            let generic_arg = match type_params {
265                PathArguments::AngleBracketed(params) => params.args.first().unwrap(),
266                _ => panic!("Could not find generic parameter in Option<...>"),
267            };
268
269            match generic_arg {
270                GenericArgument::Type(ty) => ty,
271                _ => panic!(
272                    "Found something other than a type as a generic parameter to Option<...>"
273                ),
274            }
275        }
276        _ => panic!(
277            "Struct fields must be of type Option<...>, or have #[builder(required)] attribute."
278        ),
279    }
280}