standalone_syn/
attr.rs

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
9use super::*;
10use punctuated::Punctuated;
11
12use std::iter;
13
14use proc_macro2::{Delimiter, Spacing, TokenNode, TokenStream, TokenTree};
15
16#[cfg(feature = "extra-traits")]
17use std::hash::{Hash, Hasher};
18#[cfg(feature = "extra-traits")]
19use tt::TokenStreamHelper;
20
21ast_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")]
72impl Eq for Attribute {}
73
74#[cfg(feature = "extra-traits")]
75impl PartialEq for Attribute {
76    fn eq(&self, other: &Self) -> bool {
77        self.style == other.style && self.pound_token == other.pound_token
78            && self.bracket_token == other.bracket_token && self.path == other.path
79            && TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
80            && self.is_sugared_doc == other.is_sugared_doc
81    }
82}
83
84#[cfg(feature = "extra-traits")]
85impl Hash for Attribute {
86    fn hash<H>(&self, state: &mut H)
87    where
88        H: Hasher,
89    {
90        self.style.hash(state);
91        self.pound_token.hash(state);
92        self.bracket_token.hash(state);
93        self.path.hash(state);
94        TokenStreamHelper(&self.tts).hash(state);
95        self.is_sugared_doc.hash(state);
96    }
97}
98
99impl Attribute {
100    /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
101    /// possible.
102    pub fn interpret_meta(&self) -> Option<Meta> {
103        let name = if self.path.segments.len() == 1 {
104            &self.path.segments.first().unwrap().value().ident
105        } else {
106            return None;
107        };
108
109        if self.tts.is_empty() {
110            return Some(Meta::Word(*name));
111        }
112
113        let tts = self.tts.clone().into_iter().collect::<Vec<_>>();
114
115        if tts.len() == 1 {
116            if let TokenNode::Group(Delimiter::Parenthesis, ref ts) = tts[0].kind {
117                let tokens = ts.clone().into_iter().collect::<Vec<_>>();
118                if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(&tokens) {
119                    return Some(Meta::List(MetaList {
120                        paren_token: token::Paren(tts[0].span),
121                        ident: *name,
122                        nested: nested_meta_items,
123                    }));
124                }
125            }
126        }
127
128        if tts.len() == 2 {
129            if let TokenNode::Op('=', Spacing::Alone) = tts[0].kind {
130                if let TokenNode::Literal(ref lit) = tts[1].kind {
131                    if !lit.to_string().starts_with('/') {
132                        return Some(Meta::NameValue(MetaNameValue {
133                            ident: *name,
134                            eq_token: Token![=]([tts[0].span]),
135                            lit: Lit::new(lit.clone(), tts[1].span),
136                        }));
137                    }
138                } else if let TokenNode::Term(ref term) = tts[1].kind {
139                    match term.as_str() {
140                        v @ "true" | v @ "false" => {
141                            return Some(Meta::NameValue(MetaNameValue {
142                                ident: *name,
143                                eq_token: Token![=]([tts[0].span]),
144                                lit: Lit::Bool(LitBool { value: v == "true", span: tts[1].span }),
145                            }));
146                        },
147                        _ => {}
148                    }
149                }
150            }
151        }
152
153        None
154    }
155}
156
157fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
158    assert!(!tts.is_empty());
159
160    match tts[0].kind {
161        TokenNode::Literal(ref lit) => {
162            if lit.to_string().starts_with('/') {
163                None
164            } else {
165                let lit = Lit::new(lit.clone(), tts[0].span);
166                Some((NestedMeta::Literal(lit), &tts[1..]))
167            }
168        }
169
170        TokenNode::Term(sym) => {
171            let ident = Ident::new(sym.as_str(), tts[0].span);
172            if tts.len() >= 3 {
173                if let TokenNode::Op('=', Spacing::Alone) = tts[1].kind {
174                    if let TokenNode::Literal(ref lit) = tts[2].kind {
175                        if !lit.to_string().starts_with('/') {
176                            let pair = MetaNameValue {
177                                ident: Ident::new(sym.as_str(), tts[0].span),
178                                eq_token: Token![=]([tts[1].span]),
179                                lit: Lit::new(lit.clone(), tts[2].span),
180                            };
181                            return Some((Meta::NameValue(pair).into(), &tts[3..]));
182                        }
183                    } else if let TokenNode::Term(ref term) = tts[2].kind {
184                        match term.as_str() {
185                            v @ "true" | v @ "false" => {
186                                let pair = MetaNameValue {
187                                    ident: Ident::new(sym.as_str(), tts[0].span),
188                                    eq_token: Token![=]([tts[1].span]),
189                                    lit: Lit::Bool(LitBool { value: v == "true", span: tts[2].span }),
190                                };
191                                return Some((Meta::NameValue(pair).into(), &tts[3..]));
192                            },
193                            _ => {}
194                        }
195                    }
196                }
197            }
198
199            if tts.len() >= 2 {
200                if let TokenNode::Group(Delimiter::Parenthesis, ref inner_tts) = tts[1].kind {
201                    let inner_tts = inner_tts.clone().into_iter().collect::<Vec<_>>();
202                    return match list_of_nested_meta_items_from_tokens(&inner_tts) {
203                        Some(nested_meta_items) => {
204                            let list = MetaList {
205                                ident: ident,
206                                paren_token: token::Paren(tts[1].span),
207                                nested: nested_meta_items,
208                            };
209                            Some((Meta::List(list).into(), &tts[2..]))
210                        }
211
212                        None => None,
213                    };
214                }
215            }
216
217            Some((Meta::Word(ident).into(), &tts[1..]))
218        }
219
220        _ => None,
221    }
222}
223
224fn list_of_nested_meta_items_from_tokens(
225    mut tts: &[TokenTree],
226) -> Option<Punctuated<NestedMeta, Token![,]>> {
227    let mut nested_meta_items = Punctuated::new();
228    let mut first = true;
229
230    while !tts.is_empty() {
231        let prev_comma = if first {
232            first = false;
233            None
234        } else if let TokenNode::Op(',', Spacing::Alone) = tts[0].kind {
235            let tok = Token![,]([tts[0].span]);
236            tts = &tts[1..];
237            if tts.is_empty() {
238                break;
239            }
240            Some(tok)
241        } else {
242            return None;
243        };
244        let (nested, rest) = match nested_meta_item_from_tokens(tts) {
245            Some(pair) => pair,
246            None => return None,
247        };
248        if let Some(comma) = prev_comma {
249            nested_meta_items.push_punct(comma);
250        }
251        nested_meta_items.push_value(nested);
252        tts = rest;
253    }
254
255    Some(nested_meta_items)
256}
257
258ast_enum! {
259    /// Distinguishes between attributes that decorate an item and attributes
260    /// that are contained within an item.
261    ///
262    /// *This type is available if Syn is built with the `"derive"` or `"full"`
263    /// feature.*
264    ///
265    /// # Outer attributes
266    ///
267    /// - `#[repr(transparent)]`
268    /// - `/// # Example`
269    /// - `/** Please file an issue */`
270    ///
271    /// # Inner attributes
272    ///
273    /// - `#![feature(proc_macro)]`
274    /// - `//! # Example`
275    /// - `/*! Please file an issue */`
276    #[cfg_attr(feature = "clone-impls", derive(Copy))]
277    pub enum AttrStyle {
278        Outer,
279        Inner(Token![!]),
280    }
281}
282
283ast_enum_of_structs! {
284    /// Content of a compile-time structured attribute.
285    ///
286    /// *This type is available if Syn is built with the `"derive"` or `"full"`
287    /// feature.*
288    ///
289    /// ## Word
290    ///
291    /// A meta word is like the `test` in `#[test]`.
292    ///
293    /// ## List
294    ///
295    /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`.
296    ///
297    /// ## NameValue
298    ///
299    /// A name-value meta is like the `path = "..."` in `#[path =
300    /// "sys/windows.rs"]`.
301    ///
302    /// # Syntax tree enum
303    ///
304    /// This type is a [syntax tree enum].
305    ///
306    /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
307    pub enum Meta {
308        pub Word(Ident),
309        /// A structured list within an attribute, like `derive(Copy, Clone)`.
310        ///
311        /// *This type is available if Syn is built with the `"derive"` or
312        /// `"full"` feature.*
313        pub List(MetaList {
314            pub ident: Ident,
315            pub paren_token: token::Paren,
316            pub nested: Punctuated<NestedMeta, Token![,]>,
317        }),
318        /// A name-value pair within an attribute, like `feature = "nightly"`.
319        ///
320        /// *This type is available if Syn is built with the `"derive"` or
321        /// `"full"` feature.*
322        pub NameValue(MetaNameValue {
323            pub ident: Ident,
324            pub eq_token: Token![=],
325            pub lit: Lit,
326        }),
327    }
328}
329
330impl Meta {
331    /// Returns the identifier that begins this structured meta item.
332    ///
333    /// For example this would return the `test` in `#[test]`, the `derive` in
334    /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
335    pub fn name(&self) -> Ident {
336        match *self {
337            Meta::Word(ref meta) => *meta,
338            Meta::List(ref meta) => meta.ident,
339            Meta::NameValue(ref meta) => meta.ident,
340        }
341    }
342}
343
344ast_enum_of_structs! {
345    /// Element of a compile-time attribute list.
346    ///
347    /// *This type is available if Syn is built with the `"derive"` or `"full"`
348    /// feature.*
349    pub enum NestedMeta {
350        /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
351        /// would be a nested `Meta::Word`.
352        pub Meta(Meta),
353
354        /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
355        pub Literal(Lit),
356    }
357}
358
359pub trait FilterAttrs<'a> {
360    type Ret: Iterator<Item = &'a Attribute>;
361
362    fn outer(self) -> Self::Ret;
363    fn inner(self) -> Self::Ret;
364}
365
366impl<'a, T> FilterAttrs<'a> for T
367where
368    T: IntoIterator<Item = &'a Attribute>,
369{
370    type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
371
372    fn outer(self) -> Self::Ret {
373        fn is_outer(attr: &&Attribute) -> bool {
374            match attr.style {
375                AttrStyle::Outer => true,
376                _ => false,
377            }
378        }
379        self.into_iter().filter(is_outer)
380    }
381
382    fn inner(self) -> Self::Ret {
383        fn is_inner(attr: &&Attribute) -> bool {
384            match attr.style {
385                AttrStyle::Inner(_) => true,
386                _ => false,
387            }
388        }
389        self.into_iter().filter(is_inner)
390    }
391}
392
393#[cfg(feature = "parsing")]
394pub mod parsing {
395    use super::*;
396    use buffer::Cursor;
397    use parse_error;
398    use synom::PResult;
399    use proc_macro2::{Literal, Spacing, Span, TokenNode, TokenTree};
400
401    fn eq(span: Span) -> TokenTree {
402        TokenTree {
403            span: span,
404            kind: TokenNode::Op('=', Spacing::Alone),
405        }
406    }
407
408    impl Attribute {
409        named!(pub parse_inner -> Self, alt!(
410            do_parse!(
411                pound: punct!(#) >>
412                bang: punct!(!) >>
413                path_and_tts: brackets!(tuple!(
414                    call!(Path::parse_mod_style),
415                    syn!(TokenStream)
416                )) >>
417                ({
418                    let (bracket, (path, tts)) = path_and_tts;
419
420                    Attribute {
421                        style: AttrStyle::Inner(bang),
422                        path: path,
423                        tts: tts,
424                        is_sugared_doc: false,
425                        pound_token: pound,
426                        bracket_token: bracket,
427                    }
428                })
429            )
430            |
431            map!(
432                call!(lit_doc_comment, Comment::Inner),
433                |lit| {
434                    let span = lit.span;
435                    Attribute {
436                        style: AttrStyle::Inner(<Token![!]>::new(span)),
437                        path: Ident::new("doc", span).into(),
438                        tts: vec![
439                            eq(span),
440                            lit,
441                        ].into_iter().collect(),
442                        is_sugared_doc: true,
443                        pound_token: <Token![#]>::new(span),
444                        bracket_token: token::Bracket(span),
445                    }
446                }
447            )
448        ));
449
450        named!(pub parse_outer -> Self, alt!(
451            do_parse!(
452                pound: punct!(#) >>
453                path_and_tts: brackets!(tuple!(
454                    call!(Path::parse_mod_style),
455                    syn!(TokenStream)
456                )) >>
457                ({
458                    let (bracket, (path, tts)) = path_and_tts;
459
460                    Attribute {
461                        style: AttrStyle::Outer,
462                        path: path,
463                        tts: tts,
464                        is_sugared_doc: false,
465                        pound_token: pound,
466                        bracket_token: bracket,
467                    }
468                })
469            )
470            |
471            map!(
472                call!(lit_doc_comment, Comment::Outer),
473                |lit| {
474                    let span = lit.span;
475                    Attribute {
476                        style: AttrStyle::Outer,
477                        path: Ident::new("doc", span).into(),
478                        tts: vec![
479                            eq(span),
480                            lit,
481                        ].into_iter().collect(),
482                        is_sugared_doc: true,
483                        pound_token: <Token![#]>::new(span),
484                        bracket_token: token::Bracket(span),
485                    }
486                }
487            )
488        ));
489    }
490
491    enum Comment {
492        Inner,
493        Outer,
494    }
495
496    fn lit_doc_comment(input: Cursor, style: Comment) -> PResult<TokenTree> {
497        match input.literal() {
498            Some((span, lit, rest)) => {
499                let string = lit.to_string();
500                let ok = match style {
501                    Comment::Inner => string.starts_with("//!") || string.starts_with("/*!"),
502                    Comment::Outer => string.starts_with("///") || string.starts_with("/**"),
503                };
504                if ok {
505                    Ok((
506                        TokenTree {
507                            span: span,
508                            kind: TokenNode::Literal(Literal::string(&string)),
509                        },
510                        rest,
511                    ))
512                } else {
513                    parse_error()
514                }
515            }
516            _ => parse_error(),
517        }
518    }
519}
520
521#[cfg(feature = "printing")]
522mod printing {
523    use super::*;
524    use quote::{ToTokens, Tokens};
525    use proc_macro2::Literal;
526
527    impl ToTokens for Attribute {
528        fn to_tokens(&self, tokens: &mut Tokens) {
529            // If this was a sugared doc, emit it in its original form instead of `#[doc = "..."]`
530            if self.is_sugared_doc {
531                if let Some(Meta::NameValue(ref pair)) = self.interpret_meta() {
532                    if pair.ident == "doc" {
533                        if let Lit::Str(ref comment) = pair.lit {
534                            tokens.append(TokenTree {
535                                span: comment.span,
536                                kind: TokenNode::Literal(Literal::doccomment(&comment.value())),
537                            });
538                            return;
539                        }
540                    }
541                }
542            }
543
544            self.pound_token.to_tokens(tokens);
545            if let AttrStyle::Inner(ref b) = self.style {
546                b.to_tokens(tokens);
547            }
548            self.bracket_token.surround(tokens, |tokens| {
549                self.path.to_tokens(tokens);
550                self.tts.to_tokens(tokens);
551            });
552        }
553    }
554
555    impl ToTokens for MetaList {
556        fn to_tokens(&self, tokens: &mut Tokens) {
557            self.ident.to_tokens(tokens);
558            self.paren_token.surround(tokens, |tokens| {
559                self.nested.to_tokens(tokens);
560            })
561        }
562    }
563
564    impl ToTokens for MetaNameValue {
565        fn to_tokens(&self, tokens: &mut Tokens) {
566            self.ident.to_tokens(tokens);
567            self.eq_token.to_tokens(tokens);
568            self.lit.to_tokens(tokens);
569        }
570    }
571}