1use crate::{Error, Result};
2use std::fmt;
3use syn::punctuated::Pair;
4use syn::spanned::Spanned;
5use syn::{token, Attribute, Meta, MetaList, Path};
6
7/// Try to parse an attribute into a meta list. Path-type meta values are accepted and returned
8/// as empty lists with their passed-in path. Name-value meta values and non-meta attributes
9/// will cause errors to be returned.
10pub fn parse_attribute_to_meta_list(attr: &Attribute) -> Result<MetaList> {
11 match &attr.meta {
12 Meta::List(list: &MetaList) => Ok(list.clone()),
13 Meta::NameValue(nv: &MetaNameValue) => Err(Error::custom(format!(
14 "Name-value arguments are not supported. Use #[{}(...)]",
15 DisplayPath(&nv.path)
16 ))
17 .with_span(&nv)),
18 Meta::Path(path: &Path) => Ok(MetaList {
19 path: path.clone(),
20 delimiter: syn::MacroDelimiter::Paren(token::Paren {
21 span: {
22 let mut group: Group = proc_macro2::Group::new(
23 proc_macro2::Delimiter::None,
24 stream:proc_macro2::TokenStream::new(),
25 );
26 group.set_span(attr.span());
27 group.delim_span()
28 },
29 }),
30 tokens: Default::default(),
31 }),
32 }
33}
34
35struct DisplayPath<'a>(&'a Path);
36
37impl fmt::Display for DisplayPath<'_> {
38 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39 let path: &Path = self.0;
40 if path.leading_colon.is_some() {
41 write!(f, "::")?;
42 }
43 for segment: Pair<&PathSegment, &PathSep> in path.segments.pairs() {
44 match segment {
45 Pair::Punctuated(segment: &PathSegment, _) => write!(f, "{}::", segment.ident)?,
46 Pair::End(segment: &PathSegment) => segment.ident.fmt(f)?,
47 }
48 }
49
50 Ok(())
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::parse_attribute_to_meta_list;
57 use crate::ast::NestedMeta;
58 use syn::spanned::Spanned;
59 use syn::{parse_quote, Ident};
60
61 #[test]
62 fn parse_list() {
63 let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar(baz = 4)])).unwrap();
64 let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap();
65 assert_eq!(nested_meta.len(), 1);
66 }
67
68 #[test]
69 fn parse_path_returns_empty_list() {
70 let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar])).unwrap();
71 let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap();
72 assert!(meta.path.is_ident(&Ident::new("bar", meta.path.span())));
73 assert!(nested_meta.is_empty());
74 }
75
76 #[test]
77 fn parse_name_value_returns_error() {
78 parse_attribute_to_meta_list(&parse_quote!(#[bar = 4])).unwrap_err();
79 }
80
81 #[test]
82 fn parse_name_value_error_includes_example() {
83 let err = parse_attribute_to_meta_list(&parse_quote!(#[bar = 4])).unwrap_err();
84 assert!(err.to_string().contains("#[bar(...)]"));
85 }
86}
87