diff options
| -rw-r--r-- | Cargo.lock | 16 | ||||
| -rw-r--r-- | src/lib.rs | 147 | ||||
| -rw-r--r-- | tests/basic.rs | 27 |
3 files changed, 155 insertions, 35 deletions
@@ -101,18 +101,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -141,15 +141,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -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() +} diff --git a/tests/basic.rs b/tests/basic.rs index 5031b02..431695d 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,17 +1,22 @@ -use feluments::{Builder, build}; +mod foo { + use feluments::Builder; -#[derive(Builder)] -#[allow(dead_code)] -struct Foo { - #[builder(into, vis = pub)] - bar: String, - #[builder(default = 32)] - baz: i32, - bat: (), + #[derive(Builder)] + #[allow(dead_code)] + #[builder(makro)] + pub struct Foo { + #[builder(into, vis = pub)] + bar: String, + #[builder(default = 32)] + pub baz: i32, + pub bat: (), + } } +use foo::Foo; + fn main() { - let _: Foo = Foo::builder().baz(32).bar("hello").bat(()).build(); + let _x = Foo::builder().baz(32).bar("hello").bat(()).build(); let bar = "hello"; - let _: Foo = build!(Foo { bar, bat: () }); + let _: Foo = Foo! { bar, bat: () }; } |
