bon_macros/builder/builder_gen/builder_decl.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
use crate::builder::builder_gen::NamedMember;
use crate::util::prelude::*;
impl super::BuilderGenCtx {
pub(super) fn builder_decl(&self) -> TokenStream {
let builder_vis = &self.builder_type.vis;
let builder_ident = &self.builder_type.ident;
let generics_decl = &self.generics.decl_with_defaults;
let where_clause = &self.generics.where_clause;
let phantom_data = self.phantom_data();
let state_mod = &self.state_mod.ident;
let phantom_field = &self.ident_pool.phantom;
let receiver_field = &self.ident_pool.receiver;
let start_fn_args_field = &self.ident_pool.start_fn_args;
let named_members_field = &self.ident_pool.named_members;
// The fields can't be hidden using Rust's privacy syntax.
// The details about this are described in the blog post:
// https://bon-rs.com/blog/the-weird-of-function-local-types-in-rust.
//
// We could use `#[cfg(not(rust_analyzer))]` to hide the private fields in IDE.
// However, RA would then not be able to type-check the generated code, which
// may or may not be a problem, because the main thing is that the type signatures
// would still work in RA.
let private_field_attrs = {
// The message is defined separately to make it single-line in the
// generated code. This simplifies the task of removing unnecessary
// attributes from the generated code when preparing for demo purposes.
let deprecated_msg = "\
this field should not be used directly; it's an implementation detail \
if you found yourself needing it, then you are probably doing something wrong; \
feel free to open an issue/discussion in our GitHub repository \
(https://github.com/elastio/bon) or ask for help in our Discord server \
(https://bon-rs.com/discord)";
quote! {
#[doc(hidden)]
#[deprecated = #deprecated_msg]
}
};
let receiver_field = self.receiver().map(|receiver| {
let ty = &receiver.without_self_keyword;
quote! {
#private_field_attrs
#receiver_field: #ty,
}
});
let must_use_message = format!(
"the builder does nothing until you call `{}()` on it to finish building",
self.finish_fn.ident
);
let allows = super::allow_warnings_on_member_types();
let mut start_fn_arg_types = self
.start_fn_args()
.map(|member| &member.base.ty.norm)
.peekable();
let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| {
quote! {
#private_field_attrs
#start_fn_args_field: (#(#start_fn_arg_types,)*),
}
});
let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty);
let docs = &self.builder_type.docs;
let state_var = &self.state_var;
let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
let custom_fields_types = self.custom_fields().map(|field| &field.norm_ty);
quote! {
#[must_use = #must_use_message]
#(#docs)*
#allows
#[allow(
// We use `__private` prefix for all fields intentionally to hide them
clippy::struct_field_names,
// This lint doesn't emerge until you manually expand the macro. Just
// because `bon` developers need to expand the macros a lot it makes
// sense to just silence it to avoid some noise. This lint is triggered
// by the big PhantomData type generated by the macro
clippy::type_complexity
)]
#builder_vis struct #builder_ident<
#(#generics_decl,)*
// Having the `State` trait bound on the struct declaration is important
// for future proofing. It will allow us to use this bound in the `Drop`
// implementation of the builder if we ever add one. @Veetaha already did
// some experiments with `MaybeUninit` that requires a custom drop impl,
// so this could be useful in the future.
//
// On the flip side, if we have a custom `Drop` impl, then partially moving
// the builder will be impossible. So.. it's a trade-off, and it's probably
// not a big deal to remove this bound from here if we feel like it.
#state_var: #state_mod::State = #state_mod::Empty
>
#where_clause
{
#private_field_attrs
#phantom_field: #phantom_data,
#receiver_field
#start_fn_args_field
#( #custom_fields_idents: #custom_fields_types, )*
#private_field_attrs
#named_members_field: (
#(
::core::option::Option<#named_members_types>,
)*
),
}
}
}
}