devise_core/from_meta/
meta_item.rs

1use std::convert::TryFrom;
2
3use quote::{ToTokens, TokenStreamExt};
4use proc_macro2::{Span, TokenStream, TokenTree};
5use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
6use syn::{self, punctuated::Punctuated, spanned::Spanned, parse::{Parse, Parser}};
7
8use generator::Result;
9
10#[derive(Debug, Clone)]
11pub enum MetaItem {
12    Path(syn::Path),
13    Tokens(TokenStream),
14    KeyValue {
15        path: syn::Path,
16        eq: syn::Token![=],
17        tokens: TokenStream,
18    },
19    List {
20        path: syn::Path,
21        delimiter: syn::MacroDelimiter,
22        items: Punctuated<MetaItem, syn::token::Comma>
23    }
24}
25
26fn parse_delimited_tokens(input: syn::parse::ParseStream) -> syn::Result<TokenStream> {
27    input.step(|cursor| {
28        let mut stream = TokenStream::new();
29        let mut rest = *cursor;
30        while let Some((tt, next)) = rest.token_tree() {
31            if matches!(&tt, TokenTree::Punct(p) if p.as_char() == ',') {
32                return Ok((stream, rest));
33            }
34
35            rest = next;
36            stream.append(tt);
37        }
38
39        Ok((stream, rest))
40    })
41}
42
43macro_rules! macro_delimited {
44    ($a:ident in $input:ident) => {
45        if $input.peek(syn::token::Brace) {
46            syn::MacroDelimiter::Brace(syn::braced!($a in $input))
47        } else if $input.peek(syn::token::Bracket) {
48            syn::MacroDelimiter::Bracket(syn::bracketed!($a in $input))
49        } else {
50            syn::MacroDelimiter::Paren(syn::parenthesized!($a in $input))
51        }
52    };
53}
54
55impl syn::parse::Parse for MetaItem {
56    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
57        let item = if let Ok(path) = input.parse::<syn::Path>() {
58            if input.peek(syn::token::Paren) {
59                let list;
60                MetaItem::List {
61                    path,
62                    delimiter: macro_delimited!(list in input),
63                    items: list.parse_terminated(Self::parse, syn::Token![,])?
64                }
65            } else if input.peek(syn::Token![=]) {
66                MetaItem::KeyValue {
67                    path,
68                    eq: input.parse()?,
69                    tokens: parse_delimited_tokens(input)?,
70                }
71            } else {
72                MetaItem::Path(path)
73            }
74        } else {
75            MetaItem::Tokens(parse_delimited_tokens(input)?)
76        };
77
78        Ok(item)
79    }
80}
81
82impl TryFrom<syn::Meta> for MetaItem {
83    type Error = syn::Error;
84
85    fn try_from(value: syn::Meta) -> std::result::Result<Self, Self::Error> {
86        let item = match value {
87            syn::Meta::Path(path) => MetaItem::Path(path),
88            syn::Meta::List(list) => MetaItem::List {
89                path: list.path,
90                delimiter: list.delimiter,
91                items: <Punctuated<Self, syn::Token![,]>>::parse_terminated.parse2(list.tokens)?,
92            },
93            syn::Meta::NameValue(nv) => MetaItem::KeyValue {
94                path: nv.path,
95                eq: nv.eq_token,
96                tokens: parse_delimited_tokens.parse2(nv.value.to_token_stream())?,
97            }
98        };
99
100        Ok(item)
101    }
102}
103
104impl MetaItem {
105    pub fn attr_path(&self) -> Option<&syn::Path> {
106        use MetaItem::*;
107
108        match self {
109            Path(p) => Some(p),
110            KeyValue { path, .. } => Some(path),
111            List { path, .. } => Some(path),
112            _ => None
113        }
114    }
115
116    pub fn name(&self) -> Option<&syn::Ident> {
117        let path = self.attr_path()?;
118        path.segments.last().map(|l| &l.ident)
119    }
120
121    pub fn tokens(&self) -> Option<&TokenStream> {
122        match self {
123            MetaItem::Tokens(tokens) | MetaItem::KeyValue { tokens, .. } => Some(tokens),
124            _ => None
125        }
126    }
127
128    pub fn parse_value<T: Parse>(&self, expected: &str) -> Result<T> {
129        let tokens = self.tokens().ok_or_else(|| self.expected(expected))?;
130        syn::parse2(tokens.clone())
131            .map_err(|e| e.span().error(format!("failed to parse {}: {}", expected, e)))
132    }
133
134    pub fn parse_value_with<P: Parser>(&self, parser: P, expected: &str) -> Result<P::Output> {
135        match self {
136            MetaItem::Tokens(tokens) | MetaItem::KeyValue { tokens, .. } => {
137                parser.parse2(tokens.clone()).map_err(|e| {
138                    e.span().error(format!("failed to parse {}: {}", expected, e))
139                })
140            },
141            _ => Err(self.expected(expected))
142        }
143    }
144
145    pub fn expected(&self, k: &str) -> Diagnostic {
146        let bare = self.is_bare().then_some("bare ").unwrap_or("");
147        let msg = match self.name().map(|i| i.to_string()) {
148            Some(n) => format!("expected {}, found {}{} {:?}", k, bare, self.description(), n),
149            None => format!("expected {}, found {}{}", k, bare, self.description()),
150        };
151
152        self.span().error(msg)
153    }
154
155    pub fn description(&self) -> &'static str {
156        let expr = self.tokens().and_then(|t| syn::parse2::<syn::Expr>(t.clone()).ok());
157        if let Some(syn::Expr::Lit(e)) = expr {
158            match e.lit {
159                syn::Lit::Str(..) => "string literal",
160                syn::Lit::ByteStr(..) => "byte string literal",
161                syn::Lit::Byte(..) => "byte literal",
162                syn::Lit::Char(..) => "character literal",
163                syn::Lit::Int(..) => "integer literal",
164                syn::Lit::Float(..) => "float literal",
165                syn::Lit::Bool(..) => "boolean literal",
166                syn::Lit::Verbatim(..) => "literal",
167                _ => "unknown literal"
168            }
169        } else if expr.is_some() {
170            "non-literal expression"
171        } else {
172            match self {
173                MetaItem::Tokens(..) => "tokens",
174                MetaItem::KeyValue { .. } => "key/value pair",
175                MetaItem::List { .. } => "list",
176                MetaItem::Path(_) => "path",
177            }
178        }
179    }
180
181    pub fn is_bare(&self) -> bool {
182        match self {
183            MetaItem::Path(..) | MetaItem::Tokens(..) => true,
184            MetaItem::KeyValue { .. } | MetaItem::List { .. } => false,
185        }
186    }
187
188    pub fn expr(&self) -> Result<syn::Expr> {
189        self.parse_value("expression")
190    }
191
192    pub fn path(&self) -> Result<syn::Path> {
193        match self {
194            MetaItem::Path(p) => Ok(p.clone()),
195            _ => self.parse_value("path")
196        }
197    }
198
199    pub fn lit(&self) -> Result<syn::Lit> {
200        fn from_expr(meta: &MetaItem, expr: syn::Expr) -> Result<syn::Lit> {
201            match expr {
202                syn::Expr::Lit(e) => Ok(e.lit),
203                syn::Expr::Group(g) => from_expr(meta, *g.expr),
204                _ => Err(meta.expected("literal")),
205            }
206        }
207
208        self.parse_value("literal").and_then(|e| from_expr(self, e))
209    }
210
211    pub fn list(&self) -> Result<impl Iterator<Item = &MetaItem> + Clone> {
212        match self {
213            MetaItem::List { items, .. } => Ok(items.iter()),
214            _ => {
215                let n = self.name().map(|i| i.to_string()).unwrap_or_else(|| "attr".into());
216                Err(self.expected(&format!("list `#[{}(..)]`", n)))
217            }
218        }
219    }
220
221    pub fn value_span(&self) -> Span {
222        match self {
223            MetaItem::KeyValue { tokens, .. } => tokens.span(),
224            _ => self.span(),
225        }
226    }
227}
228
229impl ToTokens for MetaItem {
230    fn to_tokens(&self, stream: &mut TokenStream) {
231        match self {
232            MetaItem::Path(p) => p.to_tokens(stream),
233            MetaItem::Tokens(tokens) => stream.append_all(tokens.clone()),
234            MetaItem::KeyValue { path, eq, tokens } => {
235                path.to_tokens(stream);
236                eq.to_tokens(stream);
237                stream.append_all(tokens.clone());
238            }
239            MetaItem::List { path, delimiter, items } => {
240                use syn::MacroDelimiter::*;
241
242                path.to_tokens(stream);
243                match delimiter {
244                    Paren(p) => p.surround(stream, |t| items.to_tokens(t)),
245                    Brace(b) => b.surround(stream, |t| items.to_tokens(t)),
246                    Bracket(b) => b.surround(stream, |t| items.to_tokens(t)),
247                }
248            }
249        }
250    }
251}