Skip to main content

bon_macros/builder/builder_gen/top_level_config/
mod.rs

1mod generics;
2mod on;
3
4pub(crate) use generics::GenericsConfig;
5pub(crate) use on::OnConfig;
6
7use crate::parsing::{BonCratePath, ItemSigConfig, ItemSigConfigParsing, SpannedKey};
8use crate::util::prelude::*;
9use darling::ast::NestedMeta;
10use darling::FromMeta;
11use syn::parse::Parser;
12use syn::punctuated::Punctuated;
13use syn::ItemFn;
14
15fn parse_finish_fn(meta: &syn::Meta) -> Result<ItemSigConfig> {
16    ItemSigConfigParsing::new(meta, Some("builder struct's impl block")).parse()
17}
18
19fn parse_builder_type(meta: &syn::Meta) -> Result<ItemSigConfig> {
20    ItemSigConfigParsing::new(meta, Some("builder struct")).parse()
21}
22
23fn parse_state_mod(meta: &syn::Meta) -> Result<ItemSigConfig> {
24    ItemSigConfigParsing::new(meta, Some("builder's state module")).parse()
25}
26
27fn parse_start_fn(meta: &syn::Meta) -> Result<ItemSigConfig> {
28    ItemSigConfigParsing::new(meta, None).parse()
29}
30
31#[derive(Debug, FromMeta)]
32pub(crate) struct TopLevelConfig {
33    /// Specifies whether the generated functions should be `const`.
34    ///
35    /// It is marked as `#[darling(skip)]` because `const` is a keyword, that
36    /// can't be parsed as a `syn::Ident` and therefore as a `syn::Meta` item.
37    /// We manually parse it from the beginning `builder(...)`.
38    #[darling(skip)]
39    pub(crate) const_: Option<syn::Token![const]>,
40
41    /// Overrides the path to the `bon` crate. This is useful when the macro is
42    /// wrapped in another macro that also reexports `bon`.
43    #[darling(rename = "crate", default)]
44    pub(crate) bon: BonCratePath,
45
46    #[darling(default, with = parse_start_fn)]
47    pub(crate) start_fn: ItemSigConfig,
48
49    #[darling(default, with = parse_finish_fn)]
50    pub(crate) finish_fn: ItemSigConfig,
51
52    #[darling(default, with = parse_builder_type)]
53    pub(crate) builder_type: ItemSigConfig,
54
55    #[darling(default, with = parse_state_mod)]
56    pub(crate) state_mod: ItemSigConfig,
57
58    #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)]
59    pub(crate) on: Vec<OnConfig>,
60
61    /// Specifies the derives to apply to the builder.
62    #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)]
63    pub(crate) derive: DerivesConfig,
64
65    /// Specifies configuration for generic parameter conversion methods.
66    #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)]
67    pub(crate) generics: Option<SpannedKey<GenericsConfig>>,
68}
69
70impl TopLevelConfig {
71    pub(crate) fn parse_for_fn(fn_item: &ItemFn, config: Option<TokenStream>) -> Result<Self> {
72        let other_configs = fn_item
73            .attrs
74            .iter()
75            .filter(|attr| attr.path().is_ident("builder"))
76            .map(|attr| {
77                if let syn::Meta::List(_) = attr.meta {
78                    crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?;
79                }
80                let meta_list = darling::util::parse_attribute_to_meta_list(attr)?;
81                Ok(meta_list.tokens)
82            });
83
84        let configs = config
85            .map(Ok)
86            .into_iter()
87            .chain(other_configs)
88            .collect::<Result<Vec<_>>>()?;
89
90        let me = Self::parse_for_any(configs)?;
91
92        if me.start_fn.name.is_none() {
93            let ItemSigConfig { name: _, vis, docs } = &me.start_fn;
94
95            let unexpected_param = None
96                .or_else(|| vis.as_ref().map(SpannedKey::key))
97                .or_else(|| docs.as_ref().map(SpannedKey::key));
98
99            if let Some(unexpected_param) = unexpected_param {
100                bail!(
101                    unexpected_param,
102                    "#[builder(start_fn({unexpected_param}))] requires that you \
103                    also specify #[builder(start_fn(name))] which makes the starting \
104                    function not to replace the positional function under the #[builder] \
105                    attribute; by default (without the explicit #[builder(start_fn(name))]) \
106                    the name, visibility and documentation of the positional \
107                    function are all copied to the starting function, and the positional \
108                    function under the #[builder] attribute becomes private with \
109                    #[doc(hidden)] and it's renamed (the name is not guaranteed \
110                    to be stable) to make it inaccessible even within the current module",
111                );
112            }
113        }
114
115        Ok(me)
116    }
117
118    pub(crate) fn parse_for_struct(configs: Vec<TokenStream>) -> Result<Self> {
119        Self::parse_for_any(configs)
120    }
121
122    fn parse_for_any(mut configs: Vec<TokenStream>) -> Result<Self> {
123        fn parse_const_prefix(
124            parse: syn::parse::ParseStream<'_>,
125        ) -> syn::Result<(Option<syn::Token![const]>, TokenStream)> {
126            let const_ = parse.parse().ok();
127            if const_.is_some() && !parse.is_empty() {
128                parse.parse::<syn::Token![,]>()?;
129            }
130
131            let rest = parse.parse()?;
132            Ok((const_, rest))
133        }
134
135        // Try to parse the first token of the first config as `const` token.
136        // We have to do this manually because `syn` doesn't support parsing
137        // keywords in the `syn::Meta` keys. Yeah, unfortunately it means that
138        // the users must ensure they place `const` right at the beginning of
139        // their `#[builder(...)]` attributes.
140        let mut const_ = None;
141
142        if let Some(config) = configs.first_mut() {
143            (const_, *config) = parse_const_prefix.parse2(std::mem::take(config))?;
144        }
145
146        let configs = configs
147            .into_iter()
148            .map(NestedMeta::parse_meta_list)
149            .collect::<Result<Vec<_>, _>>()?
150            .into_iter()
151            .flatten()
152            .collect::<Vec<_>>();
153
154        // This is a temporary hack. We only allow `on(_, required)` as the
155        // first `on(...)` clause. Instead we should implement an extended design:
156        // https://github.com/elastio/bon/issues/152
157        let mut on_configs = configs
158            .iter()
159            .enumerate()
160            .filter_map(|(i, meta)| match meta {
161                NestedMeta::Meta(syn::Meta::List(meta)) if meta.path.is_ident("on") => {
162                    Some((i, meta))
163                }
164                _ => None,
165            })
166            .peekable();
167
168        while let Some((i, _)) = on_configs.next() {
169            if let Some((j, next_on)) = on_configs.peek() {
170                if *j != i + 1 {
171                    bail!(
172                        next_on,
173                        "this `on(...)` clause is out of order; all `on(...)` \
174                        clauses must be consecutive; there shouldn't be any \
175                        other parameters between them",
176                    )
177                }
178            }
179        }
180
181        let me = Self {
182            const_,
183            ..Self::from_list(&configs)?
184        };
185
186        if let Some(generics) = &me.generics {
187            if generics.setters.is_some() {
188                if let Some(const_) = &me.const_ {
189                    bail!(
190                        const_,
191                        "`generics(setters(...))` cannot be used together with `const` \
192                         functions; if you have a use case for this, consider opening an \
193                         issue to discuss it!"
194                    );
195                }
196            }
197        }
198
199        if let Some(on) = me.on.iter().skip(1).find(|on| on.required.is_present()) {
200            bail!(
201                &on.required.span(),
202                "`required` can only be specified in the first `on(...)` clause; \
203                this restriction may be lifted in the future",
204            );
205        }
206
207        if let Some(first_on) = me.on.first().filter(|on| on.required.is_present()) {
208            if !matches!(first_on.type_pattern, syn::Type::Infer(_)) {
209                bail!(
210                    &first_on.type_pattern,
211                    "`required` can only be used with the wildcard type pattern \
212                    i.e. `on(_, required)`; this restriction may be lifted in the future",
213                );
214            }
215        }
216
217        Ok(me)
218    }
219}
220
221#[derive(Debug, Clone, Default, FromMeta)]
222pub(crate) struct DerivesConfig {
223    #[darling(rename = "Clone")]
224    pub(crate) clone: Option<DeriveConfig>,
225
226    #[darling(rename = "Debug")]
227    pub(crate) debug: Option<DeriveConfig>,
228
229    #[darling(rename = "Into")]
230    pub(crate) into: darling::util::Flag,
231
232    #[darling(rename = "IntoFuture")]
233    pub(crate) into_future: Option<IntoFutureConfig>,
234}
235
236#[derive(Debug, Clone, Default)]
237pub(crate) struct DeriveConfig {
238    pub(crate) bounds: Option<Punctuated<syn::WherePredicate, syn::Token![,]>>,
239}
240
241#[derive(Debug, Clone)]
242pub(crate) struct IntoFutureConfig {
243    pub(crate) box_ident: syn::Ident,
244    pub(crate) is_send: bool,
245}
246
247impl syn::parse::Parse for IntoFutureConfig {
248    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
249        // Parse "Box" as the required first argument.
250        let box_ident: syn::Ident = input.parse()?;
251        if box_ident != "Box" {
252            return Err(syn::Error::new(
253                box_ident.span(),
254                "expected `Box` as the first argument, only boxed futures are supported",
255            ));
256        }
257
258        // Check for optional ", ?Send" part.
259        let is_send = if input.peek(syn::Token![,]) {
260            input.parse::<syn::Token![,]>()?;
261
262            // Parse "?Send" as a single unit.
263            if input.peek(syn::Token![?]) {
264                input.parse::<syn::Token![?]>()?;
265                let send_ident: syn::Ident = input.parse()?;
266                if send_ident != "Send" {
267                    return Err(syn::Error::new(
268                        send_ident.span(),
269                        "expected `Send` after ?",
270                    ));
271                }
272                false
273            } else {
274                return Err(input.error("expected `?Send` as the second argument"));
275            }
276        } else {
277            true
278        };
279
280        // Ensure no trailing tokens.
281        if !input.is_empty() {
282            return Err(input.error("unexpected tokens after arguments"));
283        }
284
285        Ok(Self { box_ident, is_send })
286    }
287}
288
289impl FromMeta for IntoFutureConfig {
290    fn from_meta(meta: &syn::Meta) -> Result<Self> {
291        let meta = match meta {
292            syn::Meta::List(meta) => meta,
293            _ => bail!(meta, "expected an attribute of form `IntoFuture(Box, ...)`"),
294        };
295
296        meta.require_parens_delim()?;
297
298        let me = syn::parse2(meta.tokens.clone())?;
299
300        Ok(me)
301    }
302}
303
304impl FromMeta for DeriveConfig {
305    fn from_meta(meta: &syn::Meta) -> Result<Self> {
306        if let syn::Meta::Path(_) = meta {
307            return Ok(Self { bounds: None });
308        }
309
310        meta.require_list()?.require_parens_delim()?;
311
312        #[derive(FromMeta)]
313        struct Parsed {
314            #[darling(with = crate::parsing::parse_paren_meta_list_with_terminated)]
315            bounds: Punctuated<syn::WherePredicate, syn::Token![,]>,
316        }
317
318        let Parsed { bounds } = Parsed::from_meta(meta)?;
319
320        Ok(Self {
321            bounds: Some(bounds),
322        })
323    }
324}