//! 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<Visibility>,
optional: bool,
default: Option<Expr>,
into: bool,
}
#[derive(FromAttr)]
#[from_attr(ident = builder)]
struct BuilderOptions {
vis: Option<Visibility>,
}
/// 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::<Box<_>>();
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::<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! { 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::<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! {
#[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()
}
|