From d05a022d796b9dd0779e8838f1b18457f203a56a Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 18 Jul 2025 18:10:11 +0000 Subject: [PATCH 1/2] Replace custom `const` keyword parsing with new `darling` builtin keyword parsing --- .../builder_gen/input_fn/validation.rs | 14 +++---- .../builder/builder_gen/member/config/mod.rs | 2 +- bon-macros/src/builder/builder_gen/models.rs | 8 +++- .../builder_gen/top_level_config/mod.rs | 41 +++---------------- bon/tests/integration/builder/attr_const.rs | 3 +- 5 files changed, 21 insertions(+), 47 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_fn/validation.rs b/bon-macros/src/builder/builder_gen/input_fn/validation.rs index 8effae15..e539f531 100644 --- a/bon-macros/src/builder/builder_gen/input_fn/validation.rs +++ b/bon-macros/src/builder/builder_gen/input_fn/validation.rs @@ -30,14 +30,12 @@ impl super::FnInputCtx<'_> { } } - if let Some(const_) = &self.config.const_ { - if self.fn_item.orig.sig.constness.is_none() { - bail!( - &const_, - "#[builder(const)] requires the underlying function to be \ - marked as `const fn`" - ); - } + if self.config.const_.is_present() && self.fn_item.orig.sig.constness.is_none() { + bail!( + &self.config.const_.span(), + "#[builder(const)] requires the underlying function to be \ + marked as `const fn`" + ); } Ok(()) diff --git a/bon-macros/src/builder/builder_gen/member/config/mod.rs b/bon-macros/src/builder/builder_gen/member/config/mod.rs index a345f6fd..d8181f90 100644 --- a/bon-macros/src/builder/builder_gen/member/config/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/config/mod.rs @@ -209,7 +209,7 @@ impl MemberConfig { } pub(crate) fn validate(&self, top_config: &TopLevelConfig, origin: MemberOrigin) -> Result { - if top_config.const_.is_some() { + if top_config.const_.is_present() { self.require_const_compat()?; } diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 4462b3d2..5ffd28d0 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -181,7 +181,7 @@ pub(super) struct BuilderGenCtxParams<'a> { pub(super) members: Vec, pub(super) allow_attrs: Vec, - pub(super) const_: Option, + pub(super) const_: darling::util::Flag, pub(super) on: Vec, /// This is the visibility of the original item that the builder is generated for. @@ -357,6 +357,12 @@ impl BuilderGenCtx { } }); + let const_ = if const_.is_present() { + Some(syn::Token![const](const_.span())) + } else { + None + }; + Ok(Self { bon, state_var, diff --git a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs index a917f522..d5b993cf 100644 --- a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs +++ b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs @@ -6,7 +6,6 @@ use crate::parsing::{BonCratePath, ItemSigConfig, ItemSigConfigParsing, SpannedK use crate::util::prelude::*; use darling::ast::NestedMeta; use darling::FromMeta; -use syn::parse::Parser; use syn::punctuated::Punctuated; use syn::ItemFn; @@ -44,13 +43,9 @@ fn parse_start_fn(meta: &syn::Meta) -> Result { #[derive(Debug, FromMeta)] pub(crate) struct TopLevelConfig { - /// Specifies whether the generated functions should be `const`. - /// - /// It is marked as `#[darling(skip)]` because `const` is a keyword, that - /// can't be parsed as a `syn::Ident` and therefore as a `syn::Meta` item. - /// We manually parse it from the beginning `builder(...)`. - #[darling(skip)] - pub(crate) const_: Option, + /// Specifies whether the generated functions should be `co + #[darling(rename = "const")] + pub(crate) const_: darling::util::Flag, /// Overrides the path to the `bon` crate. This is useful when the macro is /// wrapped in another macro that also reexports `bon`. @@ -129,30 +124,7 @@ impl TopLevelConfig { Self::parse_for_any(configs) } - fn parse_for_any(mut configs: Vec) -> Result { - fn parse_const_prefix( - parse: syn::parse::ParseStream<'_>, - ) -> syn::Result<(Option, TokenStream)> { - let const_ = parse.parse().ok(); - if const_.is_some() && !parse.is_empty() { - parse.parse::()?; - } - - let rest = parse.parse()?; - Ok((const_, rest)) - } - - // Try to parse the first token of the first config as `const` token. - // We have to do this manually because `syn` doesn't support parsing - // keywords in the `syn::Meta` keys. Yeah, unfortunately it means that - // the users must ensure they place `const` right at the beginning of - // their `#[builder(...)]` attributes. - let mut const_ = None; - - if let Some(config) = configs.first_mut() { - (const_, *config) = parse_const_prefix.parse2(std::mem::take(config))?; - } - + fn parse_for_any(configs: Vec) -> Result { let configs = configs .into_iter() .map(NestedMeta::parse_meta_list) @@ -188,10 +160,7 @@ impl TopLevelConfig { } } - let me = Self { - const_, - ..Self::from_list(&configs)? - }; + let me = Self::from_list(&configs)?; if let Some(on) = me.on.iter().skip(1).find(|on| on.required.is_present()) { bail!( diff --git a/bon/tests/integration/builder/attr_const.rs b/bon/tests/integration/builder/attr_const.rs index 260624dc..d2a137b7 100644 --- a/bon/tests/integration/builder/attr_const.rs +++ b/bon/tests/integration/builder/attr_const.rs @@ -9,7 +9,8 @@ mod msrv_1_61 { #[test] const fn test_struct() { #[derive(Builder)] - #[builder(const)] + // Make sure `const` is parsed if it's not the first attribute + #[builder(builder_type(vis = ""), const)] struct Sut { #[builder(start_fn)] x1: u32, From 1be3d0442ea483dd3dcb1f65036a529b8bf8d050 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 18 Jul 2025 18:13:49 +0000 Subject: [PATCH 2/2] Fix clippy --- bon-macros/src/builder/builder_gen/models.rs | 8 +++----- .../src/builder/builder_gen/top_level_config/mod.rs | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 5ffd28d0..fa9684c3 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -357,11 +357,9 @@ impl BuilderGenCtx { } }); - let const_ = if const_.is_present() { - Some(syn::Token![const](const_.span())) - } else { - None - }; + let const_ = const_ + .is_present() + .then(|| syn::Token![const](const_.span())); Ok(Self { bon, diff --git a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs index d5b993cf..24230bb3 100644 --- a/bon-macros/src/builder/builder_gen/top_level_config/mod.rs +++ b/bon-macros/src/builder/builder_gen/top_level_config/mod.rs @@ -43,7 +43,7 @@ fn parse_start_fn(meta: &syn::Meta) -> Result { #[derive(Debug, FromMeta)] pub(crate) struct TopLevelConfig { - /// Specifies whether the generated functions should be `co + /// Specifies whether the generated functions should be `const` #[darling(rename = "const")] pub(crate) const_: darling::util::Flag,