Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions bon-macros/src/builder/builder_gen/input_fn/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
2 changes: 1 addition & 1 deletion bon-macros/src/builder/builder_gen/member/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
}

Expand Down
6 changes: 5 additions & 1 deletion bon-macros/src/builder/builder_gen/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ pub(super) struct BuilderGenCtxParams<'a> {
pub(super) members: Vec<Member>,

pub(super) allow_attrs: Vec<syn::Attribute>,
pub(super) const_: Option<syn::Token![const]>,
pub(super) const_: darling::util::Flag,
pub(super) on: Vec<OnConfig>,

/// This is the visibility of the original item that the builder is generated for.
Expand Down Expand Up @@ -357,6 +357,10 @@ impl BuilderGenCtx {
}
});

let const_ = const_
.is_present()
.then(|| syn::Token![const](const_.span()));

Ok(Self {
bon,
state_var,
Expand Down
41 changes: 5 additions & 36 deletions bon-macros/src/builder/builder_gen/top_level_config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -44,13 +43,9 @@ fn parse_start_fn(meta: &syn::Meta) -> Result<ItemSigConfig> {

#[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<syn::Token![const]>,
/// Specifies whether the generated functions should be `const`
#[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`.
Expand Down Expand Up @@ -129,30 +124,7 @@ impl TopLevelConfig {
Self::parse_for_any(configs)
}

fn parse_for_any(mut configs: Vec<TokenStream>) -> Result<Self> {
fn parse_const_prefix(
parse: syn::parse::ParseStream<'_>,
) -> syn::Result<(Option<syn::Token![const]>, TokenStream)> {
let const_ = parse.parse().ok();
if const_.is_some() && !parse.is_empty() {
parse.parse::<syn::Token![,]>()?;
}

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<TokenStream>) -> Result<Self> {
let configs = configs
.into_iter()
.map(NestedMeta::parse_meta_list)
Expand Down Expand Up @@ -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!(
Expand Down
3 changes: 2 additions & 1 deletion bon/tests/integration/builder/attr_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading