bon_macros/builder/builder_gen/start_fn.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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
use crate::util::prelude::*;
impl super::BuilderGenCtx {
pub(super) fn start_fn(&self) -> syn::ItemFn {
let builder_ident = &self.builder_type.ident;
let docs = &self.start_fn.docs;
let vis = &self.start_fn.vis;
let start_fn_ident = &self.start_fn.ident;
// TODO: we can use a shorter syntax with anonymous lifetimes to make
// the generated code and function signature displayed by rust-analyzer
// a bit shorter and easier to read. However, the caveat is that we can
// do this only for lifetimes that have no bounds and if they don't appear
// in the where clause. Research `darling`'s lifetime tracking API and
// maybe implement this in the future
let generics = self.start_fn.generics.as_ref().unwrap_or(&self.generics);
let generics_decl = &generics.decl_without_defaults;
let where_clause = &generics.where_clause;
let generic_args = &self.generics.args;
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;
let receiver = self.receiver();
let receiver_field_init = receiver.map(|receiver| {
let self_token = &receiver.with_self_keyword.self_token;
quote! {
#receiver_field: #self_token,
}
});
let receiver = receiver.map(|receiver| {
let mut receiver = receiver.with_self_keyword.clone();
if receiver.reference.is_none() {
receiver.mutability = None;
}
quote! { #receiver, }
});
let start_fn_params = self
.start_fn_args()
.map(|member| member.base.fn_input_param());
// Assign `start_fn_args` to intermediate variables, which may be used
// by custom fields init expressions. This is needed only if there is
// a conversion configured for the `start_fn` members, otherwise these
// are already available in scope as function arguments directly.
let start_fn_vars = self.start_fn_args().filter_map(|member| {
let ident = &member.base.ident;
let ty = &member.base.ty.orig;
let conversion = member.base.conversion()?;
Some(quote! {
let #ident: #ty = #conversion;
})
});
let mut start_fn_args = self.start_fn_args().peekable();
let start_fn_args_field_init = start_fn_args.peek().is_some().then(|| {
let idents = start_fn_args.map(|member| &member.base.ident);
quote! {
#start_fn_args_field: (#(#idents,)*),
}
});
// Create custom fields in separate variables. This way custom fields
// declared lower in the struct definition can reference custom fields
// declared higher in their init expressions.
let custom_fields_vars = self.custom_fields().map(|field| {
let ident = &field.ident;
let ty = &field.norm_ty;
let init = field
.init
.as_ref()
.map(|init| quote! { (|| #init)() })
.unwrap_or_else(|| quote! { ::core::default::Default::default() });
quote! {
let #ident: #ty = #init;
}
});
let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
let ide_hints = self.ide_hints();
// `Default` trait implementation is provided only for tuples up to 12
// elements in the standard library 😳:
// https://github.com/rust-lang/rust/blob/67bb749c2e1cf503fee64842963dd3e72a417a3f/library/core/src/tuple.rs#L213
let named_members_field_init = if self.named_members().take(13).count() <= 12 {
quote!(::core::default::Default::default())
} else {
let none = format_ident!("None");
let nones = self.named_members().map(|_| &none);
quote! {
(#(#nones,)*)
}
};
syn::parse_quote! {
#(#docs)*
#[inline(always)]
#[allow(
// This is intentional. We want the builder syntax to compile away
clippy::inline_always,
// We normalize `Self` references intentionally to simplify code generation
clippy::use_self,
// Let's keep it as non-const for now to avoid restricting ourselfves to only
// const operations.
clippy::missing_const_for_fn,
)]
#vis fn #start_fn_ident< #(#generics_decl),* >(
#receiver
#(#start_fn_params,)*
) -> #builder_ident< #(#generic_args,)* >
#where_clause
{
#ide_hints
#( #start_fn_vars )*
#( #custom_fields_vars )*
#builder_ident {
#phantom_field: ::core::marker::PhantomData,
#( #custom_fields_idents, )*
#receiver_field_init
#start_fn_args_field_init
#named_members_field: #named_members_field_init,
}
}
}
}
}