bon_macros/builder/builder_gen/member/
named.rs

1use super::config::MemberConfig;
2use super::{config, MemberOrigin};
3use crate::builder::builder_gen::member::config::SettersFnsConfig;
4use crate::builder::builder_gen::top_level_config::OnConfig;
5use crate::normalization::SyntaxVariant;
6use crate::parsing::{ItemSigConfig, SpannedKey};
7use crate::util::prelude::*;
8use proc_macro2::TokenTree;
9
10#[derive(Debug)]
11pub(crate) struct MemberName {
12    /// Original name of the member (unchanged). It's used in the finishing
13    /// function of the builder to create a variable for each member.
14    pub(crate) orig: syn::Ident,
15
16    /// `snake_case` version of the member name. By default it's the `orig` name
17    /// itself with the `_` prefix stripped. Otherwise the user can override it
18    /// via `#[builder(name = custom_name)]`
19    pub(crate) snake: syn::Ident,
20
21    /// `snake_case` version of the member name as a string without the `r#` prefix
22    /// (if there is any in the `snake` representation). It's computed and
23    /// stored separately to avoid recomputing it multiple times. It's used
24    /// to derive names for other identifiers that are based on the `snake_case` name.
25    pub(crate) snake_raw_str: String,
26
27    /// `PascalCase` version of the member name. It's always computed as the
28    /// `snake` variant converted to `PascalCase`. The user doesn't have the
29    /// granular control over this name. Users can only specify the snake case
30    /// version of the name, and the pascal case is derived from it.
31    pub(crate) pascal: syn::Ident,
32
33    /// `PascalCase` version of the member name as a string. It's computed and
34    /// stored separately to avoid recomputing it multiple times. It's guaranteed
35    /// to not have the `r#` prefix because:
36    ///
37    /// There are no pascal case keywords in Rust except for `Self`, which
38    /// is anyway not allowed even as a raw identifier:
39    /// <https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094>
40    pub(crate) pascal_str: String,
41}
42
43impl MemberName {
44    pub(crate) fn new(orig: syn::Ident, config: &MemberConfig) -> Self {
45        let snake = config.name.clone().unwrap_or_else(|| {
46            let orig_str = orig.to_string();
47            let norm = orig_str
48                // Remove the leading underscore from the member name since it's used
49                // to denote unused symbols in Rust. That doesn't mean the builder
50                // API should expose that knowledge to the caller.
51                .strip_prefix('_')
52                .unwrap_or(&orig_str);
53
54            // Preserve the original identifier span to make IDE's "go to definition" work correctly
55            // and make error messages point to the correct place.
56            syn::Ident::new_maybe_raw(norm, orig.span())
57        });
58
59        let pascal = snake.snake_to_pascal_case();
60
61        Self {
62            orig,
63            snake_raw_str: snake.raw_name(),
64            snake,
65            pascal_str: pascal.to_string(),
66            pascal,
67        }
68    }
69}
70
71/// Regular member for which the builder should have setter methods
72#[derive(Debug)]
73pub(crate) struct NamedMember {
74    /// Specifies what syntax the member comes from.
75    pub(crate) origin: MemberOrigin,
76
77    /// Index of the member relative to other named members. The index is 0-based.
78    pub(crate) index: syn::Index,
79
80    /// Name of the member is used to generate names for the setters, names for
81    /// the associated types and type aliases in the builder state, etc.
82    pub(crate) name: MemberName,
83
84    /// Doc comments on top of the original syntax. These are copied to the setters
85    /// unless there are overrides for them.
86    pub(crate) docs: Vec<syn::Attribute>,
87
88    /// Type of the member has to be known to generate the types for fields in
89    /// the builder, signatures of the setter methods, etc.
90    pub(crate) ty: SyntaxVariant<Box<syn::Type>>,
91
92    /// Parameters configured by the user explicitly via attributes
93    pub(crate) config: MemberConfig,
94
95    /// Preserve a span the documentation for the member's methods should link to.
96    pub(crate) span: Span,
97}
98
99impl NamedMember {
100    pub(super) fn validate(&self) -> Result {
101        if let Some(default) = &self.config.default {
102            if self.is_special_option_ty() {
103                bail!(
104                    &default.key,
105                    "`Option<_>` already implies a default of `None`, \
106                    so explicit #[builder(default)] is redundant",
107                );
108            }
109        }
110
111        let member_docs_not_copied = self
112            .config
113            .setters
114            .as_ref()
115            .map(|setters| {
116                if setters.doc.content.is_some() {
117                    return true;
118                }
119
120                let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
121                matches!(
122                    (some_fn.as_deref(), option_fn.as_deref()),
123                    (
124                        Some(ItemSigConfig { docs: Some(_), .. }),
125                        Some(ItemSigConfig { docs: Some(_), .. })
126                    )
127                )
128            })
129            .unwrap_or(false);
130
131        if !member_docs_not_copied {
132            crate::parsing::reject_self_mentions_in_docs(
133                "builder struct's impl block",
134                &self.docs,
135            )?;
136        }
137
138        self.validate_setters_config()?;
139
140        if self.config.required.is_present() && !self.ty.norm.is_option() {
141            bail!(
142                &self.config.required.span(),
143                "`#[builder(required)]` can only be applied to members of \
144                type `Option<T>` to disable their special handling",
145            );
146        }
147
148        Ok(())
149    }
150
151    fn validate_setters_config(&self) -> Result {
152        let setters = match &self.config.setters {
153            Some(setters) => setters,
154            None => return Ok(()),
155        };
156
157        if self.is_required() {
158            let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
159
160            let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref());
161
162            if let Some(setter) = unexpected_setter {
163                bail!(
164                    &setter.key,
165                    "`{}` setter function applies only to members with `#[builder(default)]` \
166                     or members of `Option<T>` type (if #[builder(required)] is not set)",
167                    setter.key
168                );
169            }
170        }
171
172        if let SettersFnsConfig {
173            some_fn: Some(some_fn),
174            option_fn: Some(option_fn),
175        } = &setters.fns
176        {
177            let setter_fns = &[some_fn, option_fn];
178
179            Self::validate_unused_setters_cfg(setter_fns, &setters.name, |config| &config.name)?;
180            Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |config| &config.vis)?;
181            Self::validate_unused_setters_cfg(setter_fns, &setters.doc.content, |config| {
182                &config.docs
183            })?;
184        }
185
186        Ok(())
187    }
188
189    fn validate_unused_setters_cfg<T>(
190        overrides: &[&SpannedKey<ItemSigConfig>],
191        config: &Option<SpannedKey<T>>,
192        get_val: impl Fn(&ItemSigConfig) -> &Option<SpannedKey<T>>,
193    ) -> Result {
194        let config = match config {
195            Some(config) => config,
196            None => return Ok(()),
197        };
198
199        let overrides_values = overrides
200            .iter()
201            .copied()
202            .map(|over| get_val(&over.value).as_ref());
203
204        if !overrides_values.clone().all(|over| over.is_some()) {
205            return Ok(());
206        }
207
208        let setters = overrides
209            .iter()
210            .map(|over| format!("`{}`", over.key))
211            .join(", ");
212
213        bail!(
214            &config.key,
215            "this `{name}` configuration is unused because all of the \
216             {setters} setters contain a `{name}` override",
217            name = config.key,
218        );
219    }
220
221    /// Returns `true` if this member is of `Option<_>` type, but returns `false`
222    /// if `#[builder(required)]` is set.
223    pub(crate) fn is_special_option_ty(&self) -> bool {
224        !self.config.required.is_present() && self.ty.norm.is_option()
225    }
226
227    /// Returns `false` if the member has a default value. It means this member
228    /// is required to be set before building can be finished.
229    pub(crate) fn is_required(&self) -> bool {
230        self.config.default.is_none() && !self.is_special_option_ty()
231    }
232
233    /// A stateful member is the one that has a corresponding associated type in
234    /// the builder's type state trait. This is used to track the fact that the
235    /// member was set or not. This is necessary to make sure all members without
236    /// default values are set before building can be finished.
237    pub(crate) fn is_stateful(&self) -> bool {
238        self.is_required() || !self.config.overwritable.is_present()
239    }
240
241    /// Returns the normalized type of the member stripping the `Option<_>`
242    /// wrapper if it's present unless `#[builder(required)]` is set.
243    pub(crate) fn underlying_norm_ty(&self) -> &syn::Type {
244        self.underlying_ty(&self.ty.norm)
245    }
246
247    /// Returns the original type of the member stripping the `Option<_>`
248    /// wrapper if it's present unless `#[builder(required)]` is set.
249    pub(crate) fn underlying_orig_ty(&self) -> &syn::Type {
250        self.underlying_ty(&self.ty.orig)
251    }
252
253    fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type {
254        if self.config.required.is_present() || self.config.default.is_some() {
255            ty
256        } else {
257            ty.option_type_param().unwrap_or(ty)
258        }
259    }
260
261    pub(crate) fn is(&self, other: &Self) -> bool {
262        self.index == other.index
263    }
264
265    pub(crate) fn merge_on_config(&mut self, on: &[OnConfig]) -> Result {
266        // This is a temporary hack. We only allow `on(_, required)` as the
267        // first `on(...)` clause. Instead we should implement the extended design:
268        // https://github.com/elastio/bon/issues/152
269        if let Some(on) = on.first().filter(|on| on.required.is_present()) {
270            if self.is_special_option_ty() {
271                self.config.required = on.required;
272            }
273        }
274
275        self.merge_config_into(on)?;
276        self.merge_setters_doc_skip(on)?;
277
278        // FIXME: refactor this to make it more consistent with `into`
279        // and allow for non-boolean flags in `OnConfig`. E.g. add support
280        // for `with = closure` to `on` as well.
281        self.config.overwritable = config::EvalBlanketFlagParam {
282            on,
283            param_name: config::BlanketParamName::Overwritable,
284            member_config: &self.config,
285            scrutinee: self.underlying_norm_ty(),
286            origin: self.origin,
287        }
288        .eval()?;
289
290        Ok(())
291    }
292
293    fn merge_setters_doc_skip(&mut self, on: &[OnConfig]) -> Result {
294        let skip = config::EvalBlanketFlagParam {
295            on,
296            param_name: config::BlanketParamName::SettersDocDefaultSkip,
297            member_config: &self.config,
298            scrutinee: self.underlying_norm_ty(),
299            origin: self.origin,
300        }
301        .eval()?;
302
303        let setters = self.config.setters.get_or_insert_with(Default::default);
304        let default = setters.doc.default.get_or_insert_with(Default::default);
305
306        default.skip = skip;
307
308        Ok(())
309    }
310
311    /// Respan the tokens with the member's span. This is important to make
312    /// rustdoc's source links point to the original member's location.
313    pub(crate) fn respan(&self, tokens: TokenStream) -> impl Iterator<Item = TokenTree> {
314        let span = self.span;
315        tokens.into_iter().map(move |mut token| {
316            token.set_span(span);
317            token
318        })
319    }
320}