1 | use syn::{ |
2 | spanned::Spanned, Attribute, Lit, LitBool, LitStr, Meta, MetaList, NestedMeta, Result, Type, |
3 | TypePath, |
4 | }; |
5 | |
6 | // find the #[@attr_name] attribute in @attrs |
7 | fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result<Option<MetaList>> { |
8 | let meta: Meta = match attrs.iter().find(|a: &&Attribute| a.path.is_ident(attr_name)) { |
9 | Some(a: &Attribute) => a.parse_meta(), |
10 | _ => return Ok(None), |
11 | }?; |
12 | match meta { |
13 | Meta::List(n: MetaList) => Ok(Some(n)), |
14 | _ => Err(syn::Error::new( |
15 | meta.span(), |
16 | message:format!(" {attr_name} meta must specify a meta list" ), |
17 | )), |
18 | } |
19 | } |
20 | |
21 | fn get_meta_value<'a>(meta: &'a Meta, attr: &str) -> Result<&'a Lit> { |
22 | match meta { |
23 | Meta::NameValue(meta: &MetaNameValue) => Ok(&meta.lit), |
24 | Meta::Path(_) => Err(syn::Error::new( |
25 | meta.span(), |
26 | message:format!("attribute ` {attr}` must have a value" ), |
27 | )), |
28 | Meta::List(_) => Err(syn::Error::new( |
29 | meta.span(), |
30 | message:format!("attribute {attr} is not a list" ), |
31 | )), |
32 | } |
33 | } |
34 | |
35 | /// Compares `ident` and `attr` and in case they match ensures `value` is `Some` and contains a |
36 | /// [`struct@LitStr`]. Returns `true` in case `ident` and `attr` match, otherwise false. |
37 | /// |
38 | /// # Errors |
39 | /// |
40 | /// Returns an error in case `ident` and `attr` match but the value is not `Some` or is not a |
41 | /// [`struct@LitStr`]. |
42 | pub fn match_attribute_with_str_value<'a>( |
43 | meta: &'a Meta, |
44 | attr: &str, |
45 | ) -> Result<Option<&'a LitStr>> { |
46 | if meta.path().is_ident(attr) { |
47 | match get_meta_value(meta, attr)? { |
48 | Lit::Str(value: &LitStr) => Ok(Some(value)), |
49 | _ => Err(syn::Error::new( |
50 | meta.span(), |
51 | message:format!("value of the ` {attr}` attribute must be a string literal" ), |
52 | )), |
53 | } |
54 | } else { |
55 | Ok(None) |
56 | } |
57 | } |
58 | |
59 | /// Compares `ident` and `attr` and in case they match ensures `value` is `Some` and contains a |
60 | /// [`struct@LitBool`]. Returns `true` in case `ident` and `attr` match, otherwise false. |
61 | /// |
62 | /// # Errors |
63 | /// |
64 | /// Returns an error in case `ident` and `attr` match but the value is not `Some` or is not a |
65 | /// [`struct@LitBool`]. |
66 | pub fn match_attribute_with_bool_value<'a>( |
67 | meta: &'a Meta, |
68 | attr: &str, |
69 | ) -> Result<Option<&'a LitBool>> { |
70 | if meta.path().is_ident(attr) { |
71 | match get_meta_value(meta, attr)? { |
72 | Lit::Bool(value: &LitBool) => Ok(Some(value)), |
73 | other: &Lit => Err(syn::Error::new( |
74 | other.span(), |
75 | message:format!("value of the ` {attr}` attribute must be a boolean literal" ), |
76 | )), |
77 | } |
78 | } else { |
79 | Ok(None) |
80 | } |
81 | } |
82 | |
83 | pub fn match_attribute_with_str_list_value(meta: &Meta, attr: &str) -> Result<Option<Vec<String>>> { |
84 | if meta.path().is_ident(attr) { |
85 | match meta { |
86 | Meta::List(list) => { |
87 | let mut values = Vec::with_capacity(list.nested.len()); |
88 | |
89 | for meta in &list.nested { |
90 | values.push(match meta { |
91 | NestedMeta::Lit(Lit::Str(lit)) => Ok(lit.value()), |
92 | NestedMeta::Lit(lit) => Err(syn::Error::new( |
93 | lit.span(), |
94 | format!("invalid literal type for ` {attr}` attribute" ), |
95 | )), |
96 | NestedMeta::Meta(meta) => Err(syn::Error::new( |
97 | meta.span(), |
98 | format!("` {attr}` attribute must be a list of string literals" ), |
99 | )), |
100 | }?) |
101 | } |
102 | |
103 | Ok(Some(values)) |
104 | } |
105 | _ => Err(syn::Error::new( |
106 | meta.span(), |
107 | format!("invalid meta type for attribute ` {attr}`" ), |
108 | )), |
109 | } |
110 | } else { |
111 | Ok(None) |
112 | } |
113 | } |
114 | |
115 | /// Compares `ident` and `attr` and in case they match ensures `value` is `None`. Returns `true` in |
116 | /// case `ident` and `attr` match, otherwise false. |
117 | /// |
118 | /// # Errors |
119 | /// |
120 | /// Returns an error in case `ident` and `attr` match but the value is not `None`. |
121 | pub fn match_attribute_without_value(meta: &Meta, attr: &str) -> Result<bool> { |
122 | if meta.path().is_ident(attr) { |
123 | match meta { |
124 | Meta::Path(_) => Ok(true), |
125 | Meta::List(_) => Err(syn::Error::new( |
126 | meta.span(), |
127 | message:format!("attribute {attr} is not a list" ), |
128 | )), |
129 | Meta::NameValue(_) => Err(syn::Error::new( |
130 | meta.span(), |
131 | message:format!("attribute ` {attr}` must not have a value" ), |
132 | )), |
133 | } |
134 | } else { |
135 | Ok(false) |
136 | } |
137 | } |
138 | |
139 | /// Returns an iterator over the contents of all [`MetaList`]s with the specified identifier in an |
140 | /// array of [`Attribute`]s. |
141 | pub fn iter_meta_lists( |
142 | attrs: &[Attribute], |
143 | list_name: &str, |
144 | ) -> Result<impl Iterator<Item = NestedMeta>> { |
145 | let meta: Option = find_attribute_meta(attrs, attr_name:list_name)?; |
146 | |
147 | Ok(meta.into_iter().flat_map(|meta: MetaList| meta.nested.into_iter())) |
148 | } |
149 | |
150 | /// Generates one or more structures used for parsing attributes in proc macros. |
151 | /// |
152 | /// Generated structures have one static method called parse that accepts a slice of [`Attribute`]s. |
153 | /// The method finds attributes that contain meta lists (look like `#[your_custom_ident(...)]`) and |
154 | /// fills a newly allocated structure with values of the attributes if any. |
155 | /// |
156 | /// The expected input looks as follows: |
157 | /// |
158 | /// ``` |
159 | /// # use zvariant_utils::def_attrs; |
160 | /// def_attrs! { |
161 | /// crate zvariant; |
162 | /// |
163 | /// /// A comment. |
164 | /// pub StructAttributes("struct" ) { foo str, bar str, baz none }; |
165 | /// #[derive(Hash)] |
166 | /// FieldAttributes("field" ) { field_attr bool }; |
167 | /// } |
168 | /// ``` |
169 | /// |
170 | /// Here we see multiple entries: an entry for an attributes group called `StructAttributes` and |
171 | /// another one for `FieldAttributes`. The former has three defined attributes: `foo`, `bar` and |
172 | /// `baz`. The generated structures will look like this in that case: |
173 | /// |
174 | /// ``` |
175 | /// /// A comment. |
176 | /// #[derive(Default, Clone, Debug)] |
177 | /// pub struct StructAttributes { |
178 | /// foo: Option<String>, |
179 | /// bar: Option<String>, |
180 | /// baz: bool, |
181 | /// } |
182 | /// |
183 | /// #[derive(Hash)] |
184 | /// #[derive(Default, Clone, Debug)] |
185 | /// struct FieldAttributes { |
186 | /// field_attr: Option<bool>, |
187 | /// } |
188 | /// ``` |
189 | /// |
190 | /// `foo` and `bar` attributes got translated to fields with `Option<String>` type which contain the |
191 | /// value of the attribute when one is specified. They are marked with `str` keyword which stands |
192 | /// for string literals. The `baz` attribute, on the other hand, has `bool` type because it's an |
193 | /// attribute without value marked by the `none` keyword. |
194 | /// |
195 | /// Currently the following literals are supported: |
196 | /// |
197 | /// * `str` - string literals; |
198 | /// * `bool` - boolean literals; |
199 | /// * `[str]` - lists of string literals (`#[macro_name(foo("bar", "baz"))]`); |
200 | /// * `none` - no literal at all, the attribute is specified alone. |
201 | /// |
202 | /// The strings between braces are embedded into error messages produced when an attribute defined |
203 | /// for one attribute group is used on another group where it is not defined. For example, if the |
204 | /// `field_attr` attribute was encountered by the generated `StructAttributes::parse` method, the |
205 | /// error message would say that it "is not allowed on structs". |
206 | /// |
207 | /// # Nested attribute lists |
208 | /// |
209 | /// It is possible to create nested lists for specific attributes. This is done as follows: |
210 | /// |
211 | /// ``` |
212 | /// # use zvariant_utils::def_attrs; |
213 | /// def_attrs! { |
214 | /// crate zvariant; |
215 | /// |
216 | /// pub OuterAttributes("outer" ) { |
217 | /// simple_attr bool, |
218 | /// nested_attr { |
219 | /// /// An example of nested attributes. |
220 | /// pub InnerAttributes("inner" ) { |
221 | /// inner_attr str |
222 | /// } |
223 | /// } |
224 | /// }; |
225 | /// } |
226 | /// ``` |
227 | /// |
228 | /// The syntax for inner attributes is the same as for the outer attributes, but you can specify |
229 | /// only one inner attribute per outer attribute. |
230 | /// |
231 | /// # Calling the macro multiple times |
232 | /// |
233 | /// The macro generates an array called `ALLOWED_ATTRS` that contains a list of allowed attributes. |
234 | /// Calling the macro twice in the same scope will cause a name alias and thus will fail to compile. |
235 | /// You need to place each macro invocation into a module in that case. |
236 | /// |
237 | /// # Errors |
238 | /// |
239 | /// The generated parse method checks for some error conditions: |
240 | /// |
241 | /// 1. Unknown attributes. When multiple attribute groups are defined in the same macro invocation, |
242 | /// one gets a different error message when providing an attribute from a different attribute group. |
243 | /// 2. Duplicate attributes. |
244 | /// 3. Missing attribute value or present attribute value when none is expected. |
245 | /// 4. Invalid literal type for attributes with values. |
246 | #[macro_export ] |
247 | macro_rules! def_attrs { |
248 | (@attr_ty str) => {::std::option::Option<::std::string::String>}; |
249 | (@attr_ty bool) => {::std::option::Option<bool>}; |
250 | (@attr_ty [str]) => {::std::option::Option<::std::vec::Vec<::std::string::String>>}; |
251 | (@attr_ty none) => {bool}; |
252 | (@attr_ty { |
253 | $(#[$m:meta])* |
254 | $vis:vis $name:ident($what:literal) { |
255 | $($attr_name:ident $kind:tt),+ |
256 | } |
257 | }) => {::std::option::Option<$name>}; |
258 | (@match_attr_with $attr_name:ident, $meta:ident, $self:ident, $matched:expr) => { |
259 | if let ::std::option::Option::Some(value) = $matched? { |
260 | if $self.$attr_name.is_none() { |
261 | $self.$attr_name = ::std::option::Option::Some(value.value()); |
262 | return Ok(()); |
263 | } else { |
264 | return ::std::result::Result::Err(::syn::Error::new( |
265 | $meta.span(), |
266 | ::std::concat!("duplicate `" , ::std::stringify!($attr_name), "` attribute" ) |
267 | )); |
268 | } |
269 | } |
270 | }; |
271 | (@match_attr str $attr_name:ident, $meta:ident, $self:ident) => { |
272 | $crate::def_attrs!( |
273 | @match_attr_with |
274 | $attr_name, |
275 | $meta, |
276 | $self, |
277 | $crate::macros::match_attribute_with_str_value( |
278 | $meta, |
279 | ::std::stringify!($attr_name), |
280 | ) |
281 | ) |
282 | }; |
283 | (@match_attr bool $attr_name:ident, $meta:ident, $self:ident) => { |
284 | $crate::def_attrs!( |
285 | @match_attr_with |
286 | $attr_name, |
287 | $meta, |
288 | $self, |
289 | $crate::macros::match_attribute_with_bool_value( |
290 | $meta, |
291 | ::std::stringify!($attr_name), |
292 | ) |
293 | ) |
294 | }; |
295 | (@match_attr [str] $attr_name:ident, $meta:ident, $self:ident) => { |
296 | if let Some(list) = $crate::macros::match_attribute_with_str_list_value( |
297 | $meta, |
298 | ::std::stringify!($attr_name), |
299 | )? { |
300 | if $self.$attr_name.is_none() { |
301 | $self.$attr_name = Some(list); |
302 | return Ok(()); |
303 | } else { |
304 | return ::std::result::Result::Err(::syn::Error::new( |
305 | $meta.span(), |
306 | concat!("duplicate `" , stringify!($attr_name), "` attribute" ) |
307 | )); |
308 | } |
309 | } |
310 | }; |
311 | (@match_attr none $attr_name:ident, $meta:ident, $self:ident) => { |
312 | if $crate::macros::match_attribute_without_value( |
313 | $meta, |
314 | ::std::stringify!($attr_name), |
315 | )? { |
316 | if !$self.$attr_name { |
317 | $self.$attr_name = true; |
318 | return Ok(()); |
319 | } else { |
320 | return ::std::result::Result::Err(::syn::Error::new( |
321 | $meta.span(), |
322 | concat!("duplicate `" , stringify!($attr_name), "` attribute" ) |
323 | )); |
324 | } |
325 | } |
326 | }; |
327 | (@match_attr { |
328 | $(#[$m:meta])* |
329 | $vis:vis $name:ident($what:literal) $body:tt |
330 | } $attr_name:ident, $meta:expr, $self:ident) => { |
331 | if $meta.path().is_ident(::std::stringify!($attr_name)) { |
332 | return if $self.$attr_name.is_none() { |
333 | match $meta { |
334 | ::syn::Meta::List(meta) => { |
335 | $self.$attr_name = ::std::option::Option::Some($name::parse_nested_metas( |
336 | meta.nested.iter() |
337 | )?); |
338 | ::std::result::Result::Ok(()) |
339 | } |
340 | ::syn::Meta::Path(_) => { |
341 | $self.$attr_name = ::std::option::Option::Some($name::default()); |
342 | ::std::result::Result::Ok(()) |
343 | } |
344 | ::syn::Meta::NameValue(_) => Err(::syn::Error::new( |
345 | $meta.span(), |
346 | ::std::format!(::std::concat!( |
347 | "attribute `" , ::std::stringify!($attr_name), |
348 | "` must be either a list or a path" |
349 | )), |
350 | )) |
351 | } |
352 | } else { |
353 | ::std::result::Result::Err(::syn::Error::new( |
354 | $meta.span(), |
355 | concat!("duplicate `" , stringify!($attr_name), "` attribute" ) |
356 | )) |
357 | } |
358 | } |
359 | }; |
360 | (@def_ty $list_name:ident str) => {}; |
361 | (@def_ty $list_name:ident bool) => {}; |
362 | (@def_ty $list_name:ident [str]) => {}; |
363 | (@def_ty $list_name:ident none) => {}; |
364 | ( |
365 | @def_ty $list_name:ident { |
366 | $(#[$m:meta])* |
367 | $vis:vis $name:ident($what:literal) { |
368 | $($attr_name:ident $kind:tt),+ |
369 | } |
370 | } |
371 | ) => { |
372 | // Recurse further to potentially define nested lists. |
373 | $($crate::def_attrs!(@def_ty $attr_name $kind);)+ |
374 | |
375 | $crate::def_attrs!( |
376 | @def_struct |
377 | $list_name |
378 | $(#[$m])* |
379 | $vis $name($what) { |
380 | $($attr_name $kind),+ |
381 | } |
382 | ); |
383 | }; |
384 | ( |
385 | @def_struct |
386 | $list_name:ident |
387 | $(#[$m:meta])* |
388 | $vis:vis $name:ident($what:literal) { |
389 | $($attr_name:ident $kind:tt),+ |
390 | } |
391 | ) => { |
392 | $(#[$m])* |
393 | #[derive(Default, Clone, Debug)] |
394 | $vis struct $name { |
395 | $(pub $attr_name: $crate::def_attrs!(@attr_ty $kind)),+ |
396 | } |
397 | |
398 | impl $name { |
399 | pub fn parse_meta( |
400 | &mut self, |
401 | meta: &::syn::Meta |
402 | ) -> ::syn::Result<()> { |
403 | use ::syn::spanned::Spanned; |
404 | |
405 | // This creates subsequent if blocks for simplicity. Any block that is taken |
406 | // either returns an error or sets the attribute field and returns success. |
407 | $( |
408 | $crate::def_attrs!(@match_attr $kind $attr_name, meta, self); |
409 | )+ |
410 | |
411 | // None of the if blocks have been taken, return the appropriate error. |
412 | let is_valid_attr = ALLOWED_ATTRS.iter().any(|attr| meta.path().is_ident(attr)); |
413 | return ::std::result::Result::Err(::syn::Error::new(meta.span(), if is_valid_attr { |
414 | ::std::format!( |
415 | ::std::concat!("attribute `{}` is not allowed on " , $what), |
416 | meta.path().get_ident().unwrap() |
417 | ) |
418 | } else { |
419 | ::std::format!("unknown attribute `{}`" , meta.path().get_ident().unwrap()) |
420 | })) |
421 | } |
422 | |
423 | pub fn parse_nested_metas<'a, I>(iter: I) -> syn::Result<Self> |
424 | where |
425 | I: ::std::iter::IntoIterator<Item=&'a ::syn::NestedMeta> |
426 | { |
427 | let mut parsed = $name::default(); |
428 | for nested_meta in iter { |
429 | match nested_meta { |
430 | ::syn::NestedMeta::Meta(meta) => parsed.parse_meta(meta), |
431 | ::syn::NestedMeta::Lit(lit) => { |
432 | ::std::result::Result::Err(::syn::Error::new( |
433 | lit.span(), |
434 | ::std::concat!( |
435 | "attribute `" , ::std::stringify!($list_name), |
436 | "` does not support literals in meta lists" |
437 | ) |
438 | )) |
439 | } |
440 | }?; |
441 | } |
442 | |
443 | Ok(parsed) |
444 | } |
445 | |
446 | pub fn parse(attrs: &[::syn::Attribute]) -> ::syn::Result<Self> { |
447 | let mut parsed = $name::default(); |
448 | for nested_meta in $crate::macros::iter_meta_lists(attrs, ::std::stringify!($list_name))? { |
449 | match &nested_meta { |
450 | ::syn::NestedMeta::Meta(meta) => parsed.parse_meta(meta), |
451 | ::syn::NestedMeta::Lit(lit) => { |
452 | ::std::result::Result::Err(::syn::Error::new( |
453 | lit.span(), |
454 | ::std::concat!( |
455 | "attribute `" , ::std::stringify!($list_name), |
456 | "` does not support literals in meta lists" |
457 | ) |
458 | )) |
459 | } |
460 | }?; |
461 | } |
462 | |
463 | Ok(parsed) |
464 | } |
465 | } |
466 | }; |
467 | ( |
468 | crate $list_name:ident; |
469 | $( |
470 | $(#[$m:meta])* |
471 | $vis:vis $name:ident($what:literal) { |
472 | $($attr_name:ident $kind:tt),+ |
473 | } |
474 | );+; |
475 | ) => { |
476 | static ALLOWED_ATTRS: &[&'static str] = &[ |
477 | $($(::std::stringify!($attr_name),)+)+ |
478 | ]; |
479 | |
480 | $( |
481 | $crate::def_attrs!( |
482 | @def_ty |
483 | $list_name { |
484 | $(#[$m])* |
485 | $vis $name($what) { |
486 | $($attr_name $kind),+ |
487 | } |
488 | } |
489 | ); |
490 | )+ |
491 | } |
492 | } |
493 | |
494 | /// Checks if a [`Type`]'s identifier is "Option". |
495 | pub fn ty_is_option(ty: &Type) -> bool { |
496 | match ty { |
497 | Type::Path(TypePath { |
498 | path: syn::Path { segments: &Punctuated, .. }, |
499 | .. |
500 | }) => segments.last().unwrap().ident == "Option" , |
501 | _ => false, |
502 | } |
503 | } |
504 | |