1use syn::{
2 spanned::Spanned, Attribute, Lit, LitBool, LitStr, Meta, MetaList, NestedMeta, Result, Type,
3 TypePath,
4};
5
6fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result<Option<MetaList>> {
8 let meta = match attrs.iter().find(|a| a.path.is_ident(attr_name)) {
9 Some(a) => a.parse_meta(),
10 _ => return Ok(None),
11 }?;
12 match meta {
13 Meta::List(n) => Ok(Some(n)),
14 _ => Err(syn::Error::new(
15 meta.span(),
16 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) => Ok(&meta.lit),
24 Meta::Path(_) => Err(syn::Error::new(
25 meta.span(),
26 format!("attribute `{attr}` must have a value"),
27 )),
28 Meta::List(_) => Err(syn::Error::new(
29 meta.span(),
30 format!("attribute {attr} is not a list"),
31 )),
32 }
33}
34
35pub 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) => Ok(Some(value)),
49 _ => Err(syn::Error::new(
50 meta.span(),
51 format!("value of the `{attr}` attribute must be a string literal"),
52 )),
53 }
54 } else {
55 Ok(None)
56 }
57}
58
59pub 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) => Ok(Some(value)),
73 other => Err(syn::Error::new(
74 other.span(),
75 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
115pub 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 format!("attribute {attr} is not a list"),
128 )),
129 Meta::NameValue(_) => Err(syn::Error::new(
130 meta.span(),
131 format!("attribute `{attr}` must not have a value"),
132 )),
133 }
134 } else {
135 Ok(false)
136 }
137}
138
139pub fn iter_meta_lists(
142 attrs: &[Attribute],
143 list_name: &str,
144) -> Result<impl Iterator<Item = NestedMeta>> {
145 let meta = find_attribute_meta(attrs, list_name)?;
146
147 Ok(meta.into_iter().flat_map(|meta| meta.nested.into_iter()))
148}
149
150#[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 $($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 $(
408 $crate::def_attrs!(@match_attr $kind $attr_name, meta, self);
409 )+
410
411 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
494pub fn ty_is_option(ty: &Type) -> bool {
496 match ty {
497 Type::Path(TypePath {
498 path: syn::Path { segments, .. },
499 ..
500 }) => segments.last().unwrap().ident == "Option",
501 _ => false,
502 }
503}