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