Skip to content

Commit

Permalink
Support clone, copy, deref in #[builder(getter)]
Browse files Browse the repository at this point in the history
  • Loading branch information
Veetaha committed Dec 5, 2024
1 parent 7bd157e commit 2b7de11
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 74 deletions.
199 changes: 143 additions & 56 deletions bon-macros/src/builder/builder_gen/getters.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,74 @@
use super::member::{GetterConfig, GetterKind};
use super::{BuilderGenCtx, NamedMember};
use crate::util::prelude::*;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;

pub(crate) struct GettersCtx<'a> {
base: &'a BuilderGenCtx,
member: &'a NamedMember,
}

struct GetterItem {
name: syn::Ident,
vis: syn::Visibility,
docs: Vec<syn::Attribute>,
config: &'a GetterConfig,
}

impl<'a> GettersCtx<'a> {
pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Self {
Self { base, member }
pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Option<Self> {
Some(Self {
base,
member,
config: member.config.getter.as_ref()?,
})
}

pub(crate) fn getter_methods(&self) -> TokenStream {
let GetterItem { name, vis, docs } = match GetterItem::new(self) {
Some(item) => item,
None => return quote! {},
};
pub(crate) fn getter_methods(self) -> TokenStream {
let name = self.config.name.as_deref().cloned().unwrap_or_else(|| {
syn::Ident::new(
&format!("get_{}", self.member.name.snake.raw_name()),
self.member.name.snake.span(),
)
});

let vis = self
.config
.vis
.as_deref()
.unwrap_or(&self.base.builder_type.vis)
.clone();

let docs = self.config.docs.as_deref().cloned().unwrap_or_else(|| {
let header = format!(
"_**Getter.**_ Returns `{}`, which must be set before calling this method.\n\n",
self.member.name.snake,
);

std::iter::once(syn::parse_quote!(#[doc = #header]))
.chain(self.member.docs.iter().cloned())
.collect()
});

let index = &self.member.index;
let ty = self.member.underlying_norm_ty();

let (return_type, body) = if self.member.is_required() {
(
quote! { &#ty },
quote! {
unsafe {
// SAFETY: this code is runs in a method that has a where
// bound that ensures the member was set.
::core::option::Option::unwrap_unchecked(
self.__unsafe_private_named.#index.as_ref()
)
}
},
)
let ret_ty;
let body;

Check failure on line 52 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (ubuntu)

`if _ { .. } else { .. }` is an expression

Check failure on line 52 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-unstable (nightly)

`if _ { .. } else { .. }` is an expression

Check failure on line 52 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-unstable (beta)

`if _ { .. } else { .. }` is an expression

Check failure on line 52 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (macos)

`if _ { .. } else { .. }` is an expression

Check failure on line 52 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (windows)

`if _ { .. } else { .. }` is an expression

if self.member.is_required() {
ret_ty = quote! { &#ty };
body = quote! {
unsafe {
// SAFETY: this code is runs in a method that has a where
// bound that ensures the member was set.
::core::option::Option::unwrap_unchecked(
self.__unsafe_private_named.#index.as_ref()
)
}
};
} else {
(
// We are not using the fully qualified path to `Option` here
// to make function signature in IDE popus shorter and more
// readable.
quote! { Option<&#ty> },
quote! { self.__unsafe_private_named.#index.as_ref() },
)
};
// We are not using the fully qualified path to `Option` here
// to make function signature in IDE popus shorter and more
// readable.
ret_ty = quote! { Option<&#ty> };
body = quote! { self.__unsafe_private_named.#index.as_ref() };
}

let state_var = &self.base.state_var;
let member_pascal = &self.member.name.pascal;
Expand All @@ -62,40 +83,106 @@ impl<'a> GettersCtx<'a> {
)]
#[inline(always)]
#[must_use = "this method has no side effects; it only returns a value"]
#vis fn #name(&self) -> #return_type
#vis fn #name(&self) -> #ret_ty
where
#state_var::#member_pascal: #state_mod::IsSet,
{
#body
}
}
}
}

impl GetterItem {
fn new(ctx: &GettersCtx<'_>) -> Option<Self> {
let GettersCtx { member, base } = ctx;
fn underlying_return_ty(&self) -> Result<TokenStream> {

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_5)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_20)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_10_structs)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / cargo-doc (macos)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (ubuntu)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-unstable (nightly)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-unstable (beta)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_3)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-msrv (ubuntu)

associated function is never used: `underlying_return_ty`

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (macos)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / cargo-doc (ubuntu)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-msrv (macos)

associated function is never used: `underlying_return_ty`

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-msrv (windows)

associated function is never used: `underlying_return_ty`

Check warning on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / cargo-miri

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / cargo-doc (windows)

methods `underlying_return_ty` and `return_ty` are never used

Check failure on line 95 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (windows)

methods `underlying_return_ty` and `return_ty` are never used
let ty = self.member.underlying_norm_ty();

let config = member.config.getter.as_ref()?;
let kind = match &self.config.kind {
Some(kind) => kind,
None => return Ok(quote! { &#ty }),
};

Some(Self {
name: config.name().cloned().unwrap_or_else(|| {
syn::Ident::new(
&format!("get_{}", member.name.snake.raw_name()),
member.name.snake.span(),
)
match &kind.value {
GetterKind::Copy | GetterKind::Clone => return Ok(quote! { #ty }),
GetterKind::Deref(Some(deref_target)) => return Ok(quote! { &#deref_target }),
// Go below to the code that infers the deref target type
GetterKind::Deref(None) => {}
}

use quote_spanned as qs;

let span = ty.span();

let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[

Check failure on line 114 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (ubuntu)

very complex type used. Consider factoring parts into `type` definitions

Check failure on line 114 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-unstable (nightly)

very complex type used. Consider factoring parts into `type` definitions

Check failure on line 114 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-unstable (beta)

very complex type used. Consider factoring parts into `type` definitions

Check failure on line 114 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (macos)

very complex type used. Consider factoring parts into `type` definitions

Check failure on line 114 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-stable (windows)

very complex type used. Consider factoring parts into `type` definitions
("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))),
("Box", &|args| args.first().map(ToTokens::to_token_stream)),
("Rc", &|args| args.first().map(ToTokens::to_token_stream)),
("Arc", &|args| args.first().map(ToTokens::to_token_stream)),
("String", &|args| args.is_empty().then(|| qs!(span=> str))),
("CString", &|args| {
args.is_empty().then(|| qs!(span=> ::core::ffi::CStr))
}),
("OsString", &|args| {
args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))
}),
vis: config.vis().unwrap_or(&base.builder_type.vis).clone(),
docs: config.docs().map(<[_]>::to_vec).unwrap_or_else(|| {
let header = format!(
"_**Getter.**_ Returns `{}`, which must be set before calling this method.\n\n",
member.name.snake,
);

std::iter::once(syn::parse_quote!(#[doc = #header]))
.chain(member.docs.iter().cloned())
.collect()
("PathBuf", &|args| {
args.is_empty().then(|| qs!(span=> ::std::path::Path))
}),
("Cow", &|args| {
args.iter()
.find(|arg| matches!(arg, syn::GenericArgument::Type(_)))
.map(ToTokens::to_token_stream)
}),
];

let err = || {
let inferable_types = deref_target_inference_table
.iter()
.map(|(name, _)| format!("- {name}"))
.join("\n");

err!(
&kind.key,
"can't infer the `Deref::Target` for the getter from the member's type; \
please specify the return type (target of the deref coercion) explicitly \
in parentheses without the leading `&`;\n\
example: `#[builder(getter(deref(TargetTypeHere))]`\n\
\n
automatic deref target detection is supported only for the following types:\n\
{inferable_types}",
)
};

let path = ty.as_path_no_qself().ok_or_else(err)?;

let last_segment = path.segments.last().ok_or_else(err)?;

let empty_punctuated = Punctuated::new();

let args = match &last_segment.arguments {
syn::PathArguments::AngleBracketed(args) => &args.args,
_ => &empty_punctuated,
};

let last_segment_ident_str = last_segment.ident.to_string();

let inferred = deref_target_inference_table
.iter()
.filter(|(name, _)| last_segment_ident_str == *name)
.find_map(|(_, infer)| infer(args))
.ok_or_else(err)?;

Ok(quote!(&#inferred))
}

fn return_ty(&self) -> Result<TokenStream> {

Check failure on line 176 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-msrv (ubuntu)

associated function is never used: `return_ty`

Check failure on line 176 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-msrv (macos)

associated function is never used: `return_ty`

Check failure on line 176 in bon-macros/src/builder/builder_gen/getters.rs

View workflow job for this annotation

GitHub Actions / test-msrv (windows)

associated function is never used: `return_ty`
let underlying_return_ty = self.underlying_return_ty()?;

Ok(if self.member.is_required() {
quote! { #underlying_return_ty }
} else {
// We are not using the fully qualified path to `Option` here
// to make function signature in IDE popus shorter and more
// readable.
quote! { Option<#underlying_return_ty> }
})
}
}
69 changes: 53 additions & 16 deletions bon-macros/src/builder/builder_gen/member/config/getter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,25 @@ use darling::FromMeta;

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

/// Returns `&T` if [`None`]
pub(crate) kind: Option<SpannedKey<GetterKind>>,

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_5)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_20)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_10_structs)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-doc (macos)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / test-stable (ubuntu)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / test-unstable (nightly)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / test-unstable (beta)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_3)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / test-msrv (ubuntu)

field is never read: `kind`

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

View workflow job for this annotation

GitHub Actions / test-stable (macos)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-doc (ubuntu)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / test-msrv (macos)

field is never read: `kind`

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

View workflow job for this annotation

GitHub Actions / test-msrv (windows)

field is never read: `kind`

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

View workflow job for this annotation

GitHub Actions / cargo-miri

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-doc (windows)

field `kind` is never read

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

View workflow job for this annotation

GitHub Actions / test-stable (windows)

field `kind` is never read
}

#[derive(Debug)]
pub(crate) enum GetterKind {
/// Returns `T` via [`Copy`]
Copy,

/// Returns `T` via [`Clone`]
Clone,

/// Returns `&<T as Deref>::Target`.
/// If the type is `None`, it will be inferred from the member's type.
Deref(Option<syn::Type>),

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_5)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_20)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_10_structs)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-doc (macos)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / test-stable (ubuntu)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / test-unstable (nightly)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / test-unstable (beta)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / runtime-benchmarks (args_3)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / test-stable (macos)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-doc (ubuntu)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-miri

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / cargo-doc (windows)

field `0` is never read

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

View workflow job for this annotation

GitHub Actions / test-stable (windows)

field `0` is never read
}

impl FromMeta for GetterConfig {
Expand All @@ -26,25 +42,46 @@ impl FromMeta for GetterConfig {

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

Check warning on line 43 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>>>,

copy: Option<SpannedKey<()>>,
clone: Option<SpannedKey<()>>,
deref: Option<SpannedKey<Option<syn::Type>>>,
}

let Parsed { name, vis, docs } = Parsed::from_meta(meta)?;
let Parsed {
name,
vis,
docs,
copy,
clone,
deref,
} = Parsed::from_meta(meta)?;

Ok(Self { name, vis, docs })
}
}
let kinds = [
copy.map(|cfg| cfg.with_value(GetterKind::Copy)),
clone.map(|cfg| cfg.with_value(GetterKind::Clone)),
deref.map(|ty| ty.map_value(GetterKind::Deref)),
];

impl GetterConfig {
pub(crate) fn name(&self) -> Option<&syn::Ident> {
self.name.as_ref().map(|n| &n.value)
}
let kinds = kinds.into_iter().flatten().collect::<Vec<_>>();

pub(crate) fn vis(&self) -> Option<&syn::Visibility> {
self.vis.as_ref().map(|v| &v.value)
}
if let [kind1, kind2, ..] = kinds.as_slice() {
bail!(
&kind1.key,
"`{}` can't be specified together with `{}`",
kind1.key,
kind2.key
);
}

let kind = kinds.into_iter().next();

pub(crate) fn docs(&self) -> Option<&[syn::Attribute]> {
self.docs.as_ref().map(|a| &a.value).map(|a| &**a)
Ok(Self {
name,
vis,
docs,
kind,
})
}
}

Expand Down
4 changes: 3 additions & 1 deletion bon-macros/src/builder/builder_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ impl BuilderGenCtx {
.named_members()
.map(|member| {
let setters = SettersCtx::new(self, member).setter_methods()?;
let getters = GettersCtx::new(self, member).getter_methods();
let getters = GettersCtx::new(self, member)
.map(GettersCtx::getter_methods)
.unwrap_or_default();

// Output all accessor methods for the same member adjecently.
// This is important in the generated rustdoc output, because
Expand Down
14 changes: 14 additions & 0 deletions bon-macros/src/parsing/spanned_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ impl<T> SpannedKey<T> {
pub(crate) fn key(&self) -> &syn::Ident {
&self.key
}

pub(crate) fn with_value<U>(self, value: U) -> SpannedKey<U> {
SpannedKey {
value,
key: self.key,
}
}

pub(crate) fn map_value<U>(self, map: impl FnOnce(T) -> U) -> SpannedKey<U> {
SpannedKey {
value: map(self.value),
key: self.key,
}
}
}

impl<T: FromMeta> FromMeta for SpannedKey<T> {
Expand Down
2 changes: 1 addition & 1 deletion bon-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use prelude::*;

pub(crate) mod prelude {
pub(crate) use proc_macro2::{Span, TokenStream};
pub(crate) use quote::{format_ident, quote, ToTokens};
pub(crate) use quote::{format_ident, quote, quote_spanned, ToTokens};

/// The `Error` type in in this crate is supposed to act like `anyhow::Error`
/// providing a simple way to create and return errors from format strings.
Expand Down

0 comments on commit 2b7de11

Please sign in to comment.