bon_macros/builder/builder_gen/top_level_config/
on.rs

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