use proc_macro2::{self, Span, TokenStream};
use quote::quote;
use std::vec::Vec;
use syn::{
parse_macro_input, Attribute, DeriveInput, Field, Fields, GenericArgument, Ident, Path,
PathArguments, Type,
};
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast: DeriveInput = parse_macro_input!(input);
let (vis, ident, generics) = (&ast.vis, &ast.ident, &ast.generics);
let builder_ident = Ident::new(&(ident.to_string() + "Builder"), Span::call_site());
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let fields: &Fields = match ast.data {
syn::Data::Struct(ref s) => &s.fields,
_ => panic!("Can only derive Builder for structs."),
};
let named_fields: Vec<&Field> = fields
.iter()
.filter(|field| field.ident.is_some())
.collect();
let required_fields: Vec<&Field> = named_fields
.iter()
.filter_map(|field| {
if has_attr(field, "required") {
Some(*field)
} else {
None
}
})
.collect();
let optional_fields: Vec<&Field> = named_fields
.iter()
.filter_map(|field| {
if has_attr(field, "required") {
None
} else {
Some(*field)
}
})
.collect();
let builder_setter_methods: TokenStream = optional_fields
.iter()
.map(|field| {
let field_ident = &field.ident;
let field_ty = &field.ty;
let type_of_option = extract_type_from_option(field_ty);
quote! {
pub fn #field_ident(&mut self, #field_ident: #type_of_option) -> &mut Self {
self.#field_ident = ::std::option::Option::Some(#field_ident);
self
}
}
})
.collect();
let required_new_fields: TokenStream = required_fields
.iter()
.map(|field| {
let ident = &field.ident;
quote! {
#ident: ::std::option::Option::Some(#ident),
}
})
.collect();
let empty_new_fields: TokenStream = optional_fields
.iter()
.map(|field| {
let ident = &field.ident;
quote! {
#ident: None,
}
})
.collect();
let builder_required_fields: TokenStream = required_fields
.iter()
.map(|field| {
let ident = &field.ident;
let ty = &field.ty;
quote! {
#ident: ::std::option::Option<#ty>,
}
})
.collect();
let builder_optional_fields: TokenStream = optional_fields
.iter()
.map(|field| {
let ident = &field.ident;
let ty = &field.ty;
quote! {
#ident: #ty,
}
})
.collect();
let builder_struct_fields: TokenStream = builder_required_fields
.into_iter()
.chain(builder_optional_fields)
.collect();
let new_method_params: TokenStream = required_fields
.iter()
.map(|field| {
let (arg, ty) = (&field.ident, &field.ty);
quote! {
#arg: #ty,
}
})
.collect();
let build_fn_struct_fields: TokenStream = named_fields
.iter()
.map(|field| {
let is_required = has_attr(field, "required");
let ident = &field.ident;
if is_required {
quote! {
#ident: self.#ident.take().expect("Option must be Some(T) for required fields. Builder may have already been consumed by calling `build`"),
}
} else {
quote! {
#ident: self.#ident.take(),
}
}
})
.collect();
let struct_impl = quote! {
impl #impl_generics #ident #ty_generics #where_clause {
pub fn builder(#new_method_params) -> #builder_ident #ty_generics {
#builder_ident {
#required_new_fields
#empty_new_fields
}
}
}
};
let builder_struct = quote! {
#vis struct #builder_ident #ty_generics #where_clause {
#builder_struct_fields
}
impl #impl_generics #builder_ident #ty_generics #where_clause {
pub fn new(#new_method_params) -> #builder_ident #ty_generics {
#builder_ident {
#required_new_fields
#empty_new_fields
}
}
pub fn build(&mut self) -> #ident #ty_generics {
#ident {
#build_fn_struct_fields
}
}
#builder_setter_methods
}
};
let output = quote! {
#struct_impl
#builder_struct
};
output.into()
}
fn has_attr(field: &Field, attr: &'static str) -> bool {
field.attrs.iter().any(|a| has_nested_attr(a, attr))
}
fn has_nested_attr(attr: &Attribute, name: &'static str) -> bool {
let mut has_attr: bool = false;
if attr.path().is_ident("builder") {
attr.parse_nested_meta(|m| {
if m.path.is_ident(name) {
has_attr = true;
}
Ok(())
})
.expect("Parsing nested meta within #[builder(...)] failed.");
}
has_attr
}
fn extract_type_from_option(ty: &Type) -> &Type {
fn path_is_option(path: &Path) -> bool {
path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.iter().next().unwrap().ident == "Option"
}
match ty {
Type::Path(type_path) if type_path.qself.is_none() && path_is_option(&type_path.path) => {
let type_params: &PathArguments = &(type_path.path.segments.first().unwrap()).arguments;
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.first().unwrap(),
_ => panic!("Could not find generic parameter in Option<...>"),
};
match generic_arg {
GenericArgument::Type(ty) => ty,
_ => panic!(
"Found something other than a type as a generic parameter to Option<...>"
),
}
}
_ => panic!(
"Struct fields must be of type Option<...>, or have #[builder(required)] attribute."
),
}
}