simple_builder_macro/
lib.rs1use 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#[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 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}