summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-08-19 19:22:32 -0400
committerMica White <botahamec@outlook.com>2026-08-19 19:22:32 -0400
commitbda071150154b953a42cbf6743ac1b79f8bf0668 (patch)
treecf4605cea9537aca9be4512e0996b58db45b9762
parent2573cbc9622da4c74429b4f6bb9c640b95dc4c04 (diff)
Support custom fields and methods
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml9
-rw-r--r--src/lib.rs39
-rw-r--r--tests/basic.rs30
4 files changed, 58 insertions, 22 deletions
diff --git a/Cargo.lock b/Cargo.lock
index efab7c9..f9b6228 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -51,7 +51,7 @@ dependencies = [
[[package]]
name = "feluments"
-version = "0.1.0"
+version = "0.2.0"
dependencies = [
"attribute-derive",
"proc-macro2",
diff --git a/Cargo.toml b/Cargo.toml
index 8310d92..52eeaff 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,14 @@
[package]
name = "feluments"
-version = "0.1.0"
+version = "0.2.0"
+authors = ["Mica White <botahamec@outlook.com>"]
edition = "2024"
+rust-version = "1.85.1"
+description = "Derived builder macros"
+repository = "www.botahamec.dev/cgit/feluments"
+license = "CC0-1.0"
+keywords = ["macro", "derive", "macros", "builder"]
+categories = ["rust-patterns"]
[lib]
proc-macro = true
diff --git a/src/lib.rs b/src/lib.rs
index 315dd73..314f813 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,7 +8,7 @@
//! use feluments::*;
//!
//! #[derive(Debug, PartialEq, Eq, Builder)]
-//! #[builder(vis = pub)]
+//! #[builder(vis = pub, makro)]
//! struct Foo {
//! #[builder(default = 45)]
//! x: i32,
@@ -20,13 +20,13 @@
//!
//! # fn main() {
//! assert_eq!(
-//! build!(Foo { x: 32, y: "baz" }),
+//! Foo! { x: 32, y: "baz" },
//! Foo { x: 32, y: "baz".into(), z: () },
//! );
//! # }
//! ```
//!
-//! The above call to the [`build`] macro expands to the following:
+//! The above call to the `Foo` macro expands to the following:
//!
//! ```
//! # use feluments::*;
@@ -92,6 +92,7 @@ struct BuilderFieldOptions {
#[from_attr(ident = builder)]
struct BuilderOptions {
vis: Option<Visibility>,
+ name: Option<String>,
makro: bool,
}
@@ -101,6 +102,7 @@ struct BuilderOptions {
/// When the `builder` attribute is applied to the struct, it may take the
/// following arguments:
/// - `vis`: The visibility of the `builder` method.
+/// - `name`: Sets the name of the builder struct.
/// - `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
@@ -111,7 +113,8 @@ struct BuilderOptions {
/// 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`
+/// implies `optional`. The value may be an expression, and it may even
+/// reference previous fields.
/// - `into`: Arguments to the setter may be of any type that implements `Into`
/// for the type of the field.
///
@@ -125,7 +128,7 @@ struct BuilderOptions {
/// struct Foo {
/// #[builder(default = 45)]
/// x: i32,
-/// #[builder(into)]
+/// #[builder(into, default = x.to_string())]
/// y: String,
/// #[builder(optional)]
/// z: ()
@@ -133,11 +136,11 @@ struct BuilderOptions {
///
/// # fn main() {
/// assert_eq!(
-/// Foo::builder().y("bar").z(()).build(),
-/// Foo { x: 45, y: "bar".into(), z: () },
+/// Foo::builder().x(2).z(()).build(),
+/// Foo { x: 2, y: "2".into(), z: () },
/// );
/// assert_eq!(
-/// Foo! { y: "bar", z: () },
+/// Foo! { y: "bar" },
/// Foo { x: 45, y: "bar".into(), z: () },
/// )
/// # }
@@ -148,7 +151,6 @@ struct BuilderOptions {
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()
@@ -173,6 +175,11 @@ pub fn derive_builder(input: TokenStream) -> TokenStream {
|| fields
.iter()
.all(|field| matches!(field.visibility, Visibility::Public(_)));
+ let builder_name = if let Some(builder_name) = builder_options.name {
+ format_ident!("{builder_name}")
+ } else {
+ format_ident!("{}Builder", structure.ident)
+ };
let builder_macro_vis = is_builder_visible.then(|| quote! { #[macro_export]});
let builder_macro = builder_options.makro.then(|| {
quote! {
@@ -266,14 +273,16 @@ pub fn derive_builder(input: TokenStream) -> TokenStream {
}
})
.collect::<Box<_>>();
- let build_fields = fields.iter().map(|field| {
+ let build_field_assignments = 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) }
+ quote! { let #field_name = self.#field_name.unwrap_or_else(|| #default_value); }
+ }
+ Default::Impl => quote! { let #field_name = self.#field_name.unwrap_or_default(); },
+ Default::None => {
+ quote! { let #field_name = unsafe { self.#field_name.unwrap_unchecked() }; }
}
- Default::Impl => quote! { #field_name: self.#field_name.unwrap_or_default() },
- Default::None => quote! { #field_name: unsafe { self.#field_name.unwrap_unchecked() } },
}
});
@@ -302,10 +311,12 @@ pub fn derive_builder(input: TokenStream) -> TokenStream {
#(#setters)*
}
+ #[allow(all)]
impl #builder_name<#(#trues),*> {
#builder_visibility fn build(self) -> #struct_name {
+ #(#build_field_assignments)*
#struct_name {
- #(#build_fields,)*
+ #(#field_names,)*
}
}
}
diff --git a/tests/basic.rs b/tests/basic.rs
index 431695d..ce3abf8 100644
--- a/tests/basic.rs
+++ b/tests/basic.rs
@@ -1,22 +1,40 @@
mod foo {
use feluments::Builder;
- #[derive(Builder)]
+ #[derive(Debug, PartialEq, Eq, Builder)]
#[allow(dead_code)]
- #[builder(makro)]
+ #[builder(name = "FooBuild", makro)]
pub struct Foo {
#[builder(into, vis = pub)]
- bar: String,
+ pub bar: String,
#[builder(default = 32)]
pub baz: i32,
- pub bat: (),
+ #[builder(default = baz * 2)]
+ pub bat: i32,
}
}
use foo::Foo;
fn main() {
- let _x = Foo::builder().baz(32).bar("hello").bat(()).build();
+ let x = Foo::builder().baz(42).bar("hello").bat(5).build();
+ assert_eq!(
+ x,
+ Foo {
+ bar: "hello".into(),
+ baz: 42,
+ bat: 5
+ }
+ );
+
let bar = "hello";
- let _: Foo = Foo! { bar, bat: () };
+ let _: Foo = Foo! { bar };
+ assert_eq!(
+ x,
+ Foo {
+ bar: "hello".into(),
+ baz: 32,
+ bat: 64
+ }
+ );
}