1use proc_macro2::TokenStream;
2use quote::quote;
3
4use crate::options::ForwardAttrs;
5use crate::util::PathList;
6
7/// Infrastructure for generating an attribute extractor.
8pub trait ExtractAttribute {
9 /// A set of mutable declarations for all members of the implementing type.
10 fn local_declarations(&self) -> TokenStream;
11
12 /// Gets the list of attribute names that should be parsed by the extractor.
13 fn attr_names(&self) -> &PathList;
14
15 fn forwarded_attrs(&self) -> Option<&ForwardAttrs>;
16
17 /// Gets the name used by the generated impl to return to the `syn` item passed as input.
18 fn param_name(&self) -> TokenStream;
19
20 /// Get the tokens to access a borrowed list of attributes where extraction will take place.
21 ///
22 /// By default, this will be `&#input.attrs` where `#input` is `self.param_name()`.
23 fn attrs_accessor(&self) -> TokenStream {
24 let input = self.param_name();
25 quote!(&#input.attrs)
26 }
27
28 /// Gets the core from-meta-item loop that should be used on matching attributes.
29 fn core_loop(&self) -> TokenStream;
30
31 /// Generates the main extraction loop.
32 fn extractor(&self) -> TokenStream {
33 let declarations = self.local_declarations();
34
35 let will_parse_any = !self.attr_names().is_empty();
36 let will_fwd_any = self
37 .forwarded_attrs()
38 .map(|fa| !fa.is_empty())
39 .unwrap_or_default();
40
41 if !(will_parse_any || will_fwd_any) {
42 return quote! {
43 #declarations
44 };
45 }
46
47 let attrs_accessor = self.attrs_accessor();
48
49 // The block for parsing attributes whose names have been claimed by the target
50 // struct. If no attributes were claimed, this is a pass-through.
51 let parse_handled = if will_parse_any {
52 let attr_names = self.attr_names().to_strings();
53 let core_loop = self.core_loop();
54 quote!(
55 #(#attr_names)|* => {
56 match ::darling::util::parse_attribute_to_meta_list(__attr) {
57 ::darling::export::Ok(__data) => {
58 if __data.nested.is_empty() {
59 continue;
60 }
61
62 let __items = &__data.nested;
63
64 #core_loop
65 }
66 // darling was asked to handle this attribute name, but the actual attribute
67 // isn't one that darling can work with. This either indicates a typing error
68 // or some misunderstanding of the meta attribute syntax; in either case, the
69 // caller should get a useful error.
70 ::darling::export::Err(__err) => {
71 __errors.push(__err);
72 }
73 }
74 }
75 )
76 } else {
77 quote!()
78 };
79
80 // Specifies the behavior for unhandled attributes. They will either be silently ignored or
81 // forwarded to the inner struct for later analysis.
82 let forward_unhandled = if will_fwd_any {
83 forwards_to_local(self.forwarded_attrs().unwrap())
84 } else {
85 quote!(_ => continue)
86 };
87
88 quote!(
89 #declarations
90 use ::darling::ToTokens;
91 let mut __fwd_attrs: ::darling::export::Vec<::darling::export::syn::Attribute> = vec![];
92
93 for __attr in #attrs_accessor {
94 // Filter attributes based on name
95 match ::darling::export::ToString::to_string(&__attr.path.clone().into_token_stream()).as_str() {
96 #parse_handled
97 #forward_unhandled
98 }
99 }
100 )
101 }
102}
103
104fn forwards_to_local(behavior: &ForwardAttrs) -> TokenStream {
105 let push_command: TokenStream = quote!(__fwd_attrs.push(__attr.clone()));
106 match *behavior {
107 ForwardAttrs::All => quote!(_ => #push_command),
108 ForwardAttrs::Only(ref idents: &PathList) => {
109 let names: Vec = idents.to_strings();
110 quote!(
111 #(#names)|* => #push_command,
112 _ => continue,
113 )
114 }
115 }
116}
117