diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 147 |
1 files changed, 131 insertions, 16 deletions
@@ -59,7 +59,10 @@ 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, + Expr, ExprStruct, Ident, ItemStruct, LitBool, Path, Token, Type, Visibility, + parse::{Parse, ParseStream}, + parse_macro_input, + token::Pub, }; enum Default { @@ -89,13 +92,16 @@ struct BuilderFieldOptions { #[from_attr(ident = builder)] struct BuilderOptions { vis: Option<Visibility>, + makro: bool, } /// 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 the struct, it may take the +/// following arguments: +/// - `vis`: The visibility of the `builder` method. +/// - `makro`: Creates a macro to construct an instance of the struct. /// /// When the `builder` attribute is applied to a field of the struct, there are /// several arguments it may take: @@ -115,7 +121,7 @@ struct BuilderOptions { /// use feluments::Builder; /// /// #[derive(Debug, PartialEq, Eq, Builder)] -/// #[builder(vis = pub)] +/// #[builder(vis = pub, makro)] /// struct Foo { /// #[builder(default = 45)] /// x: i32, @@ -130,6 +136,10 @@ struct BuilderOptions { /// Foo::builder().y("bar").z(()).build(), /// Foo { x: 45, y: "bar".into(), z: () }, /// ); +/// assert_eq!( +/// Foo! { y: "bar", z: () }, +/// Foo { x: 45, y: "bar".into(), z: () }, +/// ) /// # } /// ``` /// @@ -158,18 +168,28 @@ pub fn derive_builder(input: TokenStream) -> TokenStream { }) .collect::<Box<_>>(); - let builder_visibility = BuilderOptions::from_attributes(structure.attrs) - .map(|options| options.vis) - .ok() - .flatten() - .or_else(|| { - fields - .iter() - .all(|field| matches!(field.visibility, Visibility::Public(_))) - .then_some(Visibility::Public(Pub { - span: Span::call_site(), - })) - }); + let builder_options = BuilderOptions::from_attributes(structure.attrs).unwrap(); + let is_builder_visible = matches!(builder_options.vis, Some(Visibility::Public(_))) + || fields + .iter() + .all(|field| matches!(field.visibility, Visibility::Public(_))); + let builder_macro_vis = is_builder_visible.then(|| quote! { #[macro_export]}); + let builder_macro = builder_options.makro.then(|| { + quote! { + #builder_macro_vis + macro_rules! #struct_name { + ($($t: tt)*) => { ::feluments::build!(#struct_name { $($t)* })} + } + } + }); + let builder_visibility = builder_options.vis.or_else(|| { + 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()) @@ -262,6 +282,8 @@ pub fn derive_builder(input: TokenStream) -> TokenStream { #(#field_names: Option<#field_types>,)* } + #builder_macro + impl<#(const #const_generics: bool),*> core::default::Default for #builder_name<#(#const_generics),*> { fn default() -> Self { Self { @@ -339,3 +361,96 @@ pub fn build(input: TokenStream) -> TokenStream { } .into() } + +/// Creates a declarative macro to construct a type that derives [`Builder`] +/// using a constructor literal syntax. +/// +/// This is used to create convenience macros 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: () +/// } +/// +/// // If the `pub` keyword is used, then #[macro_export] is applied +/// builder_macro!(pub Foo = Foo); +/// +/// # fn main() { +/// assert_eq!( +/// build!(Foo { x: 32, y: "baz" }), +/// Foo { x: 32, y: "baz".into(), z: () }, +/// ); +/// # } +/// ``` +#[proc_macro] +pub fn builder_macro(input: TokenStream) -> TokenStream { + struct BuilderAssignment { + vis: Visibility, + left: Ident, + _equal: Token![=], + metavar: Option<Token![$]>, + metacrate: Option<Token![crate]>, + path_sep: Option<Token![::]>, + right: Path, + } + + impl Parse for BuilderAssignment { + fn parse(input: ParseStream) -> syn::Result<Self> { + let vis = input.parse()?; + let left = input.parse()?; + let _equal = input.parse()?; + let metavar = input.parse()?; + let metacrate = input.parse()?; + let path_sep = input.parse()?; + let right = input.parse()?; + + Ok(Self { + vis, + left, + _equal, + metavar, + metacrate, + path_sep, + right, + }) + } + } + + let assignment = parse_macro_input!(input as BuilderAssignment); + let attribute = match assignment.vis { + Visibility::Public(_) => Some(quote! { #[macro_export] }), + Visibility::Inherited => None, + Visibility::Restricted(_) => panic!("Expected visibility to be `pub`"), + }; + let builder_name = assignment.left; + let BuilderAssignment { + metavar, + metacrate, + path_sep, + .. + } = assignment; + let path = assignment.right; + + quote! { + #attribute + macro_rules! #builder_name { + ($($t: tt)*) => { ::feluments::build!(#metavar #metacrate #path_sep #path { $($t)* }) }; + } + } + .into() +} |
