Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support #[builder(getter(...))] attribute #222

Merged
merged 18 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use crate::util::prelude::Result;

pub(crate) const DOCS_CONTEXT: &str = "builder struct's impl block";

pub(crate) fn parse_docs(meta: &syn::Meta) -> Result<super::SpannedKey<Vec<syn::Attribute>>> {
crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta)
}
Copy link
Collaborator

@Veetaha Veetaha Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to extract this module. It's okay to just copy this small function into the getter module. The DOCS_CONTEXT isn't meant to be a cross-module-shared variable with such a generic name, it's meant to be module-local and describe the context where the docs will be pasted for the config param defined in that module

99 changes: 99 additions & 0 deletions bon-macros/src/builder/builder_gen/member/config/getter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::ops::Deref;

use darling::FromMeta;
use proc_macro2::Span;
use syn::spanned::Spanned;

use super::{docs_utils::parse_docs, SpannedKey};

/// Wrapper around the getter config that allows it to work as both a flag and
/// a config.
#[derive(Debug)]
pub(crate) struct OptionalGetterConfig {
span: Span,
getter_config: Option<GetterConfig>,
}
lazkindness marked this conversation as resolved.
Show resolved Hide resolved

impl OptionalGetterConfig {
pub(crate) fn span(&self) -> Span {
self.span
}
}

impl FromMeta for OptionalGetterConfig {
fn from_none() -> Option<Self> {
Some(Self {
span: Span::call_site(),
getter_config: None,
})
}

fn from_meta(mi: &syn::Meta) -> darling::Result<Self> {
GetterConfig::from_meta(mi).map(|getter_config| Self {
span: mi.span(),
getter_config: Some(getter_config),
})
}
}

impl Deref for OptionalGetterConfig {
type Target = Option<GetterConfig>;

fn deref(&self) -> &Self::Target {
&self.getter_config
}
}

#[derive(Debug)]
pub(crate) enum GetterConfig {
#[allow(unused)]
Inferred,
Copy link
Collaborator

@Veetaha Veetaha Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably clarify what Inferred means and why it's unused in a comment. Although see #222 (comment)

Specified(SpecifiedGetterConfig),
}

impl FromMeta for GetterConfig {
fn from_none() -> Option<Self> {
None
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already the default implementation of this trait method in FromMeta?


fn from_meta(mi: &syn::Meta) -> darling::Result<Self> {
mi.span();
lazkindness marked this conversation as resolved.
Show resolved Hide resolved
if let syn::Meta::Path(_) = mi {
Ok(Self::Inferred)
} else {
SpecifiedGetterConfig::from_meta(mi).map(Self::Specified)
}
}
}

impl GetterConfig {
pub(crate) fn name(&self) -> Option<&syn::Ident> {
match self {
Self::Inferred => None,
Self::Specified(config) => config.name.as_ref().map(|n| &n.value),
}
}

pub(crate) fn vis(&self) -> Option<&syn::Visibility> {
match self {
Self::Inferred => None,
Self::Specified(config) => config.vis.as_ref().map(|v| &v.value),
}
}

pub(crate) fn docs(&self) -> Option<&[syn::Attribute]> {
match self {
Self::Inferred => None,
Self::Specified(config) => config.docs.as_ref().map(|a| &a.value).map(|a| &**a),
}
}
}

#[derive(Debug, FromMeta)]
pub(crate) struct SpecifiedGetterConfig {
name: Option<SpannedKey<syn::Ident>>,
vis: Option<SpannedKey<syn::Visibility>>,

#[darling(rename = "doc", default, with = parse_docs, map = Some)]

Check warning on line 97 in bon-macros/src/builder/builder_gen/member/config/getter.rs

View workflow job for this annotation

GitHub Actions / cargo-miri

`if let` assigns a shorter lifetime since Edition 2024
docs: Option<SpannedKey<Vec<syn::Attribute>>>,
}
33 changes: 31 additions & 2 deletions bon-macros/src/builder/builder_gen/member/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
mod blanket;
mod docs_utils;
mod getter;
mod setters;
mod with;

pub(crate) use blanket::*;
pub(crate) use getter::*;
pub(crate) use setters::*;
pub(crate) use with::*;

Expand Down Expand Up @@ -33,6 +36,12 @@ pub(crate) struct MemberConfig {
#[darling(with = parse_optional_expr, map = Some)]
pub(crate) field: Option<SpannedKey<Option<syn::Expr>>>,

/// Make the member gettable by reference.
///
/// This takes the same attributes as the setter fns; `name`, `vis`, and `doc`
/// and produces a getter method that returns `&T` for the member.
pub(crate) getter: OptionalGetterConfig,

/// Accept the value for the member in the finishing function parameters.
pub(crate) finish_fn: darling::util::Flag,

Expand Down Expand Up @@ -82,6 +91,7 @@ pub(crate) struct MemberConfig {
enum ParamName {
Default,
Field,
Getter,
FinishFn,
Into,
Name,
Expand All @@ -98,6 +108,7 @@ impl fmt::Display for ParamName {
let str = match self {
Self::Default => "default",
Self::Field => "field",
Self::Getter => "getter",
Self::FinishFn => "finish_fn",
Self::Into => "into",
Self::Name => "name",
Expand Down Expand Up @@ -162,6 +173,7 @@ impl MemberConfig {
let Self {
default,
field,
getter,
finish_fn,
into,
name,
Expand All @@ -176,6 +188,7 @@ impl MemberConfig {
let attrs = [
(default.is_some(), ParamName::Default),
(field.is_some(), ParamName::Field),
(getter.is_some(), ParamName::Getter),
(finish_fn.is_present(), ParamName::FinishFn),
(into.is_present(), ParamName::Into),
(name.is_some(), ParamName::Name),
Expand Down Expand Up @@ -212,15 +225,31 @@ impl MemberConfig {
self.validate_mutually_allowed(
ParamName::StartFn,
self.start_fn.span(),
&[ParamName::Into],
&[ParamName::Into, ParamName::Getter],
)?;
}

if self.finish_fn.is_present() {
self.validate_mutually_allowed(
ParamName::FinishFn,
self.finish_fn.span(),
&[ParamName::Into],
&[ParamName::Into, ParamName::Getter],
)?;
}

if self.getter.is_some() {
self.validate_mutually_allowed(
ParamName::Getter,
self.getter.span(),
&[
ParamName::With,
ParamName::Into,
ParamName::StartFn,
ParamName::FinishFn,
ParamName::Name,
ParamName::Setters,
ParamName::Required,
],
)?;
}

Expand Down
7 changes: 1 addition & 6 deletions bon-macros/src/builder/builder_gen/member/config/setters.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use super::docs_utils::{parse_docs, DOCS_CONTEXT};
use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, 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,
Expand All @@ -14,16 +13,12 @@
SpannedKey::new(meta.path(), params)
}

fn parse_docs(meta: &syn::Meta) -> Result<SpannedKey<Vec<syn::Attribute>>> {
crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta)
}

#[derive(Debug, FromMeta)]
pub(crate) struct SettersConfig {
pub(crate) name: Option<SpannedKey<syn::Ident>>,
pub(crate) vis: Option<SpannedKey<syn::Visibility>>,

#[darling(rename = "doc", default, with = parse_docs, map = Some)]

Check warning on line 21 in bon-macros/src/builder/builder_gen/member/config/setters.rs

View workflow job for this annotation

GitHub Actions / cargo-miri

`if let` assigns a shorter lifetime since Edition 2024
pub(crate) docs: Option<SpannedKey<Vec<syn::Attribute>>>,

#[darling(flatten)]
Expand All @@ -36,13 +31,13 @@
/// 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)]

Check warning on line 34 in bon-macros/src/builder/builder_gen/member/config/setters.rs

View workflow job for this annotation

GitHub Actions / cargo-miri

`if let` assigns a shorter lifetime since Edition 2024
pub(crate) some_fn: Option<SpannedKey<ItemSigConfig>>,

/// 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)]

Check warning on line 41 in bon-macros/src/builder/builder_gen/member/config/setters.rs

View workflow job for this annotation

GitHub Actions / cargo-miri

`if let` assigns a shorter lifetime since Edition 2024
pub(crate) option_fn: Option<SpannedKey<ItemSigConfig>>,
}
Loading
Loading