bon_macros/builder/builder_gen/member/config/
mod.rs

1// This lint occurs in code generated by `darling::FromAttributes`.
2// TODO: report/fix this in `darling` upstream.
3#![allow(clippy::needless_continue)]
4
5mod blanket;
6mod getter;
7mod setters;
8mod with;
9
10pub(crate) use blanket::*;
11pub(crate) use getter::*;
12pub(crate) use setters::*;
13pub(crate) use with::*;
14
15use super::MemberOrigin;
16use crate::builder::builder_gen::TopLevelConfig;
17use crate::parsing::SpannedKey;
18use crate::util::prelude::*;
19use std::fmt;
20
21#[derive(Debug, darling::FromAttributes)]
22#[darling(attributes(builder))]
23pub(crate) struct MemberConfig {
24    /// Assign a default value to the member it it's not specified.
25    ///
26    /// An optional expression can be provided to set the value for the member,
27    /// otherwise its [`Default`] trait impl will be used.
28    #[darling(with = parse_optional_expr, map = Some)]
29    pub(crate) default: Option<SpannedKey<Option<syn::Expr>>>,
30
31    /// Make the member a private field in the builder struct.
32    /// This is useful when the user needs to add custom fields to the builder,
33    /// that they would use in the custom methods they add to the builder.
34    ///
35    /// This is similar to `skip`. The difference is that `field` is evaluated
36    /// inside of the starting function, and stored in the builder. Its initialization
37    /// expression thus has access to all `start_fn` parameters. It must be declared
38    /// strictly after `#[builder(start_fn)]` members (if any) or right at the top of
39    /// the members list.
40    #[darling(with = parse_optional_expr, map = Some)]
41    pub(crate) field: Option<SpannedKey<Option<syn::Expr>>>,
42
43    /// Make the member gettable. [`GetterConfig`] specifies the signature for
44    /// the getter.
45    ///
46    /// This takes the same attributes as the setter fns; `name`, `vis`, and `doc`
47    /// and produces a getter method that returns the value of the member.
48    /// By default, the value is returned by a shared reference (&T).
49    pub(crate) getter: Option<SpannedKey<GetterConfig>>,
50
51    /// Accept the value for the member in the finishing function parameters.
52    pub(crate) finish_fn: darling::util::Flag,
53
54    /// Enables an `Into` conversion for the setter method.
55    pub(crate) into: darling::util::Flag,
56
57    /// Rename the name exposed in the builder API.
58    pub(crate) name: Option<syn::Ident>,
59
60    /// Allows setting the value for the member repeatedly. This reduces the
61    /// number of type states and thus increases the compilation performance.
62    ///
63    /// However, this also means that unintended overwrites won't be caught
64    /// at compile time. Measure the compilation time before and after enabling
65    /// this option to see if it's worth it.
66    pub(crate) overwritable: darling::util::Flag,
67
68    /// Disables the special handling for a member of type `Option<T>`. The
69    /// member no longer has the default of `None`. It also becomes a required
70    /// member unless a separate `#[builder(default = ...)]` attribute is
71    /// also specified.
72    pub(crate) required: darling::util::Flag,
73
74    /// Configurations for the setter methods.
75    #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)]
76    pub(crate) setters: Option<SettersConfig>,
77
78    /// Skip generating a setter method for this member.
79    ///
80    /// An optional expression can be provided to set the value for the member,
81    /// otherwise its  [`Default`] trait impl will be used.
82    #[darling(with = parse_optional_expr, map = Some)]
83    pub(crate) skip: Option<SpannedKey<Option<syn::Expr>>>,
84
85    /// Accept the value for the member in the starting function parameters.
86    pub(crate) start_fn: darling::util::Flag,
87
88    /// Customize the setter signature and body with a custom closure or a well-known
89    /// function. The closure/function must return the value of the type of the member,
90    /// or optionally a `Result<_>` type where `_` is used to mark the type of
91    /// the member. In this case the generated setters will be fallible
92    /// (they'll propagate the `Result`).
93    pub(crate) with: Option<SpannedKey<WithConfig>>,
94}
95
96#[derive(PartialEq, Eq, Clone, Copy)]
97enum ParamName {
98    Default,
99    Field,
100    Getter,
101    FinishFn,
102    Into,
103    Name,
104    Overwritable,
105    Required,
106    Setters,
107    Skip,
108    StartFn,
109    With,
110}
111
112impl fmt::Display for ParamName {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        let str = match self {
115            Self::Default => "default",
116            Self::Field => "field",
117            Self::Getter => "getter",
118            Self::FinishFn => "finish_fn",
119            Self::Into => "into",
120            Self::Name => "name",
121            Self::Overwritable => "overwritable",
122            Self::Required => "required",
123            Self::Setters => "setters",
124            Self::Skip => "skip",
125            Self::StartFn => "start_fn",
126            Self::With => "with",
127        };
128        f.write_str(str)
129    }
130}
131
132impl MemberConfig {
133    fn validate_mutually_exclusive(
134        &self,
135        attr_name: ParamName,
136        attr_span: Span,
137        mutually_exclusive: &[ParamName],
138    ) -> Result<()> {
139        self.validate_compat(attr_name, attr_span, mutually_exclusive, true)
140    }
141
142    fn validate_mutually_allowed(
143        &self,
144        attr_name: ParamName,
145        attr_span: Span,
146        mutually_allowed: &[ParamName],
147    ) -> Result<()> {
148        self.validate_compat(attr_name, attr_span, mutually_allowed, false)
149    }
150
151    fn validate_compat(
152        &self,
153        attr_name: ParamName,
154        attr_span: Span,
155        patterns: &[ParamName],
156        mutually_exclusive: bool,
157    ) -> Result<()> {
158        let conflicting: Vec<_> = self
159            .specified_param_names()
160            .filter(|name| *name != attr_name && patterns.contains(name) == mutually_exclusive)
161            .collect();
162
163        if conflicting.is_empty() {
164            return Ok(());
165        }
166
167        let conflicting = conflicting
168            .iter()
169            .map(|name| format!("`{name}`"))
170            .join(", ");
171
172        bail!(
173            &attr_span,
174            "`{attr_name}` attribute can't be specified together with {conflicting}",
175        );
176    }
177
178    fn specified_param_names(&self) -> impl Iterator<Item = ParamName> {
179        let Self {
180            default,
181            field,
182            getter,
183            finish_fn,
184            into,
185            name,
186            overwritable,
187            required,
188            setters,
189            skip,
190            start_fn,
191            with,
192        } = self;
193
194        let attrs = [
195            (default.is_some(), ParamName::Default),
196            (field.is_some(), ParamName::Field),
197            (getter.is_some(), ParamName::Getter),
198            (finish_fn.is_present(), ParamName::FinishFn),
199            (into.is_present(), ParamName::Into),
200            (name.is_some(), ParamName::Name),
201            (overwritable.is_present(), ParamName::Overwritable),
202            (required.is_present(), ParamName::Required),
203            (setters.is_some(), ParamName::Setters),
204            (skip.is_some(), ParamName::Skip),
205            (start_fn.is_present(), ParamName::StartFn),
206            (with.is_some(), ParamName::With),
207        ];
208
209        attrs
210            .into_iter()
211            .filter(|(is_present, _)| *is_present)
212            .map(|(_, name)| name)
213    }
214
215    pub(crate) fn validate(&self, top_config: &TopLevelConfig, origin: MemberOrigin) -> Result {
216        if top_config.const_.is_some() {
217            self.require_const_compat()?;
218        }
219
220        if !cfg!(feature = "experimental-overwritable") && self.overwritable.is_present() {
221            bail!(
222                &self.overwritable.span(),
223                "🔬 `overwritable` attribute is experimental and requires \
224                 \"experimental-overwritable\" cargo feature to be enabled; \
225                 we would be glad to make this attribute stable if you find it useful; \
226                 please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \
227                 to help us measure the demand for this feature; it would be \
228                 double-awesome if you could also describe your use case in \
229                 a comment under the issue for us to understand how it's used \
230                 in practice",
231            );
232        }
233
234        if let Some(getter) = &self.getter {
235            self.validate_mutually_exclusive(
236                ParamName::Getter,
237                getter.key.span(),
238                &[ParamName::Overwritable],
239            )?;
240        }
241
242        if self.start_fn.is_present() {
243            self.validate_mutually_allowed(
244                ParamName::StartFn,
245                self.start_fn.span(),
246                // TODO: add support for `#[builder(getter)]` with `start_fn`
247                &[ParamName::Into],
248            )?;
249        }
250
251        if self.finish_fn.is_present() {
252            self.validate_mutually_allowed(
253                ParamName::FinishFn,
254                self.finish_fn.span(),
255                &[ParamName::Into],
256            )?;
257        }
258
259        if let Some(field) = &self.field {
260            self.validate_mutually_allowed(ParamName::Field, field.key.span(), &[])?;
261        }
262
263        if let Some(skip) = &self.skip {
264            match origin {
265                MemberOrigin::FnArg => {
266                    bail!(
267                        &skip.key.span(),
268                        "`skip` attribute is not supported on function arguments; \
269                        use a local variable instead.",
270                    );
271                }
272                MemberOrigin::StructField => {}
273            }
274
275            if let Some(Some(_expr)) = self.default.as_deref() {
276                bail!(
277                    &skip.key.span(),
278                    "`skip` attribute can't be specified with the `default` attribute; \
279                    if you wanted to specify a value for the member, then use \
280                    the following syntax instead `#[builder(skip = value)]`",
281                );
282            }
283
284            self.validate_mutually_allowed(ParamName::Skip, skip.key.span(), &[])?;
285        }
286
287        if let Some(with) = &self.with {
288            self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?;
289        }
290
291        if let Some(setters) = &self.setters {
292            if let Some(default) = &setters.doc.default {
293                if self.default.is_none() {
294                    bail!(
295                        &default.key,
296                        "`#[builder(setters(doc(default(...)))]` may only be specified \
297                        when #[builder(default)] is also specified",
298                    );
299                }
300            }
301        }
302
303        Ok(())
304    }
305
306    fn require_const_compat(&self) -> Result {
307        fn validate_default_trait_or_expr(attr: &Option<SpannedKey<Option<syn::Expr>>>) -> Result {
308            let attr = match attr {
309                Some(attr) => attr,
310                None => return Ok(()),
311            };
312
313            let name = attr.key.to_string();
314
315            if let Some(expr) = &attr.value {
316                return crate::parsing::require_embeddable_const_expr(expr);
317            }
318
319            bail!(
320                &attr.key,
321                "bare #[builder({name})] is incompatible with #[builder(const)] \
322                because Default::default() can not be called in const context; \
323                provide an explicit value via #[builder({name} = ...)] instead",
324            )
325        }
326
327        validate_default_trait_or_expr(&self.default)?;
328        validate_default_trait_or_expr(&self.skip)?;
329        validate_default_trait_or_expr(&self.field)?;
330
331        if self.into.is_present() {
332            bail!(
333                &self.into.span(),
334                "#[builder(into)] is incompatible with #[builder(const)] \
335                because Into::into() can not be called in const context",
336            );
337        }
338
339        if let Some(getter) = &self.getter {
340            if let Some(getter_kind) = &getter.kind {
341                match &getter_kind.value {
342                    GetterKind::Copy => {}
343                    GetterKind::Clone => {
344                        bail!(
345                            &getter_kind.key,
346                            "#[builder(getter(clone))] is incompatible with #[builder(const)] \
347                            because Clone::clone() can not be called in const context",
348                        )
349                    }
350                    GetterKind::Deref(_) => {
351                        bail!(
352                            &getter_kind.key,
353                            "#[builder(getter(deref))] is incompatible with #[builder(const)] \
354                            because Deref::deref() can not be called in const context",
355                        )
356                    }
357                }
358            }
359        }
360
361        if let Some(with) = &self.with {
362            match &with.value {
363                WithConfig::Closure(closure) => {
364                    crate::parsing::require_embeddable_const_expr(&closure.body)?;
365                }
366                WithConfig::Some(_) => {}
367                WithConfig::FromIter(from_iter) => {
368                    bail!(
369                        &from_iter,
370                        "from_iter is incompatible with #[builder(const)] because \
371                        FromIterator::from_iter() can not be called in const context",
372                    )
373                }
374            }
375        }
376
377        Ok(())
378    }
379}
380
381fn parse_optional_expr(meta: &syn::Meta) -> Result<SpannedKey<Option<syn::Expr>>> {
382    match meta {
383        syn::Meta::Path(path) => SpannedKey::new(path, None),
384        syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
385        syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())),
386    }
387}