1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use proc_macro2::{Ident, Span, TokenStream}; |
4 | use proc_macro_crate::crate_name; |
5 | use quote::{quote, quote_spanned, ToTokens}; |
6 | use syn::{ |
7 | meta::ParseNestedMeta, parse::Parse, punctuated::Punctuated, spanned::Spanned, token::Comma, |
8 | Token, Variant, |
9 | }; |
10 | |
11 | pub trait ParseNestedMetaItem { |
12 | fn get_name(&self) -> &'static str; |
13 | fn get_found(&self) -> bool; |
14 | fn get_required(&self) -> bool; |
15 | fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option<syn::Result<()>>; |
16 | } |
17 | |
18 | #[derive (Default)] |
19 | pub struct NestedMetaItem<T> { |
20 | pub name: &'static str, |
21 | pub value_required: bool, |
22 | pub found: bool, |
23 | pub required: bool, |
24 | pub value: Option<T>, |
25 | } |
26 | |
27 | impl<T: Parse + ToTokens> std::fmt::Debug for NestedMetaItem<T> { |
28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
29 | f&mut DebugStruct<'_, '_>.debug_struct("NestedMetaItem" ) |
30 | .field("name" , &self.name) |
31 | .field("required" , &self.required) |
32 | .field("value_required" , &self.value_required) |
33 | .field("found" , &self.found) |
34 | .field(name:"value" , &self.value.as_ref().map(|v: &T| quote!(#v))) |
35 | .finish() |
36 | } |
37 | } |
38 | |
39 | impl<T: Parse> NestedMetaItem<T> { |
40 | pub const fn new(name: &'static str) -> Self { |
41 | Self { |
42 | required: false, |
43 | name, |
44 | found: false, |
45 | value_required: false, |
46 | value: None, |
47 | } |
48 | } |
49 | pub fn required(mut self) -> Self { |
50 | self.required = true; |
51 | self |
52 | } |
53 | // Note: this flags the `value` as required, that is, |
54 | // the parameter after the equal: `name = value`. |
55 | pub const fn value_required(mut self) -> Self { |
56 | self.value_required = true; |
57 | self |
58 | } |
59 | pub const fn value_optional(mut self) -> Self { |
60 | self.value_required = false; |
61 | self |
62 | } |
63 | fn parse_nested_forced(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> { |
64 | if self.value_required || meta.input.peek(Token![=]) { |
65 | let _eq: Token![=] = meta.input.parse()?; |
66 | self.value = Some(meta.input.parse()?); |
67 | } |
68 | Ok(()) |
69 | } |
70 | } |
71 | impl<T: Parse> ParseNestedMetaItem for NestedMetaItem<T> { |
72 | fn get_name(&self) -> &'static str { |
73 | self.name |
74 | } |
75 | fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option<syn::Result<()>> { |
76 | if meta.path.is_ident(self.name) { |
77 | self.found = true; |
78 | Some(self.parse_nested_forced(meta)) |
79 | } else { |
80 | None |
81 | } |
82 | } |
83 | fn get_found(&self) -> bool { |
84 | self.found |
85 | } |
86 | fn get_required(&self) -> bool { |
87 | self.required |
88 | } |
89 | } |
90 | |
91 | pub fn check_meta_items(span: Span, items: &mut [&mut dyn ParseNestedMetaItem]) -> syn::Result<()> { |
92 | let mut err: Option<syn::Error> = None; |
93 | for item: &mut &mut dyn ParseNestedMetaItem in &mut *items { |
94 | if item.get_required() && !item.get_found() { |
95 | let nerr: Error = syn::Error::new( |
96 | span, |
97 | message:format!("attribute ` {}` must be specified" , item.get_name()), |
98 | ); |
99 | if let Some(ref mut err: &mut Error) = err { |
100 | err.combine(another:nerr); |
101 | } else { |
102 | err = Some(nerr); |
103 | } |
104 | } |
105 | } |
106 | match err { |
107 | Some(err: Error) => Err(err), |
108 | None => Ok(()), |
109 | } |
110 | } |
111 | fn parse_nested_meta_items_from_fn( |
112 | parse_nested_meta: impl FnOnce( |
113 | &mut dyn FnMut(ParseNestedMeta) -> syn::Result<()>, |
114 | ) -> syn::Result<()>, |
115 | items: &mut [&mut dyn ParseNestedMetaItem], |
116 | ) -> syn::Result<()> { |
117 | parse_nested_meta(&mut |meta: ParseNestedMeta<'_>| { |
118 | for item: &mut &mut dyn ParseNestedMetaItem in &mut *items { |
119 | if let Some(res: Result<(), Error>) = item.parse_nested(&meta) { |
120 | return res; |
121 | } |
122 | } |
123 | Err(meta.error(msg:format!( |
124 | "unknown attribute ` {}`. Possible attributes are {}" , |
125 | meta.path.get_ident().unwrap(), |
126 | items |
127 | .iter() |
128 | .map(|i| format!("` {}`" , i.get_name())) |
129 | .collect::<Vec<_>>() |
130 | .join(", " ) |
131 | ))) |
132 | })?; |
133 | Ok(()) |
134 | } |
135 | |
136 | pub fn parse_nested_meta_items_from_stream( |
137 | input: TokenStream, |
138 | items: &mut [&mut dyn ParseNestedMetaItem], |
139 | ) -> syn::Result<()> { |
140 | parse_nested_meta_items_from_fn( |
141 | |f: &mut dyn FnMut(ParseNestedMeta<'_>) -> …| { |
142 | let p: impl Parser = syn::meta::parser(logic:f); |
143 | syn::parse::Parser::parse(self:p, tokens:input.into()) |
144 | }, |
145 | items, |
146 | )?; |
147 | check_meta_items(Span::call_site(), items) |
148 | } |
149 | |
150 | pub fn parse_nested_meta_items<'a>( |
151 | attrs: impl IntoIterator<Item = &'a syn::Attribute>, |
152 | attr_name: &str, |
153 | items: &mut [&mut dyn ParseNestedMetaItem], |
154 | ) -> syn::Result<Option<&'a syn::Attribute>> { |
155 | let attr: Option<&Attribute> = attrs as IntoIterator>::IntoIter |
156 | .into_iter() |
157 | .find(|attr: &&Attribute| attr.path().is_ident(attr_name)); |
158 | if let Some(attr: &Attribute) = attr { |
159 | parse_nested_meta_items_from_fn(|x: &mut dyn FnMut(ParseNestedMeta<'_>) -> …| attr.parse_nested_meta(logic:x), items)?; |
160 | check_meta_items(attr.span(), items)?; |
161 | Ok(Some(attr)) |
162 | } else { |
163 | Ok(None) |
164 | } |
165 | } |
166 | |
167 | pub fn crate_ident_new() -> TokenStream { |
168 | use proc_macro_crate::FoundCrate; |
169 | |
170 | match crate_name("glib" ) { |
171 | Ok(FoundCrate::Name(name)) => Some(name), |
172 | Ok(FoundCrate::Itself) => Some("glib" .to_string()), |
173 | Err(_) => None, |
174 | } |
175 | .map(|s| { |
176 | let glib = Ident::new(&s, Span::call_site()); |
177 | quote!(#glib) |
178 | }) |
179 | .unwrap_or_else(|| { |
180 | // We couldn't find the glib crate (renamed or not) so let's just hope it's in scope! |
181 | // |
182 | // We will be able to have this information once this code is stable: |
183 | // |
184 | // ``` |
185 | // let span = Span::call_site(); |
186 | // let source = span.source_file(); |
187 | // let file_path = source.path(); |
188 | // ``` |
189 | // |
190 | // Then we can use proc_macro to parse the file and check if glib is imported somehow. |
191 | let glib = Ident::new("glib" , Span::call_site()); |
192 | quote!(#glib) |
193 | }) |
194 | } |
195 | |
196 | // Generate i32 to enum mapping, used to implement |
197 | // glib::translate::TryFromGlib<i32>, such as: |
198 | // |
199 | // if value == Animal::Goat as i32 { |
200 | // return Some(Animal::Goat); |
201 | // } |
202 | pub fn gen_enum_from_glib( |
203 | enum_name: &Ident, |
204 | enum_variants: &Punctuated<Variant, Comma>, |
205 | ) -> TokenStream { |
206 | // FIXME: can we express this with a match()? |
207 | let recurse: impl Iterator = enum_variants.iter().map(|v: &Variant| { |
208 | let name: &Ident = &v.ident; |
209 | quote_spanned! { v.span() => |
210 | if value == #enum_name::#name as i32 { |
211 | return ::core::option::Option::Some(#enum_name::#name); |
212 | } |
213 | } |
214 | }); |
215 | quote! { |
216 | #(#recurse)* |
217 | ::core::option::Option::None |
218 | } |
219 | } |
220 | |
221 | // These tests are useful to pinpoint the exact location of a macro panic |
222 | // by running `cargo test --lib` |
223 | #[cfg (test)] |
224 | mod tests { |
225 | use syn::{parse_quote, DeriveInput}; |
226 | |
227 | use super::*; |
228 | |
229 | fn boxed_stub() -> DeriveInput { |
230 | parse_quote!( |
231 | #[boxed_type(name = "Author" )] |
232 | struct Author { |
233 | name: String, |
234 | } |
235 | ) |
236 | } |
237 | |
238 | #[test ] |
239 | fn check_attr_found() { |
240 | let input = boxed_stub(); |
241 | let found = parse_nested_meta_items(&input.attrs, "boxed_type" , &mut []); |
242 | matches!(found, Ok(Some(_))); |
243 | } |
244 | #[test ] |
245 | fn required_name_present() { |
246 | let input = boxed_stub(); |
247 | let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name" ) |
248 | .required() |
249 | .value_required(); |
250 | let _ = parse_nested_meta_items(&input.attrs, "boxed_type" , &mut [&mut gtype_name]); |
251 | assert!(gtype_name.get_found()); |
252 | assert_eq!( |
253 | gtype_name.value.map(|x| x.value()), |
254 | Some("Author" .to_string()) |
255 | ); |
256 | } |
257 | #[test ] |
258 | fn required_name_none() { |
259 | let input: DeriveInput = parse_quote!( |
260 | #[boxed_type(name)] |
261 | struct Author { |
262 | name: String, |
263 | } |
264 | ); |
265 | let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name" ) |
266 | .required() |
267 | .value_required(); |
268 | let found = parse_nested_meta_items(&input.attrs, "boxed_type" , &mut [&mut gtype_name]); |
269 | // The argument value was specified as required, so an error is returned |
270 | assert!(found.is_err()); |
271 | assert!(gtype_name.value.is_none()); |
272 | |
273 | // The argument key must be found though |
274 | assert!(gtype_name.get_found()); |
275 | } |
276 | } |
277 | |