Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions benchmarks/runtime/benches/criterion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ fn criterion_bench(c: &mut criterion::Criterion) {
let builder_bench_addr: fn() -> u32 = runtime_benchmarks::bench::builder_bench;
let regular_bebch_addr: fn() -> u32 = runtime_benchmarks::bench::regular_bench;

// Comes from nightly 1.85.0
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
let equal = if builder_bench_addr == regular_bebch_addr {
"equal"
} else {
Expand Down
10 changes: 5 additions & 5 deletions bon-macros/src/builder/builder_gen/builder_derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl BuilderGenCtx {
let ty = member.underlying_norm_ty();

quote! {
#bon::__::derives::clone_member::<#ty>(
#bon::__::better_errors::clone_member::<#ty>(
&self.__unsafe_private_named.#member_index
)
}
Expand Down Expand Up @@ -174,7 +174,7 @@ impl BuilderGenCtx {
Some(quote! {
output.field(
#member_ident_str,
#bon::__::derives::as_dyn_debug::<#member_ty>(
#bon::__::better_errors::as_dyn_debug::<#member_ty>(
&self.__unsafe_private_start_fn_args.#member_index
)
);
Expand All @@ -187,7 +187,7 @@ impl BuilderGenCtx {
Some(quote! {
output.field(
#member_ident_str,
#bon::__::derives::as_dyn_debug::<#member_ty>(
#bon::__::better_errors::as_dyn_debug::<#member_ty>(
&self.#member_ident
)
);
Expand All @@ -201,7 +201,7 @@ impl BuilderGenCtx {
if let Some(value) = &self.__unsafe_private_named.#member_index {
output.field(
#member_ident_str,
#bon::__::derives::as_dyn_debug::<#member_ty>(value)
#bon::__::better_errors::as_dyn_debug::<#member_ty>(value)
);
}
})
Expand All @@ -219,7 +219,7 @@ impl BuilderGenCtx {
quote! {
output.field(
"self",
#bon::__::derives::as_dyn_debug::<#ty>(
#bon::__::better_errors::as_dyn_debug::<#ty>(
&self.__unsafe_private_receiver
)
);
Expand Down
306 changes: 245 additions & 61 deletions bon-macros/src/builder/builder_gen/getters.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,59 @@
use super::member::{GetterConfig, GetterKind};
use super::{BuilderGenCtx, NamedMember};
use crate::parsing::SpannedKey;
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) -> Result<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 index = &self.member.index;
let ty = self.member.underlying_norm_ty();
let vis = self
.config
.vis
.as_deref()
.unwrap_or(&self.base.builder_type.vis)
.clone();

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()
)
}
},
)
} 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() },
)
};
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 return_ty = self.return_ty()?;
let body = self.body();

let state_var = &self.base.state_var;
let member_pascal = &self.member.name.pascal;
let state_mod = &self.base.state_mod.ident;

quote! {
Ok(quote! {
#( #docs )*
#[allow(
// This is intentional. We want the builder syntax to compile away
Expand All @@ -62,40 +62,224 @@ 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) -> #return_ty
where
#state_var::#member_pascal: #state_mod::IsSet,
{
#body
}
})
}

fn body(&self) -> TokenStream {
let index = &self.member.index;
let member = quote! {
self.__unsafe_private_named.#index
};

let bon = &self.base.bon;

match self.config.kind.as_deref() {
Some(GetterKind::Copy) => {
// Use a `_` type hint with the span of the original type
// to make the compiler point to the original type in case
// if the type doesn't implement `Copy`.
let span = self.member.underlying_orig_ty().span();
let ty = quote_spanned!(span=> _);

let copy = quote! {
#bon::__::better_errors::copy_member::<#ty>(&#member)
};

if !self.member.is_required() {
return copy;
}
quote! {
// SAFETY: the method requires S::{Member}: IsSet, so it's Some
unsafe {
::core::option::Option::unwrap_unchecked(#copy)
}
}
}
Some(GetterKind::Clone) => {
// Use a `_` type hint with the span of the original type
// to make the compiler point to the original type in case
// if the type doesn't implement `Clone`.
let span = self.member.underlying_orig_ty().span();
let ty = quote_spanned!(span=> _);

let clone = quote! {
<#ty as ::core::clone::Clone>::clone
};

if !self.member.is_required() {
return quote! {
#clone(&#member)
};
}
quote! {
match &#member {
Some(value) => #clone(value),

// SAFETY: the method requires S::{Member}: IsSet, so it's Some
None => unsafe {
::core::hint::unreachable_unchecked()
},
}
}
}
Some(GetterKind::Deref(ty)) => {
// Assign the span of the deref target type to the `value` variable
// so that compiler points to that type if there is a type mismatch.
let span = ty.span();
let value = quote_spanned!(span=> value);

if !self.member.is_required() {
return quote! {
// Explicit match is important to trigger an implicit deref coercion
// that can potentially do multiple derefs to the reach the target type.
match &#member {
Some(#value) => Some(#value),
None => None,
}
};
}
quote! {
// Explicit match is important to trigger an implicit deref coercion
// that can potentially do multiple derefs to the reach the target type.
match &#member {
Some(#value) => #value,

// SAFETY: the method requires S::{Member}: IsSet, so it's Some
None => unsafe {
::core::hint::unreachable_unchecked()
},
}
}
}
None => {
if !self.member.is_required() {
return quote! {
::core::option::Option::as_ref(&#member)
};
}
quote! {
match &#member {
Some(value) => value,

// SAFETY: the method requires S::{Member}: IsSet, so it's Some
None => unsafe {
::core::hint::unreachable_unchecked()
},
}
}
}
}
}
}

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

let config = member.config.getter.as_ref()?;
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> }
})
}

Some(Self {
name: config.name().cloned().unwrap_or_else(|| {
syn::Ident::new(
&format!("get_{}", member.name.snake.raw_name()),
member.name.snake.span(),
)
fn underlying_return_ty(&self) -> Result<TokenStream> {
let ty = self.member.underlying_norm_ty();

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

match &kind.value {
GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }),
GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }),
GetterKind::Deref(None) => Self::infer_deref_target(ty, kind),
}
}

fn infer_deref_target(
underlying_member_ty: &syn::Type,
kind: &SpannedKey<GetterKind>,
) -> Result<TokenStream> {
use quote_spanned as qs;

let span = underlying_member_ty.span();

#[allow(clippy::type_complexity)]
let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[
("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| {
// CStr is available via `core` since 1.64.0:
// https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#c-compatible-ffi-types-in-core-and-alloc
let module = if rustversion::cfg!(since(1.64.0)) {
format_ident!("core")
} else {
format_ident!("std")
};
args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr))
}),
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()
("OsString", &|args| {
args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))
}),
})
("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 = underlying_member_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()
.find(|(name, _)| last_segment_ident_str == *name)
.and_then(|(_, infer)| infer(args))
.ok_or_else(err)?;

Ok(quote!(&#inferred))
}
}
Loading
Loading