Skip to main content

bon_macros/builder/builder_gen/
getters.rs

1use super::member::{GetterConfig, GetterKind};
2use super::{BuilderGenCtx, NamedMember};
3use crate::parsing::SpannedKey;
4use crate::util::prelude::*;
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7
8pub(crate) struct GettersCtx<'a> {
9    base: &'a BuilderGenCtx,
10    member: &'a NamedMember,
11    config: &'a GetterConfig,
12}
13
14impl<'a> GettersCtx<'a> {
15    pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Option<Self> {
16        Some(Self {
17            base,
18            member,
19            config: member.config.getter.as_ref()?,
20        })
21    }
22
23    pub(crate) fn getter_methods(self) -> Result<TokenStream> {
24        let name = self.config.name.as_deref().cloned().unwrap_or_else(|| {
25            syn::Ident::new(
26                &format!("get_{}", self.member.name.snake.raw_name()),
27                self.member.name.snake.span(),
28            )
29        });
30
31        let vis = self
32            .config
33            .vis
34            .as_deref()
35            .unwrap_or(&self.base.builder_type.vis)
36            .clone();
37
38        let docs = self.config.docs.as_deref().cloned().unwrap_or_else(|| {
39            let header = format!(
40                "_**Getter.**_ Returns `{}`, which must be set before calling this method.\n\n",
41                self.member.name.snake,
42            );
43
44            std::iter::once(syn::parse_quote!(#[doc = #header]))
45                .chain(self.member.docs.iter().cloned())
46                .collect()
47        });
48
49        let return_ty = self.return_ty()?;
50        let body = self.body();
51
52        let state_var = &self.base.state_var;
53        let member_pascal = &self.member.name.pascal;
54        let state_mod = &self.base.state_mod.ident;
55        let const_ = &self.base.const_;
56        let fn_modifiers = self.member.respan(quote!(#vis #const_));
57
58        // It's important to keep the span of `self` the same across all
59        // references to it. Otherwise `self`s that have different spans will
60        // be treated as totally different symbols due to the hygiene rules.
61        let self_ = quote!(self);
62
63        let where_clause = self.member.is_stateful().then(|| {
64            quote! {
65                where
66                    #state_var::#member_pascal: #state_mod::IsSet,
67            }
68        });
69
70        Ok(quote_spanned! {self.member.span=>
71            #( #docs )*
72            #[allow(
73                // This is intentional. We want the builder syntax to compile away
74                clippy::inline_always,
75                clippy::missing_const_for_fn,
76            )]
77            #[inline(always)]
78            #[must_use = "this method has no side effects; it only returns a value"]
79            #(#fn_modifiers)* fn #name(&#self_) -> #return_ty
80            #where_clause
81            {
82                #body
83            }
84        })
85    }
86
87    fn body(&self) -> TokenStream {
88        let index = &self.member.index;
89        let member = quote! {
90            self.__unsafe_private_named.#index
91        };
92
93        let bon = &self.base.bon;
94
95        match self.config.kind.as_deref() {
96            Some(GetterKind::Copy) => {
97                // Use a `_` type hint with the span of the original type
98                // to make the compiler point to the original type in case
99                // if the type doesn't implement `Copy`.
100                let span = self.member.underlying_orig_ty().span();
101                let ty = quote_spanned!(span=> _);
102
103                let copy = quote! {
104                    #bon::__::better_errors::copy_member::<#ty>(&#member)
105                };
106
107                if !self.member.is_required() {
108                    return copy;
109                }
110                quote! {
111                    // SAFETY: the method requires S::{Member}: IsSet, so it's Some
112                    unsafe {
113                        ::core::option::Option::unwrap_unchecked(#copy)
114                    }
115                }
116            }
117            Some(GetterKind::Clone) => {
118                // Use a `_` type hint with the span of the original type
119                // to make the compiler point to the original type in case
120                // if the type doesn't implement `Clone`.
121                let span = self.member.underlying_orig_ty().span();
122                let ty = quote_spanned!(span=> _);
123
124                let clone = quote! {
125                    <#ty as ::core::clone::Clone>::clone
126                };
127
128                if !self.member.is_required() {
129                    return quote! {
130                        #clone(&#member)
131                    };
132                }
133                quote! {
134                    match &#member {
135                        Some(value) => #clone(value),
136
137                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some
138                        None => unsafe {
139                            ::core::hint::unreachable_unchecked()
140                        },
141                    }
142                }
143            }
144            Some(GetterKind::Deref(ty)) => {
145                // Assign the span of the deref target type to the `value` variable
146                // so that compiler points to that type if there is a type mismatch.
147                let span = ty.span();
148                let value = quote_spanned!(span=> value);
149
150                if !self.member.is_required() {
151                    return quote! {
152                        // Explicit match is important to trigger an implicit deref coercion
153                        // that can potentially do multiple derefs to the reach the target type.
154                        match &#member {
155                            Some(#value) => Some(#value),
156                            None => None,
157                        }
158                    };
159                }
160                quote! {
161                    // Explicit match is important to trigger an implicit deref coercion
162                    // that can potentially do multiple derefs to the reach the target type.
163                    match &#member {
164                        Some(#value) => #value,
165
166                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some
167                        None => unsafe {
168                            ::core::hint::unreachable_unchecked()
169                        },
170                    }
171                }
172            }
173            None => {
174                if !self.member.is_required() {
175                    return quote! {
176                        ::core::option::Option::as_ref(&#member)
177                    };
178                }
179                quote! {
180                    match &#member {
181                        Some(value) => value,
182
183                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some
184                        None => unsafe {
185                            ::core::hint::unreachable_unchecked()
186                        },
187                    }
188                }
189            }
190        }
191    }
192
193    fn return_ty(&self) -> Result<TokenStream> {
194        let underlying_return_ty = self.underlying_return_ty()?;
195
196        Ok(if self.member.is_required() {
197            quote! { #underlying_return_ty }
198        } else {
199            // We are not using the fully qualified path to `Option` here
200            // to make function signature in IDE popus shorter and more
201            // readable.
202            quote! { Option<#underlying_return_ty> }
203        })
204    }
205
206    fn underlying_return_ty(&self) -> Result<TokenStream> {
207        let ty = self.member.underlying_norm_ty();
208
209        let kind = match &self.config.kind {
210            Some(kind) => kind,
211            None => return Ok(quote! { &#ty }),
212        };
213
214        match &kind.value {
215            GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }),
216            GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }),
217            GetterKind::Deref(None) => Self::infer_deref_target(ty, kind),
218        }
219    }
220
221    fn infer_deref_target(
222        underlying_member_ty: &syn::Type,
223        kind: &SpannedKey<GetterKind>,
224    ) -> Result<TokenStream> {
225        use quote_spanned as qs;
226
227        let span = underlying_member_ty.span();
228
229        #[allow(clippy::type_complexity)]
230        let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[
231            ("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))),
232            ("Box", &|args| args.first().map(ToTokens::to_token_stream)),
233            ("Rc", &|args| args.first().map(ToTokens::to_token_stream)),
234            ("Arc", &|args| args.first().map(ToTokens::to_token_stream)),
235            ("String", &|args| args.is_empty().then(|| qs!(span=> str))),
236            ("CString", &|args| {
237                // CStr is available via `core` since 1.64.0:
238                // https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#c-compatible-ffi-types-in-core-and-alloc
239                let module = if rustversion::cfg!(since(1.64.0)) {
240                    format_ident!("core")
241                } else {
242                    format_ident!("std")
243                };
244                args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr))
245            }),
246            ("OsString", &|args| {
247                args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))
248            }),
249            ("PathBuf", &|args| {
250                args.is_empty().then(|| qs!(span=> ::std::path::Path))
251            }),
252            ("Cow", &|args| {
253                args.iter()
254                    .find(|arg| matches!(arg, syn::GenericArgument::Type(_)))
255                    .map(ToTokens::to_token_stream)
256            }),
257        ];
258
259        let err = || {
260            let inferable_types = deref_target_inference_table
261                .iter()
262                .map(|(name, _)| format!("- {name}"))
263                .join("\n");
264
265            err!(
266                &kind.key,
267                "can't infer the `Deref::Target` for the getter from the member's type; \
268                please specify the return type (target of the deref coercion) explicitly \
269                in parentheses without the leading `&`;\n\
270                example: `#[builder(getter(deref(TargetTypeHere))]`\n\
271                \n\
272                automatic deref target detection is supported only for the following types:\n\
273                {inferable_types}",
274            )
275        };
276
277        let path = underlying_member_ty.as_path_no_qself().ok_or_else(err)?;
278
279        let last_segment = path.segments.last().ok_or_else(err)?;
280
281        let empty_punctuated = Punctuated::new();
282
283        let args = match &last_segment.arguments {
284            syn::PathArguments::AngleBracketed(args) => &args.args,
285            _ => &empty_punctuated,
286        };
287
288        let last_segment_ident_str = last_segment.ident.to_string();
289
290        let inferred = deref_target_inference_table
291            .iter()
292            .find(|(name, _)| last_segment_ident_str == *name)
293            .and_then(|(_, infer)| infer(args))
294            .ok_or_else(err)?;
295
296        Ok(quote!(&#inferred))
297    }
298}