summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-08-19 18:37:32 -0400
committerMica White <botahamec@outlook.com>2026-08-19 18:37:32 -0400
commit2573cbc9622da4c74429b4f6bb9c640b95dc4c04 (patch)
treea3e8cfa307a8b335b3411dd573c06f24d56e4401
parent67fc414e1e490da951bd0fd037f8ad179a0c0824 (diff)
Support struct-specific builder macros
-rw-r--r--Cargo.lock16
-rw-r--r--src/lib.rs147
-rw-r--r--tests/basic.rs27
3 files changed, 155 insertions, 35 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ffd45cf..efab7c9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/src/lib.rs b/src/lib.rs
index 0289b15..315dd73 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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: () };
}