bon_macros/builder/builder_gen/top_level_config/
mod.rs

1mod on;
2
3pub(crate) use on::OnConfig;
4
5use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey};
6use crate::util::prelude::*;
7use darling::FromMeta;
8use syn::punctuated::Punctuated;
9
10fn parse_finish_fn(meta: &syn::Meta) -> Result<ItemSigConfig> {
11    ItemSigConfigParsing {
12        meta,
13        reject_self_mentions: Some("builder struct's impl block"),
14    }
15    .parse()
16}
17
18fn parse_builder_type(meta: &syn::Meta) -> Result<ItemSigConfig> {
19    ItemSigConfigParsing {
20        meta,
21        reject_self_mentions: Some("builder struct"),
22    }
23    .parse()
24}
25
26fn parse_state_mod(meta: &syn::Meta) -> Result<ItemSigConfig> {
27    ItemSigConfigParsing {
28        meta,
29        reject_self_mentions: Some("builder's state module"),
30    }
31    .parse()
32}
33
34fn parse_start_fn(meta: &syn::Meta) -> Result<ItemSigConfig> {
35    ItemSigConfigParsing {
36        meta,
37        reject_self_mentions: None,
38    }
39    .parse()
40}
41
42#[derive(Debug, FromMeta)]
43pub(crate) struct TopLevelConfig {
44    /// Overrides the path to the `bon` crate. This is usedfule when the macro is
45    /// wrapped in another macro that also reexports `bon`.
46    #[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_bon_crate_path)]
47    pub(crate) bon: Option<syn::Path>,
48
49    #[darling(default, with = parse_start_fn)]
50    pub(crate) start_fn: ItemSigConfig,
51
52    #[darling(default, with = parse_finish_fn)]
53    pub(crate) finish_fn: ItemSigConfig,
54
55    #[darling(default, with = parse_builder_type)]
56    pub(crate) builder_type: ItemSigConfig,
57
58    #[darling(default, with = parse_state_mod)]
59    pub(crate) state_mod: ItemSigConfig,
60
61    #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)]
62    pub(crate) on: Vec<OnConfig>,
63
64    /// Specifies the derives to apply to the builder.
65    #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)]
66    pub(crate) derive: DerivesConfig,
67}
68
69impl TopLevelConfig {
70    pub(crate) fn parse_for_fn(meta_list: &[darling::ast::NestedMeta]) -> Result<Self> {
71        let me = Self::parse_for_any(meta_list)?;
72
73        if me.start_fn.name.is_none() {
74            let ItemSigConfig { name: _, vis, docs } = &me.start_fn;
75
76            let unexpected_param = None
77                .or_else(|| vis.as_ref().map(SpannedKey::key))
78                .or_else(|| docs.as_ref().map(SpannedKey::key));
79
80            if let Some(unexpected_param) = unexpected_param {
81                bail!(
82                    unexpected_param,
83                    "#[builder(start_fn({unexpected_param}))] requires that you \
84                    also specify #[builder(start_fn(name))] which makes the starting \
85                    function not to replace the positional function under the #[builder] \
86                    attribute; by default (without the explicit #[builder(start_fn(name))]) \
87                    the name, visibility and documentation of the positional \
88                    function are all copied to the starting function, and the positional \
89                    function under the #[builder] attribute becomes private with \
90                    #[doc(hidden)] and it's renamed (the name is not guaranteed \
91                    to be stable) to make it inaccessible even within the current module",
92                );
93            }
94        }
95
96        Ok(me)
97    }
98
99    pub(crate) fn parse_for_struct(meta_list: &[darling::ast::NestedMeta]) -> Result<Self> {
100        Self::parse_for_any(meta_list)
101    }
102
103    fn parse_for_any(meta_list: &[darling::ast::NestedMeta]) -> Result<Self> {
104        // This is a temporary hack. We only allow `on(_, required)` as the
105        // first `on(...)` clause. Instead we should implement an extended design:
106        // https://github.com/elastio/bon/issues/152
107        let mut on_configs = meta_list
108            .iter()
109            .enumerate()
110            .filter_map(|(i, meta)| match meta {
111                darling::ast::NestedMeta::Meta(syn::Meta::List(meta))
112                    if meta.path.is_ident("on") =>
113                {
114                    Some((i, meta))
115                }
116                _ => None,
117            })
118            .peekable();
119
120        while let Some((i, _)) = on_configs.next() {
121            if let Some((j, next_on)) = on_configs.peek() {
122                if *j != i + 1 {
123                    bail!(
124                        next_on,
125                        "this `on(...)` clause is out of order; all `on(...)` \
126                        clauses must be consecutive; there shouldn't be any \
127                        other parameters between them",
128                    )
129                }
130            }
131        }
132
133        let me = Self::from_list(meta_list)?;
134
135        if let Some(on) = me.on.iter().skip(1).find(|on| on.required.is_present()) {
136            bail!(
137                &on.required.span(),
138                "`required` can only be specified in the first `on(...)` clause; \
139                this restriction may be lifted in the future",
140            );
141        }
142
143        if let Some(first_on) = me.on.first().filter(|on| on.required.is_present()) {
144            if !matches!(first_on.type_pattern, syn::Type::Infer(_)) {
145                bail!(
146                    &first_on.type_pattern,
147                    "`required` can only be used with the wildcard type pattern \
148                    i.e. `on(_, required)`; this restriction may be lifted in the future",
149                );
150            }
151        }
152
153        Ok(me)
154    }
155}
156
157#[derive(Debug, Clone, Default, FromMeta)]
158pub(crate) struct DerivesConfig {
159    #[darling(rename = "Clone")]
160    pub(crate) clone: Option<DeriveConfig>,
161
162    #[darling(rename = "Debug")]
163    pub(crate) debug: Option<DeriveConfig>,
164}
165
166#[derive(Debug, Clone, Default)]
167pub(crate) struct DeriveConfig {
168    pub(crate) bounds: Option<Punctuated<syn::WherePredicate, syn::Token![,]>>,
169}
170
171impl FromMeta for DeriveConfig {
172    fn from_meta(meta: &syn::Meta) -> Result<Self> {
173        if let syn::Meta::Path(_) = meta {
174            return Ok(Self { bounds: None });
175        }
176
177        meta.require_list()?.require_parens_delim()?;
178
179        #[derive(FromMeta)]
180        struct Parsed {
181            #[darling(with = crate::parsing::parse_paren_meta_list_with_terminated)]
182            bounds: Punctuated<syn::WherePredicate, syn::Token![,]>,
183        }
184
185        let Parsed { bounds } = Parsed::from_meta(meta)?;
186
187        Ok(Self {
188            bounds: Some(bounds),
189        })
190    }
191}