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 match ::darling::export::NestedMeta::parse_meta_list(__data.tokens) {
59 ::darling::export::Ok(ref __items) => {
60 if __items.is_empty() {
61 continue;
62 }
63
64 #core_loop
65 }
66 ::darling::export::Err(__err) => {
67 __errors.push(__err.into());
68 }
69 }
70 }
71 // darling was asked to handle this attribute name, but the actual attribute
72 // isn't one that darling can work with. This either indicates a typing error
73 // or some misunderstanding of the meta attribute syntax; in either case, the
74 // caller should get a useful error.
75 ::darling::export::Err(__err) => {
76 __errors.push(__err);
77 }
78 }
79 }
80 )
81 } else {
82 quote!()
83 };
84
85 // Specifies the behavior for unhandled attributes. They will either be silently ignored or
86 // forwarded to the inner struct for later analysis.
87 let forward_unhandled = if will_fwd_any {
88 forwards_to_local(self.forwarded_attrs().unwrap())
89 } else {
90 quote!(_ => continue)
91 };
92
93 quote!(
94 #declarations
95 use ::darling::ToTokens;
96 let mut __fwd_attrs: ::darling::export::Vec<::darling::export::syn::Attribute> = vec![];
97
98 for __attr in #attrs_accessor {
99 // Filter attributes based on name
100 match ::darling::export::ToString::to_string(&__attr.path().clone().into_token_stream()).as_str() {
101 #parse_handled
102 #forward_unhandled
103 }
104 }
105 )
106 }
107}
108
109fn forwards_to_local(behavior: &ForwardAttrs) -> TokenStream {
110 let push_command: TokenStream = quote!(__fwd_attrs.push(__attr.clone()));
111 match *behavior {
112 ForwardAttrs::All => quote!(_ => #push_command),
113 ForwardAttrs::Only(ref idents: &PathList) => {
114 let names: Vec = idents.to_strings();
115 quote!(
116 #(#names)|* => #push_command,
117 _ => continue,
118 )
119 }
120 }
121}
122