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