bon_macros/builder/builder_gen/top_level_config/
on.rs

1use crate::util::prelude::*;
2use darling::FromMeta;
3use syn::parse::Parse;
4use syn::spanned::Spanned;
5use syn::visit::Visit;
6
7#[derive(Debug)]
8pub(crate) struct OnConfig {
9    pub(crate) type_pattern: syn::Type,
10    pub(crate) into: darling::util::Flag,
11    pub(crate) overwritable: darling::util::Flag,
12    pub(crate) required: darling::util::Flag,
13}
14
15impl Parse for OnConfig {
16    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
17        let type_pattern = input.parse()?;
18
19        let _ = input.parse::<syn::Token![,]>()?;
20        let rest: TokenStream = input.parse()?;
21
22        #[derive(FromMeta)]
23        struct Parsed {
24            into: darling::util::Flag,
25            overwritable: darling::util::Flag,
26            required: darling::util::Flag,
27        }
28
29        let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?;
30
31        if !cfg!(feature = "experimental-overwritable") && parsed.overwritable.is_present() {
32            return Err(syn::Error::new(
33                parsed.overwritable.span(),
34                "🔬 `overwritable` attribute is experimental and requires \
35                 \"experimental-overwritable\" cargo feature to be enabled; \
36                 we would be glad to make this attribute stable if you find it useful; \
37                 please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \
38                 to help us measure the demand for this feature; it would be \
39                 double-awesome if you could also describe your use case in \
40                 a comment under the issue for us to understand how it's used \
41                 in practice",
42            ));
43        }
44
45        {
46            // Validate that at least some option was enabled.
47            // This lives in a separate block to make sure that if a new
48            // field is added to `Parsed` and unused here, then a compiler
49            // warning is emitted.
50            let Parsed {
51                into,
52                overwritable,
53                required,
54            } = &parsed;
55            let flags = [
56                ("into", into),
57                ("overwritable", overwritable),
58                ("required", required),
59            ];
60
61            if flags.iter().all(|(_, flag)| !flag.is_present()) {
62                let flags = flags.iter().map(|(name, _)| format!("`{name}`")).join(", ");
63                let err = format!(
64                    "this #[builder(on(type_pattern, ...))] contains no options \
65                    to override the default behavior for the selected setters \
66                    like {flags}, so it does nothing"
67                );
68
69                return Err(syn::Error::new_spanned(&rest, err));
70            }
71        }
72
73        struct FindAttr {
74            attr: Option<Span>,
75        }
76
77        impl Visit<'_> for FindAttr {
78            fn visit_attribute(&mut self, attr: &'_ syn::Attribute) {
79                self.attr.get_or_insert(attr.span());
80            }
81        }
82
83        let mut find_attr = FindAttr { attr: None };
84        find_attr.visit_type(&type_pattern);
85
86        if let Some(attr) = find_attr.attr {
87            return Err(syn::Error::new(
88                attr,
89                "nested attributes are not allowed in the type pattern of \
90                #[builder(on(type_pattern, ...))]",
91            ));
92        }
93
94        // The validation is done in the process of matching the types. To make
95        // sure that matching traverses the full pattern we match it with itself.
96        let type_pattern_matches_itself = type_pattern.matches(&type_pattern)?;
97
98        assert!(
99            type_pattern_matches_itself,
100            "BUG: the type pattern does not match itself: {type_pattern:#?}"
101        );
102
103        let Parsed {
104            into,
105            overwritable,
106            required,
107        } = parsed;
108
109        Ok(Self {
110            type_pattern,
111            into,
112            overwritable,
113            required,
114        })
115    }
116}
117
118impl FromMeta for OnConfig {
119    fn from_meta(meta: &syn::Meta) -> Result<Self> {
120        let meta = match meta {
121            syn::Meta::List(meta) => meta,
122            _ => bail!(
123                meta,
124                "expected an attribute of form `on(type_pattern, ...)`"
125            ),
126        };
127
128        let me = syn::parse2(meta.tokens.clone())?;
129
130        Ok(me)
131    }
132}