From c1de10adc1b13b0190ebac7e9f0b6909f3d367b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:48:12 +0000 Subject: [PATCH 1/5] Initial plan From 78e52f54a7b2490f85a4363c52b93217a02fe573 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:50:49 +0000 Subject: [PATCH 2/5] chore: upgrade uuid and tokio dependency versions Co-authored-by: SeokminHong <11614766+SeokminHong@users.noreply.github.com> Agent-Logs-Url: https://github.com/SeokminHong/builder-pattern/sessions/0009858d-a639-476e-86b5-9bd5c9bd0cde --- builder-pattern/Cargo.toml | 4 ++-- test-no-future/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder-pattern/Cargo.toml b/builder-pattern/Cargo.toml index 923d80f..ab3e886 100644 --- a/builder-pattern/Cargo.toml +++ b/builder-pattern/Cargo.toml @@ -17,8 +17,8 @@ builder-pattern-macro = { version = "0.4.2", path = "../builder-pattern-macro" } futures = { version = "0.3", optional = true } [dev-dependencies] -uuid = { version = "0.8", features = ["v4"] } -tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] } +uuid = { version = "1", features = ["v4"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tokio-test = "0.4" [features] diff --git a/test-no-future/Cargo.toml b/test-no-future/Cargo.toml index 408a28a..ac8c22a 100644 --- a/test-no-future/Cargo.toml +++ b/test-no-future/Cargo.toml @@ -9,5 +9,5 @@ license = "MIT" builder-pattern = { version = "0.4.2", path = "../builder-pattern", default-features = false } [dev-dependencies] -uuid = { version = "0.8", features = ["v4"] } -tokio = { version = "1.12", features = ["macros", "rt-multi-thread"] } +uuid = { version = "1", features = ["v4"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } From 2a75e9719275aa991d883483d41f52a6b8f6d315 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:56:10 +0000 Subject: [PATCH 3/5] chore: upgrade builder-pattern-macro dependencies to latest major versions Co-authored-by: SeokminHong <11614766+SeokminHong@users.noreply.github.com> Agent-Logs-Url: https://github.com/SeokminHong/builder-pattern/sessions/b5c69e90-22ee-4b28-bf99-9ca0171854e4 --- builder-pattern-macro/Cargo.toml | 4 +- builder-pattern-macro/src/attributes.rs | 71 +++++++++++-------- .../src/builder/builder_functions.rs | 2 +- .../src/builder/builder_impl.rs | 1 - builder-pattern-macro/src/field.rs | 4 +- builder-pattern-macro/src/struct_impl.rs | 1 - builder-pattern-macro/src/struct_input.rs | 10 +-- 7 files changed, 52 insertions(+), 41 deletions(-) diff --git a/builder-pattern-macro/Cargo.toml b/builder-pattern-macro/Cargo.toml index 9b1d681..307f7b5 100644 --- a/builder-pattern-macro/Cargo.toml +++ b/builder-pattern-macro/Cargo.toml @@ -12,9 +12,9 @@ keywords = ["builder", "pattern", "macro", "derive", "struct"] [dependencies] proc-macro2 = "1.0" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2", features = ["full"] } quote = "1.0" -bitflags = "1.3" +bitflags = "2" [lib] proc-macro = true diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index de8002c..d79bfba 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -1,7 +1,14 @@ use bitflags::bitflags; -use syn::{Attribute, Expr, Meta, NestedMeta}; +use syn::{ + Attribute, Expr, + ext::IdentExt, + parse::{ParseStream, Parser}, + punctuated::Punctuated, + token::Comma, +}; bitflags! { + #[derive(Clone, Copy, PartialEq, Eq)] pub struct Setters: u32 { const VALUE = 0b00000001; const LAZY = 0b00000010; @@ -42,38 +49,38 @@ impl From> for FieldAttributes { fn from(attrs: Vec) -> FieldAttributes { let mut attributes = FieldAttributes::default(); attrs.iter().for_each(|attr| { - if attr.path.is_ident("default") { + if attr.path().is_ident("default") { if attributes.default.is_some() { unimplemented!("Duplicated `default` attributes.") } parse_default(attr, &mut attributes) - } else if attr.path.is_ident("default_lazy") { + } else if attr.path().is_ident("default_lazy") { if attributes.default.is_some() { unimplemented!("Duplicated `default` attributes.") } parse_lazy_default(attr, &mut attributes) - } else if attr.path.is_ident("default_async") { + } else if attr.path().is_ident("default_async") { if attributes.default.is_some() { unimplemented!("Duplicated `default` attributes.") } unimplemented!("Asynchronous default is not implemented yet.") - } else if attr.path.is_ident("hidden") { + } else if attr.path().is_ident("hidden") { if attributes.vis != FieldVisibility::Default { unimplemented!("Duplicated `hidden` attributes.") } attributes.vis = FieldVisibility::Hidden; - } else if attr.path.is_ident("public") { + } else if attr.path().is_ident("public") { if attributes.vis != FieldVisibility::Default { unimplemented!("Duplicated `public` attributes.") } attributes.vis = FieldVisibility::Public; - } else if attr.path.is_ident("into") { + } else if attr.path().is_ident("into") { attributes.use_into = true - } else if attr.path.is_ident("validator") { + } else if attr.path().is_ident("validator") { parse_validator(attr, &mut attributes) - } else if attr.path.is_ident("doc") { + } else if attr.path().is_ident("doc") { attributes.documents = get_documents(&attrs); - } else if attr.path.is_ident("setter") { + } else if attr.path().is_ident("setter") { parse_setters(attr, &mut attributes) } }); @@ -106,26 +113,32 @@ fn parse_validator(attr: &Attribute, attributes: &mut FieldAttributes) { } fn parse_setters(attr: &Attribute, attributes: &mut FieldAttributes) { - let meta = attr.parse_meta().unwrap(); let mut setters = Setters::empty(); - if let Meta::List(l) = meta { - let it = l.nested.iter(); - it.for_each(|m| { - if let NestedMeta::Meta(Meta::Path(p)) = m { - if p.is_ident("value") { - setters.insert(Setters::VALUE); - } else if p.is_ident("lazy") { - setters.insert(Setters::LAZY); - } else if p.is_ident("async") { - setters.insert(Setters::ASYNC); - } - } else { - unimplemented!("Invalid setter.") + let parser = |input: ParseStream| { + let mut values = Punctuated::new(); + while !input.is_empty() { + values.push_value(input.call(syn::Ident::parse_any)?); + if input.is_empty() { + break; } - }); - } else { - unimplemented!("Invalid setter.") - } + values.push_punct(input.parse()?); + } + Ok::, syn::Error>(values) + }; + let metas = parser + .parse2(attr.meta.require_list().unwrap().tokens.clone()) + .unwrap(); + metas.iter().for_each(|setter| { + if setter == "value" { + setters.insert(Setters::VALUE); + } else if setter == "lazy" { + setters.insert(Setters::LAZY); + } else if setter == "async" { + setters.insert(Setters::ASYNC); + } else { + unimplemented!("Invalid setter.") + } + }); attributes.setters = setters; } @@ -133,7 +146,7 @@ pub fn get_documents(attrs: &[Attribute]) -> Vec { let mut documents: Vec = vec![]; for attr in attrs { - if attr.path.is_ident("doc") { + if attr.path().is_ident("doc") { documents.push(attr.to_owned()); } } diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 66ad37a..9ea59b3 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -61,7 +61,7 @@ impl<'a> BuilderFunctions<'a> { let mut docs: Vec = Vec::new(); let default = match f.attrs.default.as_ref() { - Some((expr, _)) => format!("\n - Default: `{}`", expr.into_token_stream().to_string()), + Some((expr, _)) => format!("\n - Default: `{}`", expr.into_token_stream()), None => String::from(""), }; let doc = format!( diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 10706a3..5af1bd1 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -47,7 +47,6 @@ impl<'a> BuilderImpl<'a> { fn optional_generics(&self) -> impl Iterator { let offset = self.input.required_fields.len() + 1; (0..self.input.optional_fields.len()) - .into_iter() .map(move |i| { TokenStream::from_str(&format!("TyBuilderPattern{}", i + offset)).unwrap() }) diff --git a/builder-pattern-macro/src/field.rs b/builder-pattern-macro/src/field.rs index 2578c5a..96f6a84 100644 --- a/builder-pattern-macro/src/field.rs +++ b/builder-pattern-macro/src/field.rs @@ -17,7 +17,7 @@ impl Field { self.attrs .documents .iter() - .filter(|a| a.path.is_ident("doc")) + .filter(|a| a.path().is_ident("doc")) .map(|a| a.to_owned()) .collect() } @@ -40,7 +40,7 @@ impl Ord for Field { impl PartialOrd for Field { fn partial_cmp(&self, other: &Field) -> Option { - Some(self.ident.cmp(&other.ident)) + Some(self.cmp(other)) } } diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 9eaa6de..75c422d 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -59,7 +59,6 @@ impl<'a> StructImpl<'a> { /// An iterator to describe initial state of builder. fn empty_generics(&self) -> impl Iterator { (0..(self.input.required_fields.len() + self.input.optional_fields.len())) - .into_iter() .map(|_| TokenStream::from_str("()").unwrap()) } diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index 72f8a32..a1c0332 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -10,14 +10,16 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream, Result}, - AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Lifetime, Token, - VisPublic, Visibility, + parse_quote, AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, + Lifetime, Token, + Visibility, }; pub struct StructInput { pub vis: Visibility, pub ident: Ident, pub generics: Generics, + #[allow(dead_code)] pub attrs: Vec, pub required_fields: Vec, pub optional_fields: Vec, @@ -57,8 +59,7 @@ impl Parse for StructInput { }; fields.push(Field { vis: if attrs.vis == FieldVisibility::Public { - let v = ::default(); - Visibility::Public(VisPublic { pub_token: v }) + parse_quote!(pub) } else { f.vis }, @@ -129,7 +130,6 @@ impl StructInput { /// An iterator for generics like [U1, U2, ...]. pub fn all_generics(&self) -> impl Iterator { (0..(self.num_fields())) - .into_iter() .map(|i| TokenStream::from_str(&format!("TyBuilderPattern{}", i + 1)).unwrap()) } From 47291a5c2d71ac8996fbc179434915bf17529b8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:59:36 +0000 Subject: [PATCH 4/5] chore: plan clippy warning cleanup Co-authored-by: SeokminHong <11614766+SeokminHong@users.noreply.github.com> Agent-Logs-Url: https://github.com/SeokminHong/builder-pattern/sessions/b5c69e90-22ee-4b28-bf99-9ca0171854e4 --- builder-pattern-macro/src/attributes.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index d79bfba..315cc87 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -118,16 +118,21 @@ fn parse_setters(attr: &Attribute, attributes: &mut FieldAttributes) { let mut values = Punctuated::new(); while !input.is_empty() { values.push_value(input.call(syn::Ident::parse_any)?); - if input.is_empty() { - break; + if input.peek(Comma) { + values.push_punct(input.parse()?); } - values.push_punct(input.parse()?); } Ok::, syn::Error>(values) }; let metas = parser - .parse2(attr.meta.require_list().unwrap().tokens.clone()) - .unwrap(); + .parse2( + attr.meta + .require_list() + .expect("`setter` attribute must be a list, e.g. #[setter(value, lazy)]") + .tokens + .clone(), + ) + .expect("`setter` attribute must contain valid identifiers separated by commas"); metas.iter().for_each(|setter| { if setter == "value" { setters.insert(Setters::VALUE); From 10bd1ad7f8f1bf0ed9f2f3a315afbf716d0fb03a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 01:01:29 +0000 Subject: [PATCH 5/5] chore: address clippy warnings in docs and macro upgrade changes Co-authored-by: SeokminHong <11614766+SeokminHong@users.noreply.github.com> Agent-Logs-Url: https://github.com/SeokminHong/builder-pattern/sessions/b5c69e90-22ee-4b28-bf99-9ca0171854e4 --- builder-pattern/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder-pattern/src/lib.rs b/builder-pattern/src/lib.rs index 266ebe1..4f24a1f 100644 --- a/builder-pattern/src/lib.rs +++ b/builder-pattern/src/lib.rs @@ -78,9 +78,9 @@ //! - **Chaining**: Can make structure with chained setters. //! - **Complex types are supported**: Lifetime, trait bounds, and where clauses are well supported. //! - **Type safety**: Autocompletion tools can suggest correct setters to build the struct. Also, `build` -//! function is allowed only the all of required fields are provided. **No Result**, **No Unwrap**. Just use it. +//! function is allowed only the all of required fields are provided. **No Result**, **No Unwrap**. Just use it. //! - **Lazy evaluation and asynchronous**: Lazy evaluation and asynchronous are supported. -//! The values will be evaluated when the structure is built. +//! The values will be evaluated when the structure is built. //! - **No additional tasks**: There's no additional constraints to use the macro. Any structures and fields are allowed. //! - **Auto-generated documentation**: Documentation for the builder functions are automatically generated. //!