use super::member::Member;
use super::top_level_config::{DerivesConfig, GenericsConfig, OnConfig};
use crate::normalization::GenericsNamespace;
use crate::parsing::{BonCratePath, ItemSigConfig, SpannedKey};
use crate::util::prelude::*;
use std::borrow::Cow;
pub(super) trait FinishFnBody {
fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream;
}
pub(super) struct AssocMethodReceiverCtx {
pub(super) with_self_keyword: syn::Receiver,
pub(super) without_self_keyword: Box<syn::Type>,
pub(super) field_ident: syn::Ident,
}
pub(super) struct AssocMethodReceiverCtxParams {
pub(super) with_self_keyword: syn::Receiver,
pub(super) without_self_keyword: Box<syn::Type>,
}
pub(super) struct AssocMethodCtx {
pub(super) self_ty: Box<syn::Type>,
pub(super) receiver: Option<AssocMethodReceiverCtx>,
}
pub(super) struct AssocMethodCtxParams {
pub(super) self_ty: Box<syn::Type>,
pub(super) receiver: Option<AssocMethodReceiverCtxParams>,
}
pub(super) struct FinishFn {
pub(super) ident: syn::Ident,
pub(super) vis: syn::Visibility,
pub(super) attrs: Vec<syn::Attribute>,
pub(super) unsafety: Option<syn::Token![unsafe]>,
pub(super) asyncness: Option<syn::Token![async]>,
pub(super) special_attrs: Vec<syn::Attribute>,
pub(super) body: Box<dyn FinishFnBody>,
pub(super) output: syn::ReturnType,
}
pub(super) struct FinishFnParams {
pub(super) ident: syn::Ident,
pub(super) vis: Option<syn::Visibility>,
pub(super) attrs: Vec<syn::Attribute>,
pub(super) unsafety: Option<syn::Token![unsafe]>,
pub(super) asyncness: Option<syn::Token![async]>,
pub(super) special_attrs: Vec<syn::Attribute>,
pub(super) body: Box<dyn FinishFnBody>,
pub(super) output: syn::ReturnType,
}
pub(super) struct StartFn {
pub(super) ident: syn::Ident,
pub(super) vis: syn::Visibility,
pub(super) docs: Vec<syn::Attribute>,
pub(super) generics: Option<Generics>,
pub(super) span: Span,
}
pub(super) struct StartFnParams {
pub(super) ident: syn::Ident,
pub(super) vis: Option<syn::Visibility>,
pub(super) docs: Vec<syn::Attribute>,
pub(super) generics: Option<Generics>,
pub(super) span: Option<Span>,
}
pub(super) struct BuilderType {
pub(super) ident: syn::Ident,
pub(super) vis: syn::Visibility,
pub(super) derives: DerivesConfig,
pub(super) docs: Vec<syn::Attribute>,
}
pub(super) struct BuilderTypeParams {
pub(super) ident: syn::Ident,
pub(super) vis: Option<syn::Visibility>,
pub(super) derives: DerivesConfig,
pub(super) docs: Option<Vec<syn::Attribute>>,
}
pub(super) struct StateMod {
pub(super) ident: syn::Ident,
pub(super) vis: syn::Visibility,
pub(super) vis_child: syn::Visibility,
pub(super) vis_child_child: syn::Visibility,
pub(super) docs: Vec<syn::Attribute>,
}
pub(super) struct Generics {
pub(super) where_clause: Option<syn::WhereClause>,
pub(super) decl_with_defaults: Vec<syn::GenericParam>,
pub(super) decl_without_defaults: Vec<syn::GenericParam>,
pub(super) args: Vec<syn::GenericArgument>,
}
pub(crate) struct BuilderGenCtx {
pub(super) bon: BonCratePath,
pub(super) state_var: syn::Ident,
pub(super) namespace: GenericsNamespace,
pub(super) members: Vec<Member>,
pub(super) allow_attrs: Vec<syn::Attribute>,
pub(super) const_: Option<syn::Token![const]>,
pub(super) on: Vec<OnConfig>,
pub(super) generics: Generics,
pub(super) generics_config: Option<GenericsConfig>,
pub(super) assoc_method_ctx: Option<AssocMethodCtx>,
pub(super) builder_type: BuilderType,
pub(super) state_mod: StateMod,
pub(super) start_fn: StartFn,
pub(super) finish_fn: FinishFn,
}
pub(super) struct BuilderGenCtxParams<'a> {
pub(crate) bon: BonCratePath,
pub(super) namespace: Cow<'a, GenericsNamespace>,
pub(super) members: Vec<Member>,
pub(super) allow_attrs: Vec<syn::Attribute>,
pub(super) const_: Option<syn::Token![const]>,
pub(super) on: Vec<OnConfig>,
pub(super) orig_item_vis: syn::Visibility,
pub(super) generics: Generics,
pub(super) generics_config: Option<GenericsConfig>,
pub(super) assoc_method_ctx: Option<AssocMethodCtxParams>,
pub(super) builder_type: BuilderTypeParams,
pub(super) state_mod: ItemSigConfig,
pub(super) start_fn: StartFnParams,
pub(super) finish_fn: FinishFnParams,
}
impl BuilderGenCtx {
pub(super) fn new(params: BuilderGenCtxParams<'_>) -> Result<Self> {
let BuilderGenCtxParams {
bon,
namespace,
members,
allow_attrs,
const_,
on,
generics,
generics_config,
orig_item_vis,
assoc_method_ctx,
builder_type,
state_mod,
start_fn,
finish_fn,
} = params;
let builder_type = BuilderType {
ident: builder_type.ident,
vis: builder_type.vis.unwrap_or(orig_item_vis),
derives: builder_type.derives,
docs: builder_type.docs.unwrap_or_else(|| {
let doc = format!(
"Use builder syntax to set the inputs and finish with [`{0}()`](Self::{0}()).",
finish_fn.ident
);
vec![syn::parse_quote! {
#[doc = #doc]
}]
}),
};
let state_mod = {
let is_ident_overridden = state_mod.name.is_some();
let ident = state_mod
.name
.map(SpannedKey::into_value)
.unwrap_or_else(|| builder_type.ident.pascal_to_snake_case());
if builder_type.ident == ident {
if is_ident_overridden {
bail!(
&ident,
"the builder module name must be different from the builder type name"
)
}
bail!(
&builder_type.ident,
"couldn't infer the builder module name that doesn't conflict with \
the builder type name; by default, the builder module name is set \
to a snake_case equivalent of the builder type name; the snake_case \
conversion doesn't produce a different name for this builder type \
name; consider using PascalCase for the builder type name or specify \
a separate name for the builder module explicitly via \
`#[builder(state_mod = {{new_name}})]`"
);
}
let vis = state_mod
.vis
.map(SpannedKey::into_value)
.unwrap_or_else(|| syn::Visibility::Inherited);
let vis_child = builder_type.vis.clone().into_equivalent_in_child_module()?;
let vis_child_child = vis_child.clone().into_equivalent_in_child_module()?;
StateMod {
vis,
vis_child,
vis_child_child,
ident,
docs: state_mod
.docs
.map(SpannedKey::into_value)
.unwrap_or_else(|| {
let docs = format!(
"Tools for manipulating the type state of [`{}`].\n\
\n\
See the [detailed guide](https://bon-rs.com/guide/typestate-api) \
that describes how all the pieces here fit together.",
builder_type.ident
);
vec![syn::parse_quote!(#[doc = #docs])]
}),
}
};
let start_fn = StartFn {
ident: start_fn.ident,
vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()),
docs: start_fn.docs,
generics: start_fn.generics,
span: start_fn.span.unwrap_or_else(Span::call_site),
};
let finish_fn = FinishFn {
ident: finish_fn.ident,
vis: finish_fn.vis.unwrap_or_else(|| builder_type.vis.clone()),
attrs: finish_fn.attrs,
unsafety: finish_fn.unsafety,
asyncness: finish_fn.asyncness,
special_attrs: finish_fn.special_attrs,
body: finish_fn.body,
output: finish_fn.output,
};
let state_var = {
let possible_names = ["S", "State", "BuilderState"];
possible_names
.iter()
.find(|&&candidate| !namespace.idents.contains(candidate))
.map(|&name| syn::Ident::new(name, Span::call_site()))
.unwrap_or_else(|| namespace.unique_ident(format!("{}_", possible_names[0])))
};
let assoc_method_ctx = assoc_method_ctx.map(|ctx| {
let receiver = ctx.receiver.map(|receiver| {
let start_fn_arg_names = members
.iter()
.filter_map(Member::as_start_fn)
.map(|member| member.ident.to_string())
.collect();
let field_ident = crate::normalization::unique_name(
&start_fn_arg_names,
"self_receiver".to_owned(),
);
AssocMethodReceiverCtx {
with_self_keyword: receiver.with_self_keyword,
without_self_keyword: receiver.without_self_keyword,
field_ident: syn::Ident::new(&field_ident, Span::call_site()),
}
});
AssocMethodCtx {
self_ty: ctx.self_ty,
receiver,
}
});
Ok(Self {
bon,
state_var,
namespace: namespace.into_owned(),
members,
allow_attrs,
const_,
on,
generics,
generics_config,
assoc_method_ctx,
builder_type,
state_mod,
start_fn,
finish_fn,
})
}
}
impl Generics {
pub(super) fn new(
decl_with_defaults: Vec<syn::GenericParam>,
where_clause: Option<syn::WhereClause>,
) -> Self {
let decl_without_defaults = decl_with_defaults
.iter()
.cloned()
.map(|mut param| {
match &mut param {
syn::GenericParam::Type(param) => {
param.default = None;
}
syn::GenericParam::Const(param) => {
param.default = None;
}
syn::GenericParam::Lifetime(_) => {}
}
param
})
.collect();
let args = decl_with_defaults
.iter()
.map(syn::GenericParam::to_generic_argument)
.collect();
Self {
where_clause,
decl_with_defaults,
decl_without_defaults,
args,
}
}
pub(super) fn where_clause_predicates(&self) -> impl Iterator<Item = &syn::WherePredicate> {
self.where_clause
.as_ref()
.into_iter()
.flat_map(|clause| &clause.predicates)
}
}