1use crate::util::prelude::*;
2use darling::FromMeta;
3use syn::parse::Parse;
4use syn::spanned::Spanned;
5use syn::visit::Visit;
67#[derive(Debug)]
8pub(crate) struct OnConfig {
9pub(crate) type_pattern: syn::Type,
10pub(crate) into: darling::util::Flag,
11pub(crate) overwritable: darling::util::Flag,
12pub(crate) required: darling::util::Flag,
13}
1415impl Parse for OnConfig {
16fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
17let type_pattern = input.parse()?;
1819let _ = input.parse::<syn::Token![,]>()?;
20let rest: TokenStream = input.parse()?;
2122#[derive(FromMeta)]
23struct Parsed {
24 into: darling::util::Flag,
25 overwritable: darling::util::Flag,
26 required: darling::util::Flag,
27 }
2829let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?;
3031if !cfg!(feature = "experimental-overwritable") && parsed.overwritable.is_present() {
32return 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 }
4445 {
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.
50let Parsed {
51 into,
52 overwritable,
53 required,
54 } = &parsed;
55let flags = [
56 ("into", into),
57 ("overwritable", overwritable),
58 ("required", required),
59 ];
6061if flags.iter().all(|(_, flag)| !flag.is_present()) {
62let flags = flags.iter().map(|(name, _)| format!("`{name}`")).join(", ");
63let 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);
6869return Err(syn::Error::new_spanned(&rest, err));
70 }
71 }
7273struct FindAttr {
74 attr: Option<Span>,
75 }
7677impl Visit<'_> for FindAttr {
78fn visit_attribute(&mut self, attr: &'_ syn::Attribute) {
79self.attr.get_or_insert(attr.span());
80 }
81 }
8283let mut find_attr = FindAttr { attr: None };
84 find_attr.visit_type(&type_pattern);
8586if let Some(attr) = find_attr.attr {
87return Err(syn::Error::new(
88 attr,
89"nested attributes are not allowed in the type pattern of \
90 #[builder(on(type_pattern, ...))]",
91 ));
92 }
9394// 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.
96let type_pattern_matches_itself = type_pattern.matches(&type_pattern)?;
9798assert!(
99 type_pattern_matches_itself,
100"BUG: the type pattern does not match itself: {type_pattern:#?}"
101);
102103let Parsed {
104 into,
105 overwritable,
106 required,
107 } = parsed;
108109Ok(Self {
110 type_pattern,
111 into,
112 overwritable,
113 required,
114 })
115 }
116}
117118impl FromMeta for OnConfig {
119fn from_meta(meta: &syn::Meta) -> Result<Self> {
120let meta = match meta {
121 syn::Meta::List(meta) => meta,
122_ => bail!(
123 meta,
124"expected an attribute of form `on(type_pattern, ...)`"
125),
126 };
127128let me = syn::parse2(meta.tokens.clone())?;
129130Ok(me)
131 }
132}