summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..056b6f1
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,198 @@
+use attribute_derive::FromAttr;
+use proc_macro::TokenStream;
+use proc_macro2::Span;
+use quote::{format_ident, quote};
+use syn::{
+ Expr, ExprStruct, Ident, ItemStruct, LitBool, Type, Visibility, parse_macro_input, token::Pub,
+};
+
+enum Default {
+ None,
+ Impl,
+ Value(Expr),
+}
+
+struct MaybeOptionalField {
+ visibility: Visibility,
+ name: Ident,
+ default: Default,
+ auto_into: bool,
+ ty: Type,
+}
+
+#[derive(FromAttr)]
+#[from_attr(ident = builder)]
+struct BuilderFieldOptions {
+ vis: Option<Visibility>,
+ optional: bool,
+ default: Option<Expr>,
+ into: bool,
+}
+
+#[proc_macro_derive(Builder, attributes(builder))]
+pub fn derive_builder(input: TokenStream) -> TokenStream {
+ let structure = parse_macro_input!(input as ItemStruct);
+ let struct_name = &structure.ident;
+ let builder_name = format_ident!("{}Builder", structure.ident);
+ let fields = structure
+ .fields
+ .into_iter()
+ .map(|field| {
+ let attrs = BuilderFieldOptions::from_attributes(field.attrs).unwrap();
+ MaybeOptionalField {
+ visibility: attrs.vis.unwrap_or(field.vis),
+ name: field.ident.unwrap(),
+ default: match (attrs.optional, attrs.default) {
+ (true, None) => Default::Impl,
+ (_, Some(expr)) => Default::Value(expr),
+ (false, None) => Default::None,
+ },
+ auto_into: attrs.into,
+ ty: field.ty,
+ }
+ })
+ .collect::<Box<_>>();
+
+ let builder_visibility = fields
+ .iter()
+ .all(|field| matches!(field.visibility, Visibility::Public(_)))
+ .then_some(Visibility::Public(Pub {
+ span: Span::call_site(),
+ }));
+ let field_names = fields
+ .iter()
+ .map(|field| field.name.clone())
+ .collect::<Box<_>>();
+ let field_types = fields
+ .iter()
+ .map(|field| field.ty.clone())
+ .collect::<Box<_>>();
+ let const_generics = fields
+ .iter()
+ .filter(|field| matches!(field.default, Default::None))
+ .map(|field| Ident::new(&field.name.to_string().to_uppercase(), field.name.span()))
+ .collect::<Box<_>>();
+ let trues = (0..const_generics.len())
+ .map(|_| LitBool::new(true, Span::call_site()))
+ .collect::<Box<_>>();
+ let falses = (0..const_generics.len())
+ .map(|_| LitBool::new(false, Span::call_site()))
+ .collect::<Box<_>>();
+
+ let mut j = 0;
+ let setters = fields
+ .iter()
+ .enumerate()
+ .map(|(i, field)| {
+ let visibility = &field.visibility;
+ let setter_name = &field.name;
+ let ty = &field.ty;
+ let param_ty = if field.auto_into { quote! {#ty} } else { quote! { impl ::core::convert::Into<#ty> } };
+ let field_name = &field.name;
+ let fields_head = &field_names[0..i];
+ let fields_tail = &field_names[i+1..];
+ if matches!(field.default, Default::None) {
+ let generics_head = &const_generics[0..j];
+ let generics_tail = &const_generics[j+1..];
+ j += 1;
+ quote! {
+ #visibility fn #setter_name(self, value: #param_ty>) -> #builder_name<#(#generics_head,)* true, #(#generics_tail),*> {
+ #builder_name {
+ #(#fields_head: self.#fields_head,)*
+ #field_name: Some(value.into()),
+ #(#fields_tail: self.#fields_tail,)*
+ }
+ }
+ }
+ } else {
+ let maybe_setter_name = format_ident!("maybe_{}", &field.name);
+ let clear_name = format_ident!("clear_{}", &field.name);
+ quote! {
+ #visibility fn #setter_name(self, value: #param_ty) -> #builder_name<#(#const_generics),*> {
+ #builder_name {
+ #(#fields_head: self.#fields_head,)*
+ #field_name: Some(value.into()),
+ #(#fields_tail: self.#fields_tail,)*
+ }
+ }
+
+ #visibility fn #maybe_setter_name(self, value: core::option::Option<#param_ty>) -> #builder_name<#(#const_generics),*> {
+ #builder_name {
+ #(#fields_head: self.#fields_head,)*
+ #field_name: value.map(Into::into),
+ #(#fields_tail: self.#fields_tail,)*
+ }
+ }
+
+ #visibility fn #clear_name(self) -> #builder_name<#(#const_generics),*> {
+ #builder_name {
+ #(#fields_head: self.#fields_head,)*
+ #field_name: None,
+ #(#fields_tail: self.#fields_tail,)*
+ }
+ }
+ }
+ }
+ })
+ .collect::<Box<_>>();
+ let build_fields = fields.iter().map(|field| {
+ let field_name = &field.name;
+ match &field.default {
+ Default::Value(default_value) => {
+ quote! { #field_name: self.#field_name.unwrap_or_else(|| #default_value) }
+ }
+ Default::Impl => quote! { #field_name: self.#field_name.unwrap_or_default() },
+ Default::None => quote! { #field_name: unsafe { self.#field_name.unwrap_unchecked() } },
+ }
+ });
+
+ quote! {
+ #builder_visibility struct #builder_name<#(const #const_generics: bool),*> {
+ #(#field_names: Option<#field_types>,)*
+ }
+
+ impl<#(const #const_generics: bool),*> core::default::Default for #builder_name<#(#const_generics),*> {
+ fn default() -> Self {
+ Self {
+ #(#field_names: None,)*
+ }
+ }
+ }
+
+ impl #struct_name {
+ #builder_visibility fn builder() -> #builder_name<#(#falses),*> {
+ #builder_name::default()
+ }
+ }
+
+ impl<#(const #const_generics: bool),*> #builder_name<#(#const_generics),*> {
+ #(#setters)*
+ }
+
+ impl #builder_name<#(#trues),*> {
+ #builder_visibility fn build(self) -> #struct_name {
+ #struct_name {
+ #(#build_fields,)*
+ }
+ }
+ }
+ }
+ .into()
+}
+
+#[proc_macro]
+pub fn build(input: TokenStream) -> TokenStream {
+ let structure = parse_macro_input!(input as ExprStruct);
+ let struct_name = &structure.path;
+
+ let field_names = structure.fields.iter().map(|field| &field.member);
+ let field_values = structure.fields.iter().map(|field| &field.expr);
+
+ quote! {
+ #struct_name
+ ::builder()
+ #(.#field_names(#field_values))*
+ .build()
+ }
+ .into()
+}