Skip to content

Commit aadc00a

Browse files
committed
Support name prefix configs for setters
1 parent 7a4b211 commit aadc00a

File tree

9 files changed

+164
-42
lines changed

9 files changed

+164
-42
lines changed

bon-macros/src/builder/builder_gen/member/config/getter.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl FromMeta for GetterConfig {
7070
if let [kind1, kind2, ..] = kinds.as_slice() {
7171
bail!(
7272
&kind1.key,
73-
"`{}` can't be specified together with `{}`",
73+
"`{}` is mutually exclusive with `{}`",
7474
kind1.key,
7575
kind2.key
7676
);

bon-macros/src/builder/builder_gen/member/config/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub(crate) struct MemberConfig {
6767
pub(crate) required: darling::util::Flag,
6868

6969
/// Configurations for the setter methods.
70-
#[darling(with = crate::parsing::parse_non_empty_paren_meta_list)]
70+
#[darling(with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)]
7171
pub(crate) setters: Option<SettersConfig>,
7272

7373
/// Skip generating a setter method for this member.
@@ -166,7 +166,7 @@ impl MemberConfig {
166166

167167
bail!(
168168
&attr_span,
169-
"`{attr_name}` attribute can't be specified together with {conflicting}",
169+
"`{attr_name}` is mutually exclusive with {conflicting}",
170170
);
171171
}
172172

@@ -278,7 +278,7 @@ impl MemberConfig {
278278
if let Some(Some(_expr)) = self.default.as_deref() {
279279
bail!(
280280
&skip.key.span(),
281-
"`skip` attribute can't be specified with the `default` attribute; \
281+
"`skip` is mutually exclusive with `default` attribute; \
282282
if you wanted to specify a value for the member, then use \
283283
the following syntax instead `#[builder(skip = value)]`",
284284
);
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,140 @@
1-
use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey};
1+
use crate::parsing::SpannedKey;
22
use crate::util::prelude::*;
33
use darling::FromMeta;
44

55
const DOCS_CONTEXT: &str = "builder struct's impl block";
66

7-
fn parse_setter_fn(meta: &syn::Meta) -> Result<SpannedKey<ItemSigConfig>> {
8-
let params = ItemSigConfigParsing {
9-
meta,
10-
reject_self_mentions: Some(DOCS_CONTEXT),
11-
}
12-
.parse()?;
7+
fn parse_docs(meta: &syn::Meta) -> Result<SpannedKey<Vec<syn::Attribute>>> {
8+
crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta)
9+
}
1310

14-
SpannedKey::new(meta.path(), params)
11+
#[derive(Debug)]
12+
pub(crate) enum SetterFnName {
13+
Name(syn::Ident),
14+
Prefix(syn::Ident),
1515
}
1616

17-
fn parse_docs(meta: &syn::Meta) -> Result<SpannedKey<Vec<syn::Attribute>>> {
18-
crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta)
17+
impl SetterFnName {
18+
fn new(
19+
name: Option<SpannedKey<syn::Ident>>,
20+
prefix: Option<SpannedKey<syn::Ident>>,
21+
) -> Result<Option<SpannedKey<Self>>> {
22+
match (name, prefix) {
23+
(Some(name), None) => Ok(Some(name.map_value(SetterFnName::Name))),
24+
(None, Some(prefix)) => Ok(Some(prefix.map_value(SetterFnName::Prefix))),
25+
(None, None) => Ok(None),
26+
(Some(name), Some(prefix)) => {
27+
bail!(
28+
&name.key,
29+
"`{}` is mutually exclusive with `{}`",
30+
name.key,
31+
prefix.key,
32+
);
33+
}
34+
}
35+
}
1936
}
2037

21-
#[derive(Debug, FromMeta)]
38+
#[derive(Debug)]
2239
pub(crate) struct SettersConfig {
23-
pub(crate) name: Option<SpannedKey<syn::Ident>>,
40+
pub(crate) name: Option<SpannedKey<SetterFnName>>,
2441
pub(crate) vis: Option<SpannedKey<syn::Visibility>>,
25-
26-
#[darling(rename = "doc", default, with = parse_docs, map = Some)]
2742
pub(crate) docs: Option<SpannedKey<Vec<syn::Attribute>>>,
28-
29-
#[darling(flatten)]
3043
pub(crate) fns: SettersFnsConfig,
3144
}
3245

46+
impl FromMeta for SettersConfig {
47+
fn from_meta(meta: &syn::Meta) -> Result<Self> {
48+
#[derive(FromMeta)]
49+
struct Parsed {
50+
name: Option<SpannedKey<syn::Ident>>,
51+
prefix: Option<SpannedKey<syn::Ident>>,
52+
53+
vis: Option<SpannedKey<syn::Visibility>>,
54+
55+
#[darling(rename = "doc", default, map = Some, with = parse_docs)]
56+
docs: Option<SpannedKey<Vec<syn::Attribute>>>,
57+
58+
#[darling(flatten)]
59+
fns: SettersFnsConfig,
60+
}
61+
62+
let Parsed {
63+
name,
64+
prefix,
65+
vis,
66+
docs,
67+
fns,
68+
} = Parsed::from_meta(meta)?;
69+
70+
Ok(SettersConfig {
71+
name: SetterFnName::new(name, prefix)?,
72+
vis,
73+
docs,
74+
fns,
75+
})
76+
}
77+
}
78+
3379
#[derive(Debug, FromMeta)]
3480
pub(crate) struct SettersFnsConfig {
3581
/// Config for the setter that accepts the value of type T for a member of
3682
/// type `Option<T>` or with `#[builder(default)]`.
3783
///
3884
/// By default, it's named `{member}` without any prefix or suffix.
39-
#[darling(default, with = parse_setter_fn, map = Some)]
40-
pub(crate) some_fn: Option<SpannedKey<ItemSigConfig>>,
85+
pub(crate) some_fn: Option<SpannedKey<SetterFnSigConfig>>,
4186

4287
/// The setter that accepts the value of type `Option<T>` for a member of
4388
/// type `Option<T>` or with `#[builder(default)]`.
4489
///
4590
/// By default, it's named `maybe_{member}`.
46-
#[darling(default, with = parse_setter_fn, map = Some)]
47-
pub(crate) option_fn: Option<SpannedKey<ItemSigConfig>>,
91+
pub(crate) option_fn: Option<SpannedKey<SetterFnSigConfig>>,
92+
}
93+
94+
#[derive(Debug, Default)]
95+
pub(crate) struct SetterFnSigConfig {
96+
pub(crate) name: Option<SpannedKey<SetterFnName>>,
97+
pub(crate) vis: Option<SpannedKey<syn::Visibility>>,
98+
pub(crate) docs: Option<SpannedKey<Vec<syn::Attribute>>>,
99+
}
100+
101+
impl FromMeta for SetterFnSigConfig {
102+
fn from_meta(meta: &syn::Meta) -> Result<Self> {
103+
if let syn::Meta::NameValue(meta) = meta {
104+
let val = &meta.value;
105+
let name = syn::parse2(val.to_token_stream())?;
106+
107+
return Ok(SetterFnSigConfig {
108+
name: Some(SpannedKey::new(&meta.path, SetterFnName::Name(name))?),
109+
vis: None,
110+
docs: None,
111+
});
112+
}
113+
114+
#[derive(Debug, FromMeta)]
115+
struct Full {
116+
name: Option<SpannedKey<syn::Ident>>,
117+
prefix: Option<SpannedKey<syn::Ident>>,
118+
119+
vis: Option<SpannedKey<syn::Visibility>>,
120+
121+
#[darling(rename = "doc", default, map = Some, with = parse_docs)]
122+
docs: Option<SpannedKey<Vec<syn::Attribute>>>,
123+
}
124+
125+
let Full {
126+
name,
127+
prefix,
128+
vis,
129+
docs,
130+
} = crate::parsing::parse_non_empty_paren_meta_list_or_name_value(meta)?;
131+
132+
let config = SetterFnSigConfig {
133+
name: SetterFnName::new(name, prefix)?,
134+
vis,
135+
docs,
136+
};
137+
138+
Ok(config)
139+
}
48140
}

bon-macros/src/builder/builder_gen/member/named.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::config::MemberConfig;
22
use super::{config, MemberOrigin};
33
use crate::builder::builder_gen::member::config::SettersFnsConfig;
4+
use crate::builder::builder_gen::member::SetterFnSigConfig;
45
use crate::builder::builder_gen::top_level_config::OnConfig;
56
use crate::normalization::SyntaxVariant;
67
use crate::parsing::{ItemSigConfig, SpannedKey};
@@ -117,8 +118,8 @@ impl NamedMember {
117118
matches!(
118119
(some_fn.as_deref(), option_fn.as_deref()),
119120
(
120-
Some(ItemSigConfig { docs: Some(_), .. }),
121-
Some(ItemSigConfig { docs: Some(_), .. })
121+
Some(SetterFnSigConfig { docs: Some(_), .. }),
122+
Some(SetterFnSigConfig { docs: Some(_), .. })
122123
)
123124
)
124125
})
@@ -183,9 +184,9 @@ impl NamedMember {
183184
// Lint from nightly. `&Option<T>` is used to reduce syntax at the call site
184185
#[allow(unknown_lints, clippy::ref_option)]
185186
fn validate_unused_setters_cfg<T>(
186-
overrides: &[&SpannedKey<ItemSigConfig>],
187+
overrides: &[&SpannedKey<SetterFnSigConfig>],
187188
config: &Option<SpannedKey<T>>,
188-
get_val: impl Fn(&ItemSigConfig) -> &Option<SpannedKey<T>>,
189+
get_val: impl Fn(&SetterFnSigConfig) -> &Option<SpannedKey<T>>,
189190
) -> Result {
190191
let config = match config {
191192
Some(config) => config,

bon-macros/src/builder/builder_gen/top_level_config/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ pub(crate) struct TopLevelConfig {
5858
#[darling(default, with = parse_state_mod)]
5959
pub(crate) state_mod: ItemSigConfig,
6060

61-
#[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)]
61+
#[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)]
6262
pub(crate) on: Vec<OnConfig>,
6363

6464
/// Specifies the derives to apply to the builder.
65-
#[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)]
65+
#[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)]
6666
pub(crate) derive: DerivesConfig,
6767
}
6868

bon-macros/src/builder/builder_gen/top_level_config/on.rs

+35-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ pub(crate) struct OnConfig {
1010
pub(crate) into: darling::util::Flag,
1111
pub(crate) overwritable: darling::util::Flag,
1212
pub(crate) required: darling::util::Flag,
13+
pub(crate) setters: OnSettersConfig,
14+
}
15+
16+
#[derive(Default, Debug, FromMeta)]
17+
pub(crate) struct OnSettersConfig {
18+
pub(crate) prefix: Option<syn::Ident>,
19+
20+
#[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)]
21+
pub(crate) some_fn: OnSetterFnConfig,
22+
23+
#[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)]
24+
pub(crate) option_fn: OnSetterFnConfig,
25+
}
26+
27+
#[derive(Default, Debug, FromMeta)]
28+
pub(crate) struct OnSetterFnConfig {
29+
pub(crate) prefix: Option<syn::Ident>,
1330
}
1431

1532
impl Parse for OnConfig {
@@ -24,6 +41,9 @@ impl Parse for OnConfig {
2441
into: darling::util::Flag,
2542
overwritable: darling::util::Flag,
2643
required: darling::util::Flag,
44+
45+
#[darling(default, map = Some, with = crate::parsing::parse_non_empty_paren_meta_list_or_name_value)]
46+
setters: Option<OnSettersConfig>,
2747
}
2848

2949
let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?;
@@ -51,19 +71,24 @@ impl Parse for OnConfig {
5171
into,
5272
overwritable,
5373
required,
74+
setters,
5475
} = &parsed;
55-
let flags = [
56-
("into", into),
57-
("overwritable", overwritable),
58-
("required", required),
76+
let configs = [
77+
("into", into.is_present()),
78+
("overwritable", overwritable.is_present()),
79+
("required", required.is_present()),
80+
("setters", setters.is_some()),
5981
];
6082

61-
if flags.iter().all(|(_, flag)| !flag.is_present()) {
62-
let flags = flags.iter().map(|(name, _)| format!("`{name}`")).join(", ");
83+
if configs.iter().all(|(_, is_present)| !is_present) {
84+
let configs = configs
85+
.iter()
86+
.map(|(name, _)| format!("`{name}`"))
87+
.join(", ");
6388
let err = format!(
6489
"this #[builder(on(type_pattern, ...))] contains no options \
65-
to override the default behavior for the selected setters \
66-
like {flags}, so it does nothing"
90+
to override the default behavior for the selected members \
91+
like {configs}, so it does nothing"
6792
);
6893

6994
return Err(syn::Error::new_spanned(&rest, err));
@@ -104,13 +129,15 @@ impl Parse for OnConfig {
104129
into,
105130
overwritable,
106131
required,
132+
setters,
107133
} = parsed;
108134

109135
Ok(Self {
110136
type_pattern,
111137
into,
112138
overwritable,
113139
required,
140+
setters: setters.unwrap_or_default(),
114141
})
115142
}
116143
}

bon-macros/src/parsing/item_sig.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl ItemSigConfigParsing<'_> {
5555
doc: Option<SpannedKey<Vec<syn::Attribute>>>,
5656
}
5757

58-
let full: Full = crate::parsing::parse_non_empty_paren_meta_list(meta)?;
58+
let full: Full = crate::parsing::parse_non_empty_paren_meta_list_or_name_value(meta)?;
5959

6060
if let Some(context) = self.reject_self_mentions {
6161
if let Some(docs) = &full.doc {

bon-macros/src/parsing/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use syn::parse::Parser;
1414
use syn::punctuated::Punctuated;
1515
use syn::spanned::Spanned;
1616

17-
pub(crate) fn parse_non_empty_paren_meta_list<T: FromMeta>(meta: &syn::Meta) -> Result<T> {
17+
pub(crate) fn parse_non_empty_paren_meta_list_or_name_value<T: FromMeta>(
18+
meta: &syn::Meta,
19+
) -> Result<T> {
1820
require_non_empty_paren_meta_list_or_name_value(meta)?;
1921
T::from_meta(meta)
2022
}

website/src/reference/builder/member/getter.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ You can override the return type of the getter, its name, visibility, and docs.
119119
deref,
120120
121121
// Return the type specified in parens.
122-
// A deref coercion is expected to be valid to the specified type.
122+
// A deref coercion is expected to exist to the specified type.
123123
// Don't specify the leading `&` here.
124124
deref(T),
125125
@@ -132,7 +132,7 @@ You can override the return type of the getter, its name, visibility, and docs.
132132
)]
133133
```
134134

135-
## Overriding the return type
135+
## Overriding the Return Type
136136

137137
Here is an example of different return type configurations and what they generate.
138138

0 commit comments

Comments
 (0)