bon_macros/builder/builder_gen/top_level_config/
mod.rs

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