bon_macros/builder/builder_gen/
input_fn.rsuse super::models::FinishFnParams;
use super::top_level_config::TopLevelConfig;
use super::{
AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics, Member,
MemberOrigin, RawMember,
};
use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams};
use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant};
use crate::parsing::{ItemSigConfig, SpannedKey};
use crate::util::prelude::*;
use std::borrow::Cow;
use std::rc::Rc;
use syn::punctuated::Punctuated;
use syn::visit::Visit;
use syn::visit_mut::VisitMut;
pub(crate) struct FnInputCtx<'a> {
namespace: &'a GenericsNamespace,
fn_item: SyntaxVariant<syn::ItemFn>,
impl_ctx: Option<Rc<ImplCtx>>,
config: TopLevelConfig,
start_fn: StartFnParams,
self_ty_prefix: Option<String>,
}
pub(crate) struct FnInputCtxParams<'a> {
pub(crate) namespace: &'a GenericsNamespace,
pub(crate) fn_item: SyntaxVariant<syn::ItemFn>,
pub(crate) impl_ctx: Option<Rc<ImplCtx>>,
pub(crate) config: TopLevelConfig,
}
pub(crate) struct ImplCtx {
pub(crate) self_ty: Box<syn::Type>,
pub(crate) generics: syn::Generics,
pub(crate) allow_attrs: Vec<syn::Attribute>,
}
impl<'a> FnInputCtx<'a> {
pub(crate) fn new(params: FnInputCtxParams<'a>) -> Self {
let start_fn = params.config.start_fn.clone();
let start_fn_ident = start_fn
.name
.map(SpannedKey::into_value)
.unwrap_or_else(|| {
let fn_ident = ¶ms.fn_item.norm.sig.ident;
if params.impl_ctx.is_some() && fn_ident == "new" {
syn::Ident::new("builder", fn_ident.span())
} else {
fn_ident.clone()
}
});
let start_fn = StartFnParams {
ident: start_fn_ident,
vis: start_fn.vis.map(SpannedKey::into_value),
docs: start_fn
.docs
.map(SpannedKey::into_value)
.unwrap_or_else(|| {
params
.fn_item
.norm
.attrs
.iter()
.filter(|attr| attr.is_doc_expr())
.cloned()
.collect()
}),
generics: Some(Generics::new(
params
.fn_item
.norm
.sig
.generics
.params
.iter()
.cloned()
.collect(),
params.fn_item.norm.sig.generics.where_clause.clone(),
)),
};
let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| {
let prefix = impl_ctx
.self_ty
.as_path()?
.path
.segments
.last()?
.ident
.to_string();
Some(prefix)
});
Self {
namespace: params.namespace,
fn_item: params.fn_item,
impl_ctx: params.impl_ctx,
config: params.config,
self_ty_prefix,
start_fn,
}
}
fn assoc_method_ctx(&self) -> Result<Option<AssocMethodCtx>> {
let self_ty = match self.impl_ctx.as_deref() {
Some(impl_ctx) => impl_ctx.self_ty.clone(),
None => return Ok(None),
};
Ok(Some(AssocMethodCtx {
self_ty,
receiver: self.assoc_method_receiver_ctx()?,
}))
}
fn assoc_method_receiver_ctx(&self) -> Result<Option<AssocMethodReceiverCtx>> {
let receiver = match self.fn_item.norm.sig.receiver() {
Some(receiver) => receiver,
None => return Ok(None),
};
let builder_attr_on_receiver = receiver
.attrs
.iter()
.find(|attr| attr.path().is_ident("builder"));
if let Some(attr) = builder_attr_on_receiver {
bail!(
attr,
"#[builder] attributes on the receiver are not supported"
);
}
let self_ty = match self.impl_ctx.as_deref() {
Some(impl_ctx) => &impl_ctx.self_ty,
None => return Ok(None),
};
let mut without_self_keyword = receiver.ty.clone();
NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_keyword);
Ok(Some(AssocMethodReceiverCtx {
with_self_keyword: receiver.clone(),
without_self_keyword,
}))
}
fn generics(&self) -> Generics {
let impl_ctx = self.impl_ctx.as_ref();
let norm_fn_params = &self.fn_item.norm.sig.generics.params;
let params = impl_ctx
.map(|impl_ctx| merge_generic_params(&impl_ctx.generics.params, norm_fn_params))
.unwrap_or_else(|| norm_fn_params.iter().cloned().collect());
let where_clauses = [
self.fn_item.norm.sig.generics.where_clause.clone(),
impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()),
];
let where_clause = where_clauses
.into_iter()
.flatten()
.reduce(|mut combined, clause| {
combined.predicates.extend(clause.predicates);
combined
});
Generics::new(params, where_clause)
}
pub(crate) fn adapted_fn(&self) -> Result<syn::ItemFn> {
let mut orig = self.fn_item.orig.clone();
if let Some(name) = self.config.start_fn.name.as_deref() {
if *name == orig.sig.ident {
bail!(
&name,
"the starting function name must be different from the name \
of the positional function under the #[builder] attribute"
)
}
} else {
orig.vis = syn::Visibility::Inherited;
orig.sig.ident = format_ident!("__orig_{}", orig.sig.ident.raw_name());
orig.attrs.retain(|attr| !attr.is_doc_expr());
orig.attrs.extend([syn::parse_quote!(#[doc(hidden)])]);
}
orig.attrs.retain(|attr| !attr.path().is_ident("builder"));
for arg in &mut orig.sig.inputs {
arg.attrs_mut()
.retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder"));
}
orig.attrs.push(syn::parse_quote!(#[allow(
clippy::too_many_arguments,
clippy::fn_params_excessive_bools,
)]));
Ok(orig)
}
pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
let assoc_method_ctx = self.assoc_method_ctx()?;
if self.impl_ctx.is_none() {
let explanation = "\
but #[bon] attribute is absent on top of the impl block; this \
additional #[bon] attribute on the impl block is required for \
the macro to see the type of `Self` and properly generate \
the builder struct definition adjacently to the impl block.";
if let Some(receiver) = &self.fn_item.orig.sig.receiver() {
bail!(
&receiver.self_token,
"function contains a `self` parameter {explanation}"
);
}
let mut ctx = FindSelfReference::default();
ctx.visit_item_fn(&self.fn_item.orig);
if let Some(self_span) = ctx.self_span {
bail!(
&self_span,
"function contains a `Self` type reference {explanation}"
);
}
}
let members = self
.fn_item
.apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed))
.into_iter()
.map(|arg| {
let pat = match arg.norm.pat.as_ref() {
syn::Pat::Ident(pat) => pat,
_ => bail!(
&arg.orig.pat,
"use a simple `identifier: type` syntax for the function argument; \
destructuring patterns in arguments aren't supported by the `#[builder]`",
),
};
let ty = SyntaxVariant {
norm: arg.norm.ty.clone(),
orig: arg.orig.ty.clone(),
};
Ok(RawMember {
attrs: &arg.norm.attrs,
ident: pat.ident.clone(),
ty,
})
})
.collect::<Result<Vec<_>>>()?;
let members = Member::from_raw(&self.config.on, MemberOrigin::FnArg, members)?;
let generics = self.generics();
let finish_fn_body = FnCallBody {
sig: self.adapted_fn()?.sig,
impl_ctx: self.impl_ctx.clone(),
};
let ItemSigConfig {
name: finish_fn_ident,
vis: finish_fn_vis,
docs: finish_fn_docs,
} = self.config.finish_fn;
let is_special_builder_method = self.impl_ctx.is_some()
&& (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");
let finish_fn_ident = finish_fn_ident
.map(SpannedKey::into_value)
.unwrap_or_else(|| {
if is_special_builder_method {
format_ident!("build")
} else {
format_ident!("call")
}
});
let finish_fn_docs = finish_fn_docs
.map(SpannedKey::into_value)
.unwrap_or_else(|| {
vec![syn::parse_quote! {
}]
});
let finish_fn = FinishFnParams {
ident: finish_fn_ident,
vis: finish_fn_vis.map(SpannedKey::into_value),
unsafety: self.fn_item.norm.sig.unsafety,
asyncness: self.fn_item.norm.sig.asyncness,
must_use: get_must_use_attribute(&self.fn_item.norm.attrs)?,
body: Box::new(finish_fn_body),
output: self.fn_item.norm.sig.output,
attrs: finish_fn_docs,
};
let fn_allows = self
.fn_item
.norm
.attrs
.iter()
.filter_map(syn::Attribute::to_allow);
let allow_attrs = self
.impl_ctx
.as_ref()
.into_iter()
.flat_map(|impl_ctx| impl_ctx.allow_attrs.iter().cloned())
.chain(fn_allows)
.collect();
let builder_ident = || {
let user_override = self.config.builder_type.name.map(SpannedKey::into_value);
if let Some(user_override) = user_override {
return user_override;
}
let ty_prefix = self.self_ty_prefix.unwrap_or_default();
if is_special_builder_method {
return format_ident!("{ty_prefix}Builder");
}
let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case();
format_ident!("{ty_prefix}{pascal_case_fn}Builder")
};
let builder_type = BuilderTypeParams {
ident: builder_ident(),
derives: self.config.derive,
docs: self.config.builder_type.docs.map(SpannedKey::into_value),
vis: self.config.builder_type.vis.map(SpannedKey::into_value),
};
BuilderGenCtx::new(BuilderGenCtxParams {
bon: self.config.bon,
namespace: Cow::Borrowed(self.namespace),
members,
allow_attrs,
on: self.config.on,
assoc_method_ctx,
generics,
orig_item_vis: self.fn_item.norm.vis,
builder_type,
state_mod: self.config.state_mod,
start_fn: self.start_fn,
finish_fn,
})
}
}
struct FnCallBody {
sig: syn::Signature,
impl_ctx: Option<Rc<ImplCtx>>,
}
impl FinishFnBody for FnCallBody {
fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream {
let asyncness = &self.sig.asyncness;
let maybe_await = asyncness.is_some().then(|| quote!(.await));
let generic_args = self
.sig
.generics
.params
.iter()
.filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_)))
.map(syn::GenericParam::to_generic_argument);
let prefix = self
.sig
.receiver()
.map(|_| quote!(self.__unsafe_private_receiver.))
.or_else(|| {
let self_ty = &self.impl_ctx.as_deref()?.self_ty;
Some(quote!(<#self_ty>::))
});
let fn_ident = &self.sig.ident;
let member_vars = ctx.members.iter().map(Member::orig_ident);
quote! {
#prefix #fn_ident::<#(#generic_args,)*>(
#( #member_vars ),*
)
#maybe_await
}
}
}
fn merge_generic_params(
left: &Punctuated<syn::GenericParam, syn::Token![,]>,
right: &Punctuated<syn::GenericParam, syn::Token![,]>,
) -> Vec<syn::GenericParam> {
let is_lifetime = |param: &&_| matches!(param, &&syn::GenericParam::Lifetime(_));
let (left_lifetimes, left_rest): (Vec<_>, Vec<_>) = left.iter().partition(is_lifetime);
let (right_lifetimes, right_rest): (Vec<_>, Vec<_>) = right.iter().partition(is_lifetime);
left_lifetimes
.into_iter()
.chain(right_lifetimes)
.chain(left_rest)
.chain(right_rest)
.cloned()
.collect()
}
#[derive(Default)]
struct FindSelfReference {
self_span: Option<Span>,
}
impl Visit<'_> for FindSelfReference {
fn visit_item(&mut self, _: &syn::Item) {
}
fn visit_path(&mut self, path: &syn::Path) {
if self.self_span.is_some() {
return;
}
syn::visit::visit_path(self, path);
let first_segment = match path.segments.first() {
Some(first_segment) => first_segment,
_ => return,
};
if first_segment.ident == "Self" {
self.self_span = Some(first_segment.ident.span());
}
}
}
fn get_must_use_attribute(attrs: &[syn::Attribute]) -> Result<Option<syn::Attribute>> {
let mut iter = attrs
.iter()
.filter(|attr| attr.meta.path().is_ident("must_use"));
let result = iter.next();
if let Some(second) = iter.next() {
bail!(
second,
"found multiple #[must_use], but bon only works with exactly one or zero."
);
}
if let Some(attr) = result {
if let syn::AttrStyle::Inner(_) = attr.style {
bail!(
attr,
"#[must_use] attribute must be placed on the function itself, \
not inside it."
);
}
}
Ok(result.cloned())
}