bon_macros/builder/builder_gen/finish_fn.rs
1use super::member::{Member, PosFnMember};
2use crate::util::prelude::*;
3
4impl super::BuilderGenCtx {
5 fn finish_fn_member_expr(member: &Member) -> TokenStream {
6 let member = match member {
7 Member::Named(member) => member,
8 Member::Skip(member) => {
9 return member
10 .value
11 .as_ref()
12 .map(|value| quote! { (|| #value)() })
13 .unwrap_or_else(|| quote! { ::core::default::Default::default() });
14 }
15 Member::StartFn(member) => {
16 let ident = &member.ident;
17 return quote! { self.#ident };
18 }
19 Member::FinishFn(member) => {
20 return member
21 .conversion()
22 .unwrap_or_else(|| member.ident.to_token_stream());
23 }
24 Member::Field(member) => {
25 let ident = &member.ident;
26 return quote! { self.#ident };
27 }
28 };
29
30 let index = &member.index;
31
32 let member_field = quote! {
33 self.__unsafe_private_named.#index
34 };
35
36 let default = member
37 .config
38 .default
39 .as_ref()
40 .map(|default| default.value.as_ref());
41
42 match default {
43 Some(Some(default)) => {
44 let default = if member.config.into.is_present() {
45 quote! { Into::into((|| #default)()) }
46 } else {
47 quote! { #default }
48 };
49
50 quote! {
51 ::core::option::Option::unwrap_or_else(#member_field, || #default)
52 }
53 }
54 Some(None) => {
55 quote! {
56 ::core::option::Option::unwrap_or_default(#member_field)
57 }
58 }
59 None => {
60 // For `Option` the default value is always `None`. So we can just return
61 // the value of the member field itself (which is already an `Option<T>`).
62 if member.is_special_option_ty() {
63 return member_field;
64 }
65
66 quote! {
67 unsafe {
68 // SAFETY: we know that the member is set because we are in
69 // the `finish` function where this method uses the trait
70 // bound of `IsSet` for every required member. It's also
71 // not possible to intervene with the builder's state from
72 // the outside because all members of the builder are considered
73 // private (we even generate random names for them to make it
74 // impossible to access them from the outside in the same module).
75 //
76 // We also make sure to use fully qualified paths to methods
77 // involved in setting the value for the required member to make
78 // sure no trait/function in scope can override the behavior.
79 ::core::option::Option::unwrap_unchecked(#member_field)
80 }
81 }
82 }
83 }
84 }
85
86 pub(super) fn finish_fn(&self) -> TokenStream {
87 let members_vars_decls = self.members.iter().map(|member| {
88 let expr = Self::finish_fn_member_expr(member);
89 let var_ident = member.orig_ident();
90
91 // The type hint is necessary in some cases to assist the compiler
92 // in type inference.
93 //
94 // For example, if the expression is passed to a function that accepts
95 // an impl Trait such as `impl Default`, and the expression itself looks
96 // like `Default::default()`. In this case nothing hints to the compiler
97 // the resulting type of the expression, so we add a type hint via an
98 // intermediate variable here.
99 //
100 // This variable can also be accessed by other member's `default`
101 // or `skip` expressions.
102 let ty = member.norm_ty();
103
104 quote! {
105 let #var_ident: #ty = #expr;
106 }
107 });
108
109 let state_mod = &self.state_mod.ident;
110
111 let finish_fn_params = self.finish_fn_args().map(PosFnMember::fn_input_param);
112
113 let body = &self.finish_fn.body.generate(self);
114 let asyncness = &self.finish_fn.asyncness;
115 let unsafety = &self.finish_fn.unsafety;
116 let must_use = &self.finish_fn.must_use;
117 let attrs = &self.finish_fn.attrs;
118 let finish_fn_vis = &self.finish_fn.vis;
119 let finish_fn_ident = &self.finish_fn.ident;
120 let output = &self.finish_fn.output;
121 let state_var = &self.state_var;
122
123 quote! {
124 #(#attrs)*
125 #[inline(always)]
126 #[allow(
127 // This is intentional. We want the builder syntax to compile away
128 clippy::inline_always,
129
130 // This lint flags any function that returns a possibly `!Send` future.
131 // However, it doesn't apply in the generic context where the future is
132 // `Send` if the generic parameters are `Send` as well, so we just suppress
133 // this lint. See the issue: https://github.com/rust-lang/rust-clippy/issues/6947
134 clippy::future_not_send,
135 clippy::missing_const_for_fn,
136 )]
137 #must_use
138 #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output
139 where
140 #state_var: #state_mod::IsComplete
141 {
142 #(#members_vars_decls)*
143 #body
144 }
145 }
146 }
147}