bon_macros/builder/builder_gen/top_level_config/
on.rs1use 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 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}