1 // Copyright 2018 Syn Developers
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8 
9 use super::*;
10 use punctuated::Punctuated;
11 
12 use std::iter;
13 
14 use proc_macro2::{Delimiter, Spacing, TokenStream, TokenTree};
15 
16 #[cfg(feature = "extra-traits")]
17 use std::hash::{Hash, Hasher};
18 #[cfg(feature = "extra-traits")]
19 use tt::TokenStreamHelper;
20 
21 ast_struct! {
22     /// An attribute like `#[repr(transparent)]`.
23     ///
24     /// *This type is available if Syn is built with the `"derive"` or `"full"`
25     /// feature.*
26     ///
27     /// # Syntax
28     ///
29     /// Rust has six types of attributes.
30     ///
31     /// - Outer attributes like `#[repr(transparent)]`. These appear outside or
32     ///   in front of the item they describe.
33     /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside
34     ///   of the item they describe, usually a module.
35     /// - Outer doc comments like `/// # Example`.
36     /// - Inner doc comments like `//! Please file an issue`.
37     /// - Outer block comments `/** # Example */`.
38     /// - Inner block comments `/*! Please file an issue */`.
39     ///
40     /// The `style` field of type `AttrStyle` distinguishes whether an attribute
41     /// is outer or inner. Doc comments and block comments are promoted to
42     /// attributes that have `is_sugared_doc` set to true, as this is how they
43     /// are processed by the compiler and by `macro_rules!` macros.
44     ///
45     /// The `path` field gives the possibly colon-delimited path against which
46     /// the attribute is resolved. It is equal to `"doc"` for desugared doc
47     /// comments. The `tts` field contains the rest of the attribute body as
48     /// tokens.
49     ///
50     /// ```text
51     /// #[derive(Copy)]      #[crate::precondition x < 5]
52     ///   ^^^^^^~~~~~~         ^^^^^^^^^^^^^^^^^^^ ~~~~~
53     ///    path  tts                   path         tts
54     /// ```
55     ///
56     /// Use the [`interpret_meta`] method to try parsing the tokens of an
57     /// attribute into the structured representation that is used by convention
58     /// across most Rust libraries.
59     ///
60     /// [`interpret_meta`]: #method.interpret_meta
61     pub struct Attribute #manual_extra_traits {
62         pub pound_token: Token![#],
63         pub style: AttrStyle,
64         pub bracket_token: token::Bracket,
65         pub path: Path,
66         pub tts: TokenStream,
67         pub is_sugared_doc: bool,
68     }
69 }
70 
71 #[cfg(feature = "extra-traits")]
72 impl Eq for Attribute {}
73 
74 #[cfg(feature = "extra-traits")]
75 impl PartialEq for Attribute {
eq(&self, other: &Self) -> bool76     fn eq(&self, other: &Self) -> bool {
77         self.style == other.style
78             && self.pound_token == other.pound_token
79             && self.bracket_token == other.bracket_token
80             && self.path == other.path
81             && TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
82             && self.is_sugared_doc == other.is_sugared_doc
83     }
84 }
85 
86 #[cfg(feature = "extra-traits")]
87 impl Hash for Attribute {
hash<H>(&self, state: &mut H) where H: Hasher,88     fn hash<H>(&self, state: &mut H)
89     where
90         H: Hasher,
91     {
92         self.style.hash(state);
93         self.pound_token.hash(state);
94         self.bracket_token.hash(state);
95         self.path.hash(state);
96         TokenStreamHelper(&self.tts).hash(state);
97         self.is_sugared_doc.hash(state);
98     }
99 }
100 
101 impl Attribute {
102     /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
103     /// possible.
interpret_meta(&self) -> Option<Meta>104     pub fn interpret_meta(&self) -> Option<Meta> {
105         let name = if self.path.segments.len() == 1 {
106             &self.path.segments.first().unwrap().value().ident
107         } else {
108             return None;
109         };
110 
111         if self.tts.is_empty() {
112             return Some(Meta::Word(name.clone()));
113         }
114 
115         let tts = self.tts.clone().into_iter().collect::<Vec<_>>();
116 
117         if tts.len() == 1 {
118             if let Some(meta) = Attribute::extract_meta_list(name.clone(), &tts[0]) {
119                 return Some(meta);
120             }
121         }
122 
123         if tts.len() == 2 {
124             if let Some(meta) = Attribute::extract_name_value(name.clone(), &tts[0], &tts[1]) {
125                 return Some(meta);
126             }
127         }
128 
129         None
130     }
131 
extract_meta_list(ident: Ident, tt: &TokenTree) -> Option<Meta>132     fn extract_meta_list(ident: Ident, tt: &TokenTree) -> Option<Meta> {
133         let g = match *tt {
134             TokenTree::Group(ref g) => g,
135             _ => return None,
136         };
137         if g.delimiter() != Delimiter::Parenthesis {
138             return None;
139         }
140         let tokens = g.stream().clone().into_iter().collect::<Vec<_>>();
141         let nested = match list_of_nested_meta_items_from_tokens(&tokens) {
142             Some(n) => n,
143             None => return None,
144         };
145         Some(Meta::List(MetaList {
146             paren_token: token::Paren(g.span()),
147             ident: ident,
148             nested: nested,
149         }))
150     }
151 
extract_name_value(ident: Ident, a: &TokenTree, b: &TokenTree) -> Option<Meta>152     fn extract_name_value(ident: Ident, a: &TokenTree, b: &TokenTree) -> Option<Meta> {
153         let a = match *a {
154             TokenTree::Punct(ref o) => o,
155             _ => return None,
156         };
157         if a.spacing() != Spacing::Alone {
158             return None;
159         }
160         if a.as_char() != '=' {
161             return None;
162         }
163 
164         match *b {
165             TokenTree::Literal(ref l) if !l.to_string().starts_with('/') => {
166                 Some(Meta::NameValue(MetaNameValue {
167                     ident: ident,
168                     eq_token: Token![=]([a.span()]),
169                     lit: Lit::new(l.clone()),
170                 }))
171             }
172             TokenTree::Ident(ref v) => match &v.to_string()[..] {
173                 v @ "true" | v @ "false" => Some(Meta::NameValue(MetaNameValue {
174                     ident: ident,
175                     eq_token: Token![=]([a.span()]),
176                     lit: Lit::Bool(LitBool {
177                         value: v == "true",
178                         span: b.span(),
179                     }),
180                 })),
181                 _ => None,
182             },
183             _ => None,
184         }
185     }
186 }
187 
nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])>188 fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
189     assert!(!tts.is_empty());
190 
191     match tts[0] {
192         TokenTree::Literal(ref lit) => {
193             if lit.to_string().starts_with('/') {
194                 None
195             } else {
196                 let lit = Lit::new(lit.clone());
197                 Some((NestedMeta::Literal(lit), &tts[1..]))
198             }
199         }
200 
201         TokenTree::Ident(ref ident) => {
202             if tts.len() >= 3 {
203                 if let Some(meta) = Attribute::extract_name_value(ident.clone(), &tts[1], &tts[2]) {
204                     return Some((NestedMeta::Meta(meta), &tts[3..]));
205                 }
206             }
207 
208             if tts.len() >= 2 {
209                 if let Some(meta) = Attribute::extract_meta_list(ident.clone(), &tts[1]) {
210                     return Some((NestedMeta::Meta(meta), &tts[2..]));
211                 }
212             }
213 
214             Some((Meta::Word(ident.clone()).into(), &tts[1..]))
215         }
216 
217         _ => None,
218     }
219 }
220 
list_of_nested_meta_items_from_tokens( mut tts: &[TokenTree], ) -> Option<Punctuated<NestedMeta, Token![,]>>221 fn list_of_nested_meta_items_from_tokens(
222     mut tts: &[TokenTree],
223 ) -> Option<Punctuated<NestedMeta, Token![,]>> {
224     let mut nested_meta_items = Punctuated::new();
225     let mut first = true;
226 
227     while !tts.is_empty() {
228         let prev_comma = if first {
229             first = false;
230             None
231         } else if let TokenTree::Punct(ref op) = tts[0] {
232             if op.spacing() != Spacing::Alone {
233                 return None;
234             }
235             if op.as_char() != ',' {
236                 return None;
237             }
238             let tok = Token![,]([op.span()]);
239             tts = &tts[1..];
240             if tts.is_empty() {
241                 break;
242             }
243             Some(tok)
244         } else {
245             return None;
246         };
247         let (nested, rest) = match nested_meta_item_from_tokens(tts) {
248             Some(pair) => pair,
249             None => return None,
250         };
251         if let Some(comma) = prev_comma {
252             nested_meta_items.push_punct(comma);
253         }
254         nested_meta_items.push_value(nested);
255         tts = rest;
256     }
257 
258     Some(nested_meta_items)
259 }
260 
261 ast_enum! {
262     /// Distinguishes between attributes that decorate an item and attributes
263     /// that are contained within an item.
264     ///
265     /// *This type is available if Syn is built with the `"derive"` or `"full"`
266     /// feature.*
267     ///
268     /// # Outer attributes
269     ///
270     /// - `#[repr(transparent)]`
271     /// - `/// # Example`
272     /// - `/** Please file an issue */`
273     ///
274     /// # Inner attributes
275     ///
276     /// - `#![feature(proc_macro)]`
277     /// - `//! # Example`
278     /// - `/*! Please file an issue */`
279     #[cfg_attr(feature = "clone-impls", derive(Copy))]
280     pub enum AttrStyle {
281         Outer,
282         Inner(Token![!]),
283     }
284 }
285 
286 ast_enum_of_structs! {
287     /// Content of a compile-time structured attribute.
288     ///
289     /// *This type is available if Syn is built with the `"derive"` or `"full"`
290     /// feature.*
291     ///
292     /// ## Word
293     ///
294     /// A meta word is like the `test` in `#[test]`.
295     ///
296     /// ## List
297     ///
298     /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`.
299     ///
300     /// ## NameValue
301     ///
302     /// A name-value meta is like the `path = "..."` in `#[path =
303     /// "sys/windows.rs"]`.
304     ///
305     /// # Syntax tree enum
306     ///
307     /// This type is a [syntax tree enum].
308     ///
309     /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
310     pub enum Meta {
311         pub Word(Ident),
312         /// A structured list within an attribute, like `derive(Copy, Clone)`.
313         ///
314         /// *This type is available if Syn is built with the `"derive"` or
315         /// `"full"` feature.*
316         pub List(MetaList {
317             pub ident: Ident,
318             pub paren_token: token::Paren,
319             pub nested: Punctuated<NestedMeta, Token![,]>,
320         }),
321         /// A name-value pair within an attribute, like `feature = "nightly"`.
322         ///
323         /// *This type is available if Syn is built with the `"derive"` or
324         /// `"full"` feature.*
325         pub NameValue(MetaNameValue {
326             pub ident: Ident,
327             pub eq_token: Token![=],
328             pub lit: Lit,
329         }),
330     }
331 }
332 
333 impl Meta {
334     /// Returns the identifier that begins this structured meta item.
335     ///
336     /// For example this would return the `test` in `#[test]`, the `derive` in
337     /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
name(&self) -> Ident338     pub fn name(&self) -> Ident {
339         match *self {
340             Meta::Word(ref meta) => meta.clone(),
341             Meta::List(ref meta) => meta.ident.clone(),
342             Meta::NameValue(ref meta) => meta.ident.clone(),
343         }
344     }
345 }
346 
347 ast_enum_of_structs! {
348     /// Element of a compile-time attribute list.
349     ///
350     /// *This type is available if Syn is built with the `"derive"` or `"full"`
351     /// feature.*
352     pub enum NestedMeta {
353         /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
354         /// would be a nested `Meta::Word`.
355         pub Meta(Meta),
356 
357         /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
358         pub Literal(Lit),
359     }
360 }
361 
362 pub trait FilterAttrs<'a> {
363     type Ret: Iterator<Item = &'a Attribute>;
364 
outer(self) -> Self::Ret365     fn outer(self) -> Self::Ret;
inner(self) -> Self::Ret366     fn inner(self) -> Self::Ret;
367 }
368 
369 impl<'a, T> FilterAttrs<'a> for T
370 where
371     T: IntoIterator<Item = &'a Attribute>,
372 {
373     type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
374 
outer(self) -> Self::Ret375     fn outer(self) -> Self::Ret {
376         fn is_outer(attr: &&Attribute) -> bool {
377             match attr.style {
378                 AttrStyle::Outer => true,
379                 _ => false,
380             }
381         }
382         self.into_iter().filter(is_outer)
383     }
384 
inner(self) -> Self::Ret385     fn inner(self) -> Self::Ret {
386         fn is_inner(attr: &&Attribute) -> bool {
387             match attr.style {
388                 AttrStyle::Inner(_) => true,
389                 _ => false,
390             }
391         }
392         self.into_iter().filter(is_inner)
393     }
394 }
395 
396 #[cfg(feature = "parsing")]
397 pub mod parsing {
398     use super::*;
399     use buffer::Cursor;
400     use parse_error;
401     use proc_macro2::{Literal, Punct, Spacing, Span, TokenTree};
402     use synom::PResult;
403 
eq(span: Span) -> TokenTree404     fn eq(span: Span) -> TokenTree {
405         let mut op = Punct::new('=', Spacing::Alone);
406         op.set_span(span);
407         op.into()
408     }
409 
410     impl Attribute {
411         named!(pub parse_inner -> Self, alt!(
412             do_parse!(
413                 pound: punct!(#) >>
414                 bang: punct!(!) >>
415                 path_and_tts: brackets!(tuple!(
416                     call!(Path::parse_mod_style),
417                     syn!(TokenStream),
418                 )) >>
419                 ({
420                     let (bracket, (path, tts)) = path_and_tts;
421 
422                     Attribute {
423                         style: AttrStyle::Inner(bang),
424                         path: path,
425                         tts: tts,
426                         is_sugared_doc: false,
427                         pound_token: pound,
428                         bracket_token: bracket,
429                     }
430                 })
431             )
432             |
433             map!(
434                 call!(lit_doc_comment, Comment::Inner),
435                 |lit| {
436                     let span = lit.span();
437                     Attribute {
438                         style: AttrStyle::Inner(<Token![!]>::new(span)),
439                         path: Ident::new("doc", span).into(),
440                         tts: vec![
441                             eq(span),
442                             lit,
443                         ].into_iter().collect(),
444                         is_sugared_doc: true,
445                         pound_token: <Token![#]>::new(span),
446                         bracket_token: token::Bracket(span),
447                     }
448                 }
449             )
450         ));
451 
452         named!(pub parse_outer -> Self, alt!(
453             do_parse!(
454                 pound: punct!(#) >>
455                 path_and_tts: brackets!(tuple!(
456                     call!(Path::parse_mod_style),
457                     syn!(TokenStream),
458                 )) >>
459                 ({
460                     let (bracket, (path, tts)) = path_and_tts;
461 
462                     Attribute {
463                         style: AttrStyle::Outer,
464                         path: path,
465                         tts: tts,
466                         is_sugared_doc: false,
467                         pound_token: pound,
468                         bracket_token: bracket,
469                     }
470                 })
471             )
472             |
473             map!(
474                 call!(lit_doc_comment, Comment::Outer),
475                 |lit| {
476                     let span = lit.span();
477                     Attribute {
478                         style: AttrStyle::Outer,
479                         path: Ident::new("doc", span).into(),
480                         tts: vec![
481                             eq(span),
482                             lit,
483                         ].into_iter().collect(),
484                         is_sugared_doc: true,
485                         pound_token: <Token![#]>::new(span),
486                         bracket_token: token::Bracket(span),
487                     }
488                 }
489             )
490         ));
491     }
492 
493     enum Comment {
494         Inner,
495         Outer,
496     }
497 
lit_doc_comment(input: Cursor, style: Comment) -> PResult<TokenTree>498     fn lit_doc_comment(input: Cursor, style: Comment) -> PResult<TokenTree> {
499         match input.literal() {
500             Some((lit, rest)) => {
501                 let string = lit.to_string();
502                 let ok = match style {
503                     Comment::Inner => string.starts_with("//!") || string.starts_with("/*!"),
504                     Comment::Outer => string.starts_with("///") || string.starts_with("/**"),
505                 };
506                 if ok {
507                     let mut new = Literal::string(&string);
508                     new.set_span(lit.span());
509                     Ok((new.into(), rest))
510                 } else {
511                     parse_error()
512                 }
513             }
514             _ => parse_error(),
515         }
516     }
517 }
518 
519 #[cfg(feature = "printing")]
520 mod printing {
521     use super::*;
522     use proc_macro2::TokenStream;
523     use quote::ToTokens;
524 
525     impl ToTokens for Attribute {
to_tokens(&self, tokens: &mut TokenStream)526         fn to_tokens(&self, tokens: &mut TokenStream) {
527             self.pound_token.to_tokens(tokens);
528             if let AttrStyle::Inner(ref b) = self.style {
529                 b.to_tokens(tokens);
530             }
531             self.bracket_token.surround(tokens, |tokens| {
532                 self.path.to_tokens(tokens);
533                 self.tts.to_tokens(tokens);
534             });
535         }
536     }
537 
538     impl ToTokens for MetaList {
to_tokens(&self, tokens: &mut TokenStream)539         fn to_tokens(&self, tokens: &mut TokenStream) {
540             self.ident.to_tokens(tokens);
541             self.paren_token.surround(tokens, |tokens| {
542                 self.nested.to_tokens(tokens);
543             })
544         }
545     }
546 
547     impl ToTokens for MetaNameValue {
to_tokens(&self, tokens: &mut TokenStream)548         fn to_tokens(&self, tokens: &mut TokenStream) {
549             self.ident.to_tokens(tokens);
550             self.eq_token.to_tokens(tokens);
551             self.lit.to_tokens(tokens);
552         }
553     }
554 }
555