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
56        Ok(quote! {
57            #( #docs )*
58            #[allow(
59                // This is intentional. We want the builder syntax to compile away
60                clippy::inline_always,
61                clippy::missing_const_for_fn,
62            )]
63            #[inline(always)]
64            #[must_use = "this method has no side effects; it only returns a value"]
65            #vis fn #name(&self) -> #return_ty
66            where
67                #state_var::#member_pascal: #state_mod::IsSet,
68            {
69                #body
70            }
71        })
72    }
73
74    fn body(&self) -> TokenStream {
75        let index = &self.member.index;
76        let member = quote! {
77            self.__unsafe_private_named.#index
78        };
79
80        let bon = &self.base.bon;
81
82        match self.config.kind.as_deref() {
83            Some(GetterKind::Copy) => {
84                // Use a `_` type hint with the span of the original type
85                // to make the compiler point to the original type in case
86                // if the type doesn't implement `Copy`.
87                let span = self.member.underlying_orig_ty().span();
88                let ty = quote_spanned!(span=> _);
89
90                let copy = quote! {
91                    #bon::__::better_errors::copy_member::<#ty>(&#member)
92                };
93
94                if !self.member.is_required() {
95                    return copy;
96                }
97                quote! {
98                    // SAFETY: the method requires S::{Member}: IsSet, so it's Some
99                    unsafe {
100                        ::core::option::Option::unwrap_unchecked(#copy)
101                    }
102                }
103            }
104            Some(GetterKind::Clone) => {
105                // Use a `_` type hint with the span of the original type
106                // to make the compiler point to the original type in case
107                // if the type doesn't implement `Clone`.
108                let span = self.member.underlying_orig_ty().span();
109                let ty = quote_spanned!(span=> _);
110
111                let clone = quote! {
112                    <#ty as ::core::clone::Clone>::clone
113                };
114
115                if !self.member.is_required() {
116                    return quote! {
117                        #clone(&#member)
118                    };
119                }
120                quote! {
121                    match &#member {
122                        Some(value) => #clone(value),
123
124                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some
125                        None => unsafe {
126                            ::core::hint::unreachable_unchecked()
127                        },
128                    }
129                }
130            }
131            Some(GetterKind::Deref(ty)) => {
132                // Assign the span of the deref target type to the `value` variable
133                // so that compiler points to that type if there is a type mismatch.
134                let span = ty.span();
135                let value = quote_spanned!(span=> value);
136
137                if !self.member.is_required() {
138                    return quote! {
139                        // Explicit match is important to trigger an implicit deref coercion
140                        // that can potentially do multiple derefs to the reach the target type.
141                        match &#member {
142                            Some(#value) => Some(#value),
143                            None => None,
144                        }
145                    };
146                }
147                quote! {
148                    // Explicit match is important to trigger an implicit deref coercion
149                    // that can potentially do multiple derefs to the reach the target type.
150                    match &#member {
151                        Some(#value) => #value,
152
153                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some
154                        None => unsafe {
155                            ::core::hint::unreachable_unchecked()
156                        },
157                    }
158                }
159            }
160            None => {
161                if !self.member.is_required() {
162                    return quote! {
163                        ::core::option::Option::as_ref(&#member)
164                    };
165                }
166                quote! {
167                    match &#member {
168                        Some(value) => value,
169
170                        // SAFETY: the method requires S::{Member}: IsSet, so it's Some
171                        None => unsafe {
172                            ::core::hint::unreachable_unchecked()
173                        },
174                    }
175                }
176            }
177        }
178    }
179
180    fn return_ty(&self) -> Result<TokenStream> {
181        let underlying_return_ty = self.underlying_return_ty()?;
182
183        Ok(if self.member.is_required() {
184            quote! { #underlying_return_ty }
185        } else {
186            // We are not using the fully qualified path to `Option` here
187            // to make function signature in IDE popus shorter and more
188            // readable.
189            quote! { Option<#underlying_return_ty> }
190        })
191    }
192
193    fn underlying_return_ty(&self) -> Result<TokenStream> {
194        let ty = self.member.underlying_norm_ty();
195
196        let kind = match &self.config.kind {
197            Some(kind) => kind,
198            None => return Ok(quote! { &#ty }),
199        };
200
201        match &kind.value {
202            GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }),
203            GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }),
204            GetterKind::Deref(None) => Self::infer_deref_target(ty, kind),
205        }
206    }
207
208    fn infer_deref_target(
209        underlying_member_ty: &syn::Type,
210        kind: &SpannedKey<GetterKind>,
211    ) -> Result<TokenStream> {
212        use quote_spanned as qs;
213
214        let span = underlying_member_ty.span();
215
216        #[allow(clippy::type_complexity)]
217        let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[
218            ("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))),
219            ("Box", &|args| args.first().map(ToTokens::to_token_stream)),
220            ("Rc", &|args| args.first().map(ToTokens::to_token_stream)),
221            ("Arc", &|args| args.first().map(ToTokens::to_token_stream)),
222            ("String", &|args| args.is_empty().then(|| qs!(span=> str))),
223            ("CString", &|args| {
224                // CStr is available via `core` since 1.64.0:
225                // https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#c-compatible-ffi-types-in-core-and-alloc
226                let module = if rustversion::cfg!(since(1.64.0)) {
227                    format_ident!("core")
228                } else {
229                    format_ident!("std")
230                };
231                args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr))
232            }),
233            ("OsString", &|args| {
234                args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))
235            }),
236            ("PathBuf", &|args| {
237                args.is_empty().then(|| qs!(span=> ::std::path::Path))
238            }),
239            ("Cow", &|args| {
240                args.iter()
241                    .find(|arg| matches!(arg, syn::GenericArgument::Type(_)))
242                    .map(ToTokens::to_token_stream)
243            }),
244        ];
245
246        let err = || {
247            let inferable_types = deref_target_inference_table
248                .iter()
249                .map(|(name, _)| format!("- {name}"))
250                .join("\n");
251
252            err!(
253                &kind.key,
254                "can't infer the `Deref::Target` for the getter from the member's type; \
255                please specify the return type (target of the deref coercion) explicitly \
256                in parentheses without the leading `&`;\n\
257                example: `#[builder(getter(deref(TargetTypeHere))]`\n\
258                \n\
259                automatic deref target detection is supported only for the following types:\n\
260                {inferable_types}",
261            )
262        };
263
264        let path = underlying_member_ty.as_path_no_qself().ok_or_else(err)?;
265
266        let last_segment = path.segments.last().ok_or_else(err)?;
267
268        let empty_punctuated = Punctuated::new();
269
270        let args = match &last_segment.arguments {
271            syn::PathArguments::AngleBracketed(args) => &args.args,
272            _ => &empty_punctuated,
273        };
274
275        let last_segment_ident_str = last_segment.ident.to_string();
276
277        let inferred = deref_target_inference_table
278            .iter()
279            .find(|(name, _)| last_segment_ident_str == *name)
280            .and_then(|(_, infer)| infer(args))
281            .ok_or_else(err)?;
282
283        Ok(quote!(&#inferred))
284    }
285}