1use 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
7fn 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
21fn 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`].
42pub 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`].
66pub 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
83pub 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`.
121pub 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.
141pub 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]
247macro_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".
495pub 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