summaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 056b6f164b275eb2e696737275e4b3704cfa1a98 (plain)
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()
}