From 6d54b9e727422d8d9a5dd035b0b5f334d9179da5 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 12 Dec 2024 23:08:54 +0000 Subject: [PATCH] Support name `prefix` configs for setters --- .../builder_gen/member/config/getter.rs | 2 +- .../builder/builder_gen/member/config/mod.rs | 6 +- .../builder_gen/member/config/setters.rs | 132 +++++++++++++++--- .../src/builder/builder_gen/member/named.rs | 9 +- .../builder_gen/top_level_config/mod.rs | 4 +- .../builder_gen/top_level_config/on.rs | 43 ++++-- bon-macros/src/parsing/item_sig.rs | 2 +- bon-macros/src/parsing/mod.rs | 4 +- .../src/reference/builder/member/getter.md | 4 +- 9 files changed, 164 insertions(+), 42 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/config/getter.rs b/bon-macros/src/builder/builder_gen/member/config/getter.rs index 474382f3..ab6258b7 100644 --- a/bon-macros/src/builder/builder_gen/member/config/getter.rs +++ b/bon-macros/src/builder/builder_gen/member/config/getter.rs @@ -70,7 +70,7 @@ impl FromMeta for GetterConfig { if let [kind1, kind2, ..] = kinds.as_slice() { bail!( &kind1.key, - "`{}` can't be specified together with `{}`", + "`{}` is mutually exclusive with `{}`", kind1.key, kind2.key ); 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 38f31634..cf81b55b 100644 --- a/bon-macros/src/builder/builder_gen/member/config/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/config/mod.rs @@ -67,7 +67,7 @@ pub(crate) struct MemberConfig { pub(crate) required: darling::util::Flag, /// Configurations for the setter methods. - #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)] + #[darling(with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)] pub(crate) setters: Option, /// 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` attribute; \ 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..c97588c4 100644 --- a/bon-macros/src/builder/builder_gen/member/config/setters.rs +++ b/bon-macros/src/builder/builder_gen/member/config/setters.rs @@ -1,48 +1,140 @@ -use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey}; +use crate::parsing::SpannedKey; use crate::util::prelude::*; use darling::FromMeta; 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()?; +fn parse_docs(meta: &syn::Meta) -> Result>> { + crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta) +} - SpannedKey::new(meta.path(), params) +#[derive(Debug)] +pub(crate) enum SetterFnName { + Name(syn::Ident), + Prefix(syn::Ident), } -fn parse_docs(meta: &syn::Meta) -> Result>> { - crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta) +impl SetterFnName { + fn new( + name: Option>, + prefix: Option>, + ) -> Result>> { + match (name, prefix) { + (Some(name), None) => Ok(Some(name.map_value(SetterFnName::Name))), + (None, Some(prefix)) => Ok(Some(prefix.map_value(SetterFnName::Prefix))), + (None, None) => Ok(None), + (Some(name), Some(prefix)) => { + bail!( + &name.key, + "`{}` is mutually exclusive with `{}`", + name.key, + prefix.key, + ); + } + } + } } -#[derive(Debug, FromMeta)] +#[derive(Debug)] pub(crate) struct SettersConfig { - pub(crate) name: Option>, + pub(crate) name: Option>, pub(crate) vis: Option>, - - #[darling(rename = "doc", default, with = parse_docs, map = Some)] pub(crate) docs: Option>>, - - #[darling(flatten)] pub(crate) fns: SettersFnsConfig, } +impl FromMeta for SettersConfig { + fn from_meta(meta: &syn::Meta) -> Result { + #[derive(FromMeta)] + struct Parsed { + name: Option>, + prefix: Option>, + + vis: Option>, + + #[darling(rename = "doc", default, map = Some, with = parse_docs)] + docs: Option>>, + + #[darling(flatten)] + fns: SettersFnsConfig, + } + + let Parsed { + name, + prefix, + vis, + docs, + fns, + } = Parsed::from_meta(meta)?; + + Ok(SettersConfig { + name: SetterFnName::new(name, prefix)?, + vis, + docs, + fns, + }) + } +} + #[derive(Debug, FromMeta)] pub(crate) struct SettersFnsConfig { /// Config for the setter that accepts the value of type T for a member of /// 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) 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, SetterFnName::Name(name))?), + vis: None, + docs: None, + }); + } + + #[derive(Debug, FromMeta)] + struct Full { + name: Option>, + prefix: Option>, + + vis: Option>, + + #[darling(rename = "doc", default, map = Some, with = parse_docs)] + docs: Option>>, + } + + let Full { + name, + prefix, + vis, + docs, + } = crate::parsing::parse_non_empty_paren_meta_list_or_name_value(meta)?; + + let config = SetterFnSigConfig { + name: SetterFnName::new(name, prefix)?, + vis, + docs, + }; + + Ok(config) + } } 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..27a4b0b8 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_non_empty_paren_meta_list_or_name_value)] 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_non_empty_paren_meta_list_or_name_value)] 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..b202cf24 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_non_empty_paren_meta_list_or_name_value)] + pub(crate) some_fn: OnSetterFnConfig, + + #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)] + 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_non_empty_paren_meta_list_or_name_value)] + 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/parsing/item_sig.rs b/bon-macros/src/parsing/item_sig.rs index 7288ebde..65b68f29 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_non_empty_paren_meta_list_or_name_value(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..2c7c5273 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -14,7 +14,9 @@ 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 { +pub(crate) fn parse_non_empty_paren_meta_list_or_name_value( + meta: &syn::Meta, +) -> Result { require_non_empty_paren_meta_list_or_name_value(meta)?; T::from_meta(meta) } 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.