diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 95174c1c..d5c9a6b7 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -29,7 +29,7 @@ fn parse_top_level_config(item_struct: &syn::ItemStruct) -> Result, /// Skip generating a setter method for this member. @@ -166,7 +166,7 @@ impl MemberConfig { bail!( &attr_span, - "`{attr_name}` attribute can't be specified together with {conflicting}", + "`{attr_name}` is mutually exclusive with {conflicting}", ); } @@ -278,7 +278,7 @@ impl MemberConfig { if let Some(Some(_expr)) = self.default.as_deref() { bail!( &skip.key.span(), - "`skip` attribute can't be specified with the `default` attribute; \ + "`skip` is mutually exclusive with `default`; \ if you wanted to specify a value for the member, then use \ the following syntax instead `#[builder(skip = value)]`", ); diff --git a/bon-macros/src/builder/builder_gen/member/config/setters.rs b/bon-macros/src/builder/builder_gen/member/config/setters.rs index 72a9c1c2..49453da3 100644 --- a/bon-macros/src/builder/builder_gen/member/config/setters.rs +++ b/bon-macros/src/builder/builder_gen/member/config/setters.rs @@ -1,33 +1,29 @@ -use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey}; +use crate::parsing::{reject_syntax, SpannedKey}; use crate::util::prelude::*; +use darling::util::SpannedValue; use darling::FromMeta; +use syn::spanned::Spanned; const DOCS_CONTEXT: &str = "builder struct's impl block"; -fn parse_setter_fn(meta: &syn::Meta) -> Result> { - let params = ItemSigConfigParsing { - meta, - reject_self_mentions: Some(DOCS_CONTEXT), - } - .parse()?; - - SpannedKey::new(meta.path(), params) -} - fn parse_docs(meta: &syn::Meta) -> Result>> { crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta) } #[derive(Debug, FromMeta)] pub(crate) struct SettersConfig { - pub(crate) name: Option>, - pub(crate) vis: Option>, + name: Option>, - #[darling(rename = "doc", default, with = parse_docs, map = Some)] - pub(crate) docs: Option>>, + #[darling(default, map = Some, with = parse_ident_or_str_lit)] + prefix: Option>>, + + vis: Option>, + + #[darling(rename = "doc", default, map = Some, with = parse_docs)] + docs: Option>>, #[darling(flatten)] - pub(crate) fns: SettersFnsConfig, + fns: SettersFnsConfig, } #[derive(Debug, FromMeta)] @@ -36,13 +32,94 @@ pub(crate) struct SettersFnsConfig { /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `{member}` without any prefix or suffix. - #[darling(default, with = parse_setter_fn, map = Some)] - pub(crate) some_fn: Option>, + pub(crate) some_fn: Option>, /// The setter that accepts the value of type `Option` for a member of /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `maybe_{member}`. - #[darling(default, with = parse_setter_fn, map = Some)] - pub(crate) option_fn: Option>, + pub(crate) option_fn: Option>, +} + +#[derive(Debug, Default)] +pub(crate) struct SetterFnSigConfig { + pub(crate) name: Option>, + pub(crate) prefix: Option>>, + pub(crate) vis: Option>, + pub(crate) docs: Option>>, +} + +impl FromMeta for SetterFnSigConfig { + fn from_meta(meta: &syn::Meta) -> Result { + if let syn::Meta::NameValue(meta) = meta { + let val = &meta.value; + let name = syn::parse2(val.to_token_stream())?; + + return Ok(SetterFnSigConfig { + name: Some(SpannedKey::new(&meta.path, name)?), + prefix: None, + vis: None, + docs: None, + }); + } + + #[derive(Debug, FromMeta)] + struct Full { + name: Option>, + + #[darling(default, map = Some, with = parse_ident_or_str_lit)] + prefix: Option>>, + + vis: Option>, + + #[darling(rename = "doc", default, map = Some, with = parse_docs)] + docs: Option>>, + } + + let Full { + name, + prefix, + vis, + docs, + } = crate::parsing::parse_classic_non_empty(meta)?; + + let config = SetterFnSigConfig { + name, + prefix, + vis, + docs, + }; + + Ok(config) + } +} + +fn parse_ident_or_str_lit(meta: &syn::Meta) -> Result>> { + let expr = darling::util::parse_expr::preserve_str_literal(meta)?; + let value = match &expr { + syn::Expr::Lit(syn::ExprLit { + attrs, + lit: syn::Lit::Str(str), + .. + }) => { + reject_syntax("attribute", &attrs.first())?; + str.value() + } + + syn::Expr::Path(syn::ExprPath { + attrs, qself, path, .. + }) => { + reject_syntax("attribute", &attrs.first())?; + reject_syntax(" syntax", qself)?; + path.get_ident() + .ok_or_else(|| err!(&path, "expected an identifier"))? + .to_string() + } + + _ => bail!(&expr, "expected an indetifier or a string literal",), + }; + + let value = SpannedValue::new(value, expr.span()); + + SpannedKey::new(meta.path(), value) } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 69cc80c1..18f1d4c1 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -121,9 +121,7 @@ impl Member { .map(|member| { for attr in member.attrs { if attr.meta.path().is_ident("builder") { - crate::parsing::require_non_empty_paren_meta_list_or_name_value( - &attr.meta, - )?; + crate::parsing::require_classic_non_empty(&attr.meta)?; } } diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 215926ef..621563c9 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,6 +1,7 @@ use super::config::MemberConfig; use super::{config, MemberOrigin}; use crate::builder::builder_gen::member::config::SettersFnsConfig; +use crate::builder::builder_gen::member::SetterFnSigConfig; use crate::builder::builder_gen::top_level_config::OnConfig; use crate::normalization::SyntaxVariant; use crate::parsing::{ItemSigConfig, SpannedKey}; @@ -117,8 +118,8 @@ impl NamedMember { matches!( (some_fn.as_deref(), option_fn.as_deref()), ( - Some(ItemSigConfig { docs: Some(_), .. }), - Some(ItemSigConfig { docs: Some(_), .. }) + Some(SetterFnSigConfig { docs: Some(_), .. }), + Some(SetterFnSigConfig { docs: Some(_), .. }) ) ) }) @@ -183,9 +184,9 @@ impl NamedMember { // Lint from nightly. `&Option` is used to reduce syntax at the call site #[allow(unknown_lints, clippy::ref_option)] fn validate_unused_setters_cfg( - overrides: &[&SpannedKey], + overrides: &[&SpannedKey], config: &Option>, - get_val: impl Fn(&ItemSigConfig) -> &Option>, + get_val: impl Fn(&SetterFnSigConfig) -> &Option>, ) -> Result { let config = match config { Some(config) => config, 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 2b755938..0ffa1cc6 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 @@ -58,11 +58,11 @@ pub(crate) struct TopLevelConfig { #[darling(default, with = parse_state_mod)] pub(crate) state_mod: ItemSigConfig, - #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)] + #[darling(multiple, with = crate::parsing::parse_classic_non_empty)] pub(crate) on: Vec, /// Specifies the derives to apply to the builder. - #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] + #[darling(default, with = crate::parsing::parse_classic_non_empty)] pub(crate) derive: DerivesConfig, } diff --git a/bon-macros/src/builder/builder_gen/top_level_config/on.rs b/bon-macros/src/builder/builder_gen/top_level_config/on.rs index 6fb598b6..be76ce43 100644 --- a/bon-macros/src/builder/builder_gen/top_level_config/on.rs +++ b/bon-macros/src/builder/builder_gen/top_level_config/on.rs @@ -10,6 +10,23 @@ pub(crate) struct OnConfig { pub(crate) into: darling::util::Flag, pub(crate) overwritable: darling::util::Flag, pub(crate) required: darling::util::Flag, + pub(crate) setters: OnSettersConfig, +} + +#[derive(Default, Debug, FromMeta)] +pub(crate) struct OnSettersConfig { + pub(crate) prefix: Option, + + #[darling(default, with = crate::parsing::parse_classic_non_empty)] + pub(crate) some_fn: OnSetterFnConfig, + + #[darling(default, with = crate::parsing::parse_classic_non_empty)] + pub(crate) option_fn: OnSetterFnConfig, +} + +#[derive(Default, Debug, FromMeta)] +pub(crate) struct OnSetterFnConfig { + pub(crate) prefix: Option, } impl Parse for OnConfig { @@ -24,6 +41,9 @@ impl Parse for OnConfig { into: darling::util::Flag, overwritable: darling::util::Flag, required: darling::util::Flag, + + #[darling(default, map = Some, with = crate::parsing::parse_classic_non_empty)] + setters: Option, } let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?; @@ -51,19 +71,24 @@ impl Parse for OnConfig { into, overwritable, required, + setters, } = &parsed; - let flags = [ - ("into", into), - ("overwritable", overwritable), - ("required", required), + let configs = [ + ("into", into.is_present()), + ("overwritable", overwritable.is_present()), + ("required", required.is_present()), + ("setters", setters.is_some()), ]; - if flags.iter().all(|(_, flag)| !flag.is_present()) { - let flags = flags.iter().map(|(name, _)| format!("`{name}`")).join(", "); + if configs.iter().all(|(_, is_present)| !is_present) { + let configs = configs + .iter() + .map(|(name, _)| format!("`{name}`")) + .join(", "); let err = format!( "this #[builder(on(type_pattern, ...))] contains no options \ - to override the default behavior for the selected setters \ - like {flags}, so it does nothing" + to override the default behavior for the selected members \ + like {configs}, so it does nothing" ); return Err(syn::Error::new_spanned(&rest, err)); @@ -104,6 +129,7 @@ impl Parse for OnConfig { into, overwritable, required, + setters, } = parsed; Ok(Self { @@ -111,6 +137,7 @@ impl Parse for OnConfig { into, overwritable, required, + setters: setters.unwrap_or_default(), }) } } diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 7740f663..a3793030 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -116,9 +116,7 @@ pub(crate) fn generate( .filter(|attr| attr.path().is_ident("builder")) .map(|attr| { if let syn::Meta::List(_) = attr.meta { - crate::parsing::require_non_empty_paren_meta_list_or_name_value( - &attr.meta, - )?; + crate::parsing::require_classic_non_empty(&attr.meta)?; } let meta_list = darling::util::parse_attribute_to_meta_list(attr)?; NestedMeta::parse_meta_list(meta_list.tokens).map_err(Into::into) diff --git a/bon-macros/src/parsing/item_sig.rs b/bon-macros/src/parsing/item_sig.rs index 7288ebde..647e44b2 100644 --- a/bon-macros/src/parsing/item_sig.rs +++ b/bon-macros/src/parsing/item_sig.rs @@ -55,7 +55,7 @@ impl ItemSigConfigParsing<'_> { doc: Option>>, } - let full: Full = crate::parsing::parse_non_empty_paren_meta_list(meta)?; + let full: Full = crate::parsing::parse_classic_non_empty(meta)?; if let Some(context) = self.reject_self_mentions { if let Some(docs) = &full.doc { diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index 15788d18..494646ca 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -14,12 +14,12 @@ use syn::parse::Parser; use syn::punctuated::Punctuated; use syn::spanned::Spanned; -pub(crate) fn parse_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { - require_non_empty_paren_meta_list_or_name_value(meta)?; +pub(crate) fn parse_classic_non_empty(meta: &syn::Meta) -> Result { + require_classic_non_empty(meta)?; T::from_meta(meta) } -pub(crate) fn require_non_empty_paren_meta_list_or_name_value(meta: &syn::Meta) -> Result { +pub(crate) fn require_classic_non_empty(meta: &syn::Meta) -> Result { match meta { syn::Meta::List(meta) => { meta.require_parens_delim()?; diff --git a/website/src/reference/builder/member/getter.md b/website/src/reference/builder/member/getter.md index 45f66810..4083f60f 100644 --- a/website/src/reference/builder/member/getter.md +++ b/website/src/reference/builder/member/getter.md @@ -119,7 +119,7 @@ You can override the return type of the getter, its name, visibility, and docs. deref, // Return the type specified in parens. - // A deref coercion is expected to be valid to the specified type. + // A deref coercion is expected to exist to the specified type. // Don't specify the leading `&` here. deref(T), @@ -132,7 +132,7 @@ You can override the return type of the getter, its name, visibility, and docs. )] ``` -## Overriding the return type +## Overriding the Return Type Here is an example of different return type configurations and what they generate.