//! A macro for optional arguments //! //! There are many builder crates for Rust. Thanks to Rust's powerful macro //! system, it is easy to make a derive macro that creates a builder for a //! struct. But none of them take the next step of adding a macro to allow //! using literal syntax. //! ``` //! use feluments::*; //! //! #[derive(Debug, PartialEq, Eq, Builder)] //! #[builder(vis = pub)] //! struct Foo { //! #[builder(default = 45)] //! x: i32, //! #[builder(into)] //! y: String, //! #[builder(optional)] //! z: () //! } //! //! # fn main() { //! assert_eq!( //! build!(Foo { x: 32, y: "baz" }), //! Foo { x: 32, y: "baz".into(), z: () }, //! ); //! # } //! ``` //! //! The above call to the [`build`] macro expands to the following: //! //! ``` //! # use feluments::*; //! # #[derive(Debug, PartialEq, Eq, Builder)] //! # #[builder(vis = pub)] //! # struct Foo { //! # #[builder(default = 45)] //! # x: i32, //! # #[builder(into)] //! # y: String, //! # #[builder(optional)] //! # z: () //! # } //! # fn main() { //! # assert_eq!( //! Foo::builder().x(32).y("baz").build(), //! # Foo { x: 32, y: "baz".into(), z: () }, //! # ); //! # } //! ``` //! //! Although this library provides a [`Builder`] derive macro to make the //! [`build`] macro work, the `build!` macro is also compatible with other //! builder crates, such as [bon](https://bon-rs.com/), //! [buildstructor](https://crates.io/crates/buildstructor), or //! [typed-builder](https://crates.io/crates/typed-builder). 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, optional: bool, default: Option, into: bool, } #[derive(FromAttr)] #[from_attr(ident = builder)] struct BuilderOptions { vis: Option, } /// Creates a builder implementation for struct that is compatible with the /// build! macro. /// /// When the `builder` attribute is applied to the struct, it may take a `vis` /// argument, followed by the visibility of the `builder` method. /// /// When the `builder` attribute is applied to a field of the struct, there are /// several arguments it may take: /// - `vis`: The visibility of the setter for this field. The default is the /// visibility of the field. /// - `optional`: Makes the field optional. If the `default` argument is /// provided, then the default value will be the one provided. Otherwise, it /// will be [`Default::default`]. /// - `default`: The default value of the field. Setting this parameter also /// implies `optional` /// - `into`: Arguments to the setter may be of any type that implements `Into` /// for the type of the field. /// /// # Examples /// /// ``` /// use feluments::Builder; /// /// #[derive(Debug, PartialEq, Eq, Builder)] /// #[builder(vis = pub)] /// struct Foo { /// #[builder(default = 45)] /// x: i32, /// #[builder(into)] /// y: String, /// #[builder(optional)] /// z: () /// } /// /// # fn main() { /// assert_eq!( /// Foo::builder().y("bar").z(()).build(), /// Foo { x: 45, y: "bar".into(), z: () }, /// ); /// # } /// ``` /// /// [`Default::default`]: std::default::Default::default #[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::>(); let builder_visibility = BuilderOptions::from_attributes(structure.attrs) .map(|options| options.vis) .ok() .flatten() .or_else(|| { // a normal struct can be constructed if all of the fields are public, so // it makes sense to also allow a builder in this case. 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::>(); let field_types = fields .iter() .map(|field| field.ty.clone()) .collect::>(); 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::>(); let trues = (0..const_generics.len()) .map(|_| LitBool::new(true, Span::call_site())) .collect::>(); let falses = (0..const_generics.len()) .map(|_| LitBool::new(false, Span::call_site())) .collect::>(); 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! { impl ::core::convert::Into<#ty> } } else { quote! {#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::>(); 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! { #[doc(hidden)] #builder_visibility struct #builder_name<#(const #const_generics: bool),*> { #(#field_names: Option<#field_types>,)* } // using impl instead of derive to improve compilation time 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() } /// Builds a value of a type that derives [`Builder`] using a constructor /// literal syntax. /// /// This is meant to be a convenience macro to using `Builder`, with a less /// verbose syntax. Although this crate contains a `Builder` derive macro that /// works well with this macro, this macro is also compatible with other crates /// such as [bon](https://bon-rs.com/), [buildstructor](https://crates.io/crates/buildstructor), /// or [typed-builder](https://crates.io/crates/typed-builder). /// /// # Examples /// /// ``` /// use feluments::*; /// /// #[derive(Debug, PartialEq, Eq, Builder)] /// #[builder(vis = pub)] /// struct Foo { /// #[builder(default = 45)] /// x: i32, /// #[builder(into)] /// y: String, /// #[builder(optional)] /// z: () /// } /// /// # fn main() { /// assert_eq!( /// build!(Foo { x: 32, y: "baz" }), /// Foo { x: 32, y: "baz".into(), z: () }, /// ); /// # } /// ``` #[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() }