Skip to content

Commit

Permalink
Support name prefix configs for setters
Browse files Browse the repository at this point in the history
  • Loading branch information
Veetaha committed Dec 24, 2024
1 parent 5099fef commit 6d54b9e
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 42 deletions.
2 changes: 1 addition & 1 deletion bon-macros/src/builder/builder_gen/member/config/getter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down
6 changes: 3 additions & 3 deletions bon-macros/src/builder/builder_gen/member/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SettersConfig>,

/// Skip generating a setter method for this member.
Expand Down Expand Up @@ -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}",
);
}

Expand Down Expand Up @@ -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)]`",
);
Expand Down
132 changes: 112 additions & 20 deletions bon-macros/src/builder/builder_gen/member/config/setters.rs
Original file line number Diff line number Diff line change
@@ -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<SpannedKey<ItemSigConfig>> {
let params = ItemSigConfigParsing {
meta,
reject_self_mentions: Some(DOCS_CONTEXT),
}
.parse()?;
fn parse_docs(meta: &syn::Meta) -> Result<SpannedKey<Vec<syn::Attribute>>> {
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<SpannedKey<Vec<syn::Attribute>>> {
crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta)
impl SetterFnName {
fn new(
name: Option<SpannedKey<syn::Ident>>,
prefix: Option<SpannedKey<syn::Ident>>,
) -> Result<Option<SpannedKey<Self>>> {
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<SpannedKey<syn::Ident>>,
pub(crate) name: Option<SpannedKey<SetterFnName>>,
pub(crate) vis: Option<SpannedKey<syn::Visibility>>,

#[darling(rename = "doc", default, with = parse_docs, map = Some)]
pub(crate) docs: Option<SpannedKey<Vec<syn::Attribute>>>,

#[darling(flatten)]
pub(crate) fns: SettersFnsConfig,
}

impl FromMeta for SettersConfig {
fn from_meta(meta: &syn::Meta) -> Result<Self> {
#[derive(FromMeta)]
struct Parsed {
name: Option<SpannedKey<syn::Ident>>,
prefix: Option<SpannedKey<syn::Ident>>,

vis: Option<SpannedKey<syn::Visibility>>,

#[darling(rename = "doc", default, map = Some, with = parse_docs)]
docs: Option<SpannedKey<Vec<syn::Attribute>>>,

#[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<T>` 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<SpannedKey<ItemSigConfig>>,
pub(crate) some_fn: Option<SpannedKey<SetterFnSigConfig>>,

/// The setter that accepts the value of type `Option<T>` for a member of
/// type `Option<T>` or with `#[builder(default)]`.
///
/// By default, it's named `maybe_{member}`.
#[darling(default, with = parse_setter_fn, map = Some)]
pub(crate) option_fn: Option<SpannedKey<ItemSigConfig>>,
pub(crate) option_fn: Option<SpannedKey<SetterFnSigConfig>>,
}

#[derive(Debug, Default)]
pub(crate) struct SetterFnSigConfig {
pub(crate) name: Option<SpannedKey<SetterFnName>>,
pub(crate) vis: Option<SpannedKey<syn::Visibility>>,
pub(crate) docs: Option<SpannedKey<Vec<syn::Attribute>>>,
}

impl FromMeta for SetterFnSigConfig {
fn from_meta(meta: &syn::Meta) -> Result<Self> {
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<SpannedKey<syn::Ident>>,
prefix: Option<SpannedKey<syn::Ident>>,

vis: Option<SpannedKey<syn::Visibility>>,

#[darling(rename = "doc", default, map = Some, with = parse_docs)]
docs: Option<SpannedKey<Vec<syn::Attribute>>>,
}

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)
}
}
9 changes: 5 additions & 4 deletions bon-macros/src/builder/builder_gen/member/named.rs
Original file line number Diff line number Diff line change
@@ -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};

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / cargo-doc (ubuntu)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-unstable (beta)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_10_structs)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_5)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_20)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-stable (ubuntu)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-unstable (nightly)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / cargo-doc (macos)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-msrv (ubuntu)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-stable (macos)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-msrv (macos)

unused import: `ItemSigConfig`

Check warning on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / cargo-miri

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-msrv (windows)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / test-stable (windows)

unused import: `ItemSigConfig`

Check failure on line 7 in bon-macros/src/builder/builder_gen/member/named.rs

View workflow job for this annotation

GitHub Actions / cargo-doc (windows)

unused import: `ItemSigConfig`
Expand Down Expand Up @@ -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(_), .. })
)
)
})
Expand Down Expand Up @@ -183,9 +184,9 @@ impl NamedMember {
// Lint from nightly. `&Option<T>` is used to reduce syntax at the call site
#[allow(unknown_lints, clippy::ref_option)]
fn validate_unused_setters_cfg<T>(
overrides: &[&SpannedKey<ItemSigConfig>],
overrides: &[&SpannedKey<SetterFnSigConfig>],
config: &Option<SpannedKey<T>>,
get_val: impl Fn(&ItemSigConfig) -> &Option<SpannedKey<T>>,
get_val: impl Fn(&SetterFnSigConfig) -> &Option<SpannedKey<T>>,
) -> Result {
let config = match config {
Some(config) => config,
Expand Down
4 changes: 2 additions & 2 deletions bon-macros/src/builder/builder_gen/top_level_config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<OnConfig>,

/// 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,
}

Expand Down
43 changes: 35 additions & 8 deletions bon-macros/src/builder/builder_gen/top_level_config/on.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<syn::Ident>,

#[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<syn::Ident>,
}

impl Parse for OnConfig {
Expand All @@ -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<OnSettersConfig>,
}

let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -104,13 +129,15 @@ impl Parse for OnConfig {
into,
overwritable,
required,
setters,
} = parsed;

Ok(Self {
type_pattern,
into,
overwritable,
required,
setters: setters.unwrap_or_default(),
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion bon-macros/src/parsing/item_sig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl ItemSigConfigParsing<'_> {
doc: Option<SpannedKey<Vec<syn::Attribute>>>,
}

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 {
Expand Down
4 changes: 3 additions & 1 deletion bon-macros/src/parsing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: FromMeta>(meta: &syn::Meta) -> Result<T> {
pub(crate) fn parse_non_empty_paren_meta_list_or_name_value<T: FromMeta>(
meta: &syn::Meta,
) -> Result<T> {
require_non_empty_paren_meta_list_or_name_value(meta)?;
T::from_meta(meta)
}
Expand Down
4 changes: 2 additions & 2 deletions website/src/reference/builder/member/getter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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.

Expand Down

0 comments on commit 6d54b9e

Please sign in to comment.