syn_pub_items/
attr.rs

1use super::*;
2use punctuated::Punctuated;
3
4use std::iter;
5
6use proc_macro2::TokenStream;
7#[cfg(not(feature = "parsing"))]
8use proc_macro2::{Delimiter, Spacing, TokenTree};
9
10#[cfg(feature = "parsing")]
11use parse::{ParseStream, Result};
12#[cfg(feature = "extra-traits")]
13use std::hash::{Hash, Hasher};
14#[cfg(feature = "extra-traits")]
15use tt::TokenStreamHelper;
16
17ast_struct! {
18    /// An attribute like `#[repr(transparent)]`.
19    ///
20    /// *This type is available if Syn is built with the `"derive"` or `"full"`
21    /// feature.*
22    ///
23    /// # Syntax
24    ///
25    /// Rust has six types of attributes.
26    ///
27    /// - Outer attributes like `#[repr(transparent)]`. These appear outside or
28    ///   in front of the item they describe.
29    /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside
30    ///   of the item they describe, usually a module.
31    /// - Outer doc comments like `/// # Example`.
32    /// - Inner doc comments like `//! Please file an issue`.
33    /// - Outer block comments `/** # Example */`.
34    /// - Inner block comments `/*! Please file an issue */`.
35    ///
36    /// The `style` field of type `AttrStyle` distinguishes whether an attribute
37    /// is outer or inner. Doc comments and block comments are promoted to
38    /// attributes, as this is how they are processed by the compiler and by
39    /// `macro_rules!` macros.
40    ///
41    /// The `path` field gives the possibly colon-delimited path against which
42    /// the attribute is resolved. It is equal to `"doc"` for desugared doc
43    /// comments. The `tts` field contains the rest of the attribute body as
44    /// tokens.
45    ///
46    /// ```text
47    /// #[derive(Copy)]      #[crate::precondition x < 5]
48    ///   ^^^^^^~~~~~~         ^^^^^^^^^^^^^^^^^^^ ~~~~~
49    ///    path  tts                   path         tts
50    /// ```
51    ///
52    /// Use the [`parse_meta`] method to try parsing the tokens of an attribute
53    /// into the structured representation that is used by convention across
54    /// most Rust libraries.
55    ///
56    /// [`parse_meta`]: #method.parse_meta
57    ///
58    /// # Parsing
59    ///
60    /// This type does not implement the [`Parse`] trait and thus cannot be
61    /// parsed directly by [`ParseStream::parse`]. Instead use
62    /// [`ParseStream::call`] with one of the two parser functions
63    /// [`Attribute::parse_outer`] or [`Attribute::parse_inner`] depending on
64    /// which you intend to parse.
65    ///
66    /// [`Parse`]: parse/trait.Parse.html
67    /// [`ParseStream::parse`]: parse/struct.ParseBuffer.html#method.parse
68    /// [`ParseStream::call`]: parse/struct.ParseBuffer.html#method.call
69    /// [`Attribute::parse_outer`]: #method.parse_outer
70    /// [`Attribute::parse_inner`]: #method.parse_inner
71    ///
72    /// ```edition2018
73    /// use syn::{Attribute, Ident, Result, Token};
74    /// use syn::parse::{Parse, ParseStream};
75    ///
76    /// // Parses a unit struct with attributes.
77    /// //
78    /// //     #[path = "s.tmpl"]
79    /// //     struct S;
80    /// struct UnitStruct {
81    ///     attrs: Vec<Attribute>,
82    ///     struct_token: Token![struct],
83    ///     name: Ident,
84    ///     semi_token: Token![;],
85    /// }
86    ///
87    /// impl Parse for UnitStruct {
88    ///     fn parse(input: ParseStream) -> Result<Self> {
89    ///         Ok(UnitStruct {
90    ///             attrs: input.call(Attribute::parse_outer)?,
91    ///             struct_token: input.parse()?,
92    ///             name: input.parse()?,
93    ///             semi_token: input.parse()?,
94    ///         })
95    ///     }
96    /// }
97    /// ```
98    pub struct Attribute #manual_extra_traits {
99        pub pound_token: Token![#],
100        pub style: AttrStyle,
101        pub bracket_token: token::Bracket,
102        pub path: Path,
103        pub tts: TokenStream,
104    }
105}
106
107#[cfg(feature = "extra-traits")]
108impl Eq for Attribute {}
109
110#[cfg(feature = "extra-traits")]
111impl PartialEq for Attribute {
112    fn eq(&self, other: &Self) -> bool {
113        self.style == other.style
114            && self.pound_token == other.pound_token
115            && self.bracket_token == other.bracket_token
116            && self.path == other.path
117            && TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
118    }
119}
120
121#[cfg(feature = "extra-traits")]
122impl Hash for Attribute {
123    fn hash<H>(&self, state: &mut H)
124    where
125        H: Hasher,
126    {
127        self.style.hash(state);
128        self.pound_token.hash(state);
129        self.bracket_token.hash(state);
130        self.path.hash(state);
131        TokenStreamHelper(&self.tts).hash(state);
132    }
133}
134
135impl Attribute {
136    /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
137    /// possible.
138    ///
139    /// Deprecated; use `parse_meta` instead.
140    #[doc(hidden)]
141    pub fn interpret_meta(&self) -> Option<Meta> {
142        #[cfg(feature = "parsing")]
143        {
144            self.parse_meta().ok()
145        }
146
147        #[cfg(not(feature = "parsing"))]
148        {
149            let name = if self.path.segments.len() == 1 {
150                &self.path.segments.first().unwrap().value().ident
151            } else {
152                return None;
153            };
154
155            if self.tts.is_empty() {
156                return Some(Meta::Word(name.clone()));
157            }
158
159            let tts = self.tts.clone().into_iter().collect::<Vec<_>>();
160
161            if tts.len() == 1 {
162                if let Some(meta) = Attribute::extract_meta_list(name.clone(), &tts[0]) {
163                    return Some(meta);
164                }
165            }
166
167            if tts.len() == 2 {
168                if let Some(meta) = Attribute::extract_name_value(name.clone(), &tts[0], &tts[1]) {
169                    return Some(meta);
170                }
171            }
172
173            None
174        }
175    }
176
177    /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
178    /// possible.
179    #[cfg(feature = "parsing")]
180    pub fn parse_meta(&self) -> Result<Meta> {
181        if let Some(ref colon) = self.path.leading_colon {
182            return Err(Error::new(colon.spans[0], "expected meta identifier"));
183        }
184
185        let first_segment = self
186            .path
187            .segments
188            .first()
189            .expect("paths have at least one segment");
190        if let Some(colon) = first_segment.punct() {
191            return Err(Error::new(colon.spans[0], "expected meta value"));
192        }
193        let ident = first_segment.value().ident.clone();
194
195        let parser = |input: ParseStream| parsing::parse_meta_after_ident(ident, input);
196        parse::Parser::parse2(parser, self.tts.clone())
197    }
198
199    /// Parses zero or more outer attributes from the stream.
200    ///
201    /// *This function is available if Syn is built with the `"parsing"`
202    /// feature.*
203    #[cfg(feature = "parsing")]
204    pub fn parse_outer(input: ParseStream) -> Result<Vec<Self>> {
205        let mut attrs = Vec::new();
206        while input.peek(Token![#]) {
207            attrs.push(input.call(parsing::single_parse_outer)?);
208        }
209        Ok(attrs)
210    }
211
212    /// Parses zero or more inner attributes from the stream.
213    ///
214    /// *This function is available if Syn is built with the `"parsing"`
215    /// feature.*
216    #[cfg(feature = "parsing")]
217    pub fn parse_inner(input: ParseStream) -> Result<Vec<Self>> {
218        let mut attrs = Vec::new();
219        while input.peek(Token![#]) && input.peek2(Token![!]) {
220            attrs.push(input.call(parsing::single_parse_inner)?);
221        }
222        Ok(attrs)
223    }
224
225    #[cfg(not(feature = "parsing"))]
226    fn extract_meta_list(ident: Ident, tt: &TokenTree) -> Option<Meta> {
227        let g = match *tt {
228            TokenTree::Group(ref g) => g,
229            _ => return None,
230        };
231        if g.delimiter() != Delimiter::Parenthesis {
232            return None;
233        }
234        let tokens = g.stream().clone().into_iter().collect::<Vec<_>>();
235        let nested = match list_of_nested_meta_items_from_tokens(&tokens) {
236            Some(n) => n,
237            None => return None,
238        };
239        Some(Meta::List(MetaList {
240            paren_token: token::Paren(g.span()),
241            ident: ident,
242            nested: nested,
243        }))
244    }
245
246    #[cfg(not(feature = "parsing"))]
247    fn extract_name_value(ident: Ident, a: &TokenTree, b: &TokenTree) -> Option<Meta> {
248        let a = match *a {
249            TokenTree::Punct(ref o) => o,
250            _ => return None,
251        };
252        if a.spacing() != Spacing::Alone {
253            return None;
254        }
255        if a.as_char() != '=' {
256            return None;
257        }
258
259        match *b {
260            TokenTree::Literal(ref l) if !l.to_string().starts_with('/') => {
261                Some(Meta::NameValue(MetaNameValue {
262                    ident: ident,
263                    eq_token: Token![=]([a.span()]),
264                    lit: Lit::new(l.clone()),
265                }))
266            }
267            TokenTree::Ident(ref v) => match &v.to_string()[..] {
268                v @ "true" | v @ "false" => Some(Meta::NameValue(MetaNameValue {
269                    ident: ident,
270                    eq_token: Token![=]([a.span()]),
271                    lit: Lit::Bool(LitBool {
272                        value: v == "true",
273                        span: b.span(),
274                    }),
275                })),
276                _ => None,
277            },
278            _ => None,
279        }
280    }
281}
282
283#[cfg(not(feature = "parsing"))]
284fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
285    assert!(!tts.is_empty());
286
287    match tts[0] {
288        TokenTree::Literal(ref lit) => {
289            if lit.to_string().starts_with('/') {
290                None
291            } else {
292                let lit = Lit::new(lit.clone());
293                Some((NestedMeta::Literal(lit), &tts[1..]))
294            }
295        }
296
297        TokenTree::Ident(ref ident) => {
298            if tts.len() >= 3 {
299                if let Some(meta) = Attribute::extract_name_value(ident.clone(), &tts[1], &tts[2]) {
300                    return Some((NestedMeta::Meta(meta), &tts[3..]));
301                }
302            }
303
304            if tts.len() >= 2 {
305                if let Some(meta) = Attribute::extract_meta_list(ident.clone(), &tts[1]) {
306                    return Some((NestedMeta::Meta(meta), &tts[2..]));
307                }
308            }
309
310            let nested_meta = if ident == "true" || ident == "false" {
311                NestedMeta::Literal(Lit::Bool(LitBool {
312                    value: ident == "true",
313                    span: ident.span(),
314                }))
315            } else {
316                NestedMeta::Meta(Meta::Word(ident.clone()))
317            };
318            Some((nested_meta, &tts[1..]))
319        }
320
321        _ => None,
322    }
323}
324
325#[cfg(not(feature = "parsing"))]
326fn list_of_nested_meta_items_from_tokens(
327    mut tts: &[TokenTree],
328) -> Option<Punctuated<NestedMeta, Token![,]>> {
329    let mut nested_meta_items = Punctuated::new();
330    let mut first = true;
331
332    while !tts.is_empty() {
333        let prev_comma = if first {
334            first = false;
335            None
336        } else if let TokenTree::Punct(ref op) = tts[0] {
337            if op.spacing() != Spacing::Alone {
338                return None;
339            }
340            if op.as_char() != ',' {
341                return None;
342            }
343            let tok = Token![,]([op.span()]);
344            tts = &tts[1..];
345            if tts.is_empty() {
346                break;
347            }
348            Some(tok)
349        } else {
350            return None;
351        };
352        let (nested, rest) = match nested_meta_item_from_tokens(tts) {
353            Some(pair) => pair,
354            None => return None,
355        };
356        if let Some(comma) = prev_comma {
357            nested_meta_items.push_punct(comma);
358        }
359        nested_meta_items.push_value(nested);
360        tts = rest;
361    }
362
363    Some(nested_meta_items)
364}
365
366ast_enum! {
367    /// Distinguishes between attributes that decorate an item and attributes
368    /// that are contained within an item.
369    ///
370    /// *This type is available if Syn is built with the `"derive"` or `"full"`
371    /// feature.*
372    ///
373    /// # Outer attributes
374    ///
375    /// - `#[repr(transparent)]`
376    /// - `/// # Example`
377    /// - `/** Please file an issue */`
378    ///
379    /// # Inner attributes
380    ///
381    /// - `#![feature(proc_macro)]`
382    /// - `//! # Example`
383    /// - `/*! Please file an issue */`
384    #[cfg_attr(feature = "clone-impls", derive(Copy))]
385    pub enum AttrStyle {
386        Outer,
387        Inner(Token![!]),
388    }
389}
390
391ast_enum_of_structs! {
392    /// Content of a compile-time structured attribute.
393    ///
394    /// *This type is available if Syn is built with the `"derive"` or `"full"`
395    /// feature.*
396    ///
397    /// ## Word
398    ///
399    /// A meta word is like the `test` in `#[test]`.
400    ///
401    /// ## List
402    ///
403    /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`.
404    ///
405    /// ## NameValue
406    ///
407    /// A name-value meta is like the `path = "..."` in `#[path =
408    /// "sys/windows.rs"]`.
409    ///
410    /// # Syntax tree enum
411    ///
412    /// This type is a [syntax tree enum].
413    ///
414    /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
415    pub enum Meta {
416        pub Word(Ident),
417        /// A structured list within an attribute, like `derive(Copy, Clone)`.
418        ///
419        /// *This type is available if Syn is built with the `"derive"` or
420        /// `"full"` feature.*
421        pub List(MetaList {
422            pub ident: Ident,
423            pub paren_token: token::Paren,
424            pub nested: Punctuated<NestedMeta, Token![,]>,
425        }),
426        /// A name-value pair within an attribute, like `feature = "nightly"`.
427        ///
428        /// *This type is available if Syn is built with the `"derive"` or
429        /// `"full"` feature.*
430        pub NameValue(MetaNameValue {
431            pub ident: Ident,
432            pub eq_token: Token![=],
433            pub lit: Lit,
434        }),
435    }
436}
437
438impl Meta {
439    /// Returns the identifier that begins this structured meta item.
440    ///
441    /// For example this would return the `test` in `#[test]`, the `derive` in
442    /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
443    pub fn name(&self) -> Ident {
444        match *self {
445            Meta::Word(ref meta) => meta.clone(),
446            Meta::List(ref meta) => meta.ident.clone(),
447            Meta::NameValue(ref meta) => meta.ident.clone(),
448        }
449    }
450}
451
452ast_enum_of_structs! {
453    /// Element of a compile-time attribute list.
454    ///
455    /// *This type is available if Syn is built with the `"derive"` or `"full"`
456    /// feature.*
457    pub enum NestedMeta {
458        /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
459        /// would be a nested `Meta::Word`.
460        pub Meta(Meta),
461
462        /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
463        pub Literal(Lit),
464    }
465}
466
467/// Conventional argument type associated with an invocation of an attribute
468/// macro.
469///
470/// For example if we are developing an attribute macro that is intended to be
471/// invoked on function items as follows:
472///
473/// ```edition2018
474/// # const IGNORE: &str = stringify! {
475/// #[my_attribute(path = "/v1/refresh")]
476/// # };
477/// pub fn refresh() {
478///     /* ... */
479/// }
480/// ```
481///
482/// The implementation of this macro would want to parse its attribute arguments
483/// as type `AttributeArgs`.
484///
485/// ```edition2018
486/// extern crate proc_macro;
487///
488/// use proc_macro::TokenStream;
489/// use syn::{parse_macro_input, AttributeArgs, ItemFn};
490///
491/// # const IGNORE: &str = stringify! {
492/// #[proc_macro_attribute]
493/// # };
494/// pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
495///     let args = parse_macro_input!(args as AttributeArgs);
496///     let input = parse_macro_input!(input as ItemFn);
497///
498///     /* ... */
499/// #   "".parse().unwrap()
500/// }
501/// ```
502pub type AttributeArgs = Vec<NestedMeta>;
503
504pub trait FilterAttrs<'a> {
505    type Ret: Iterator<Item = &'a Attribute>;
506
507    fn outer(self) -> Self::Ret;
508    fn inner(self) -> Self::Ret;
509}
510
511impl<'a, T> FilterAttrs<'a> for T
512where
513    T: IntoIterator<Item = &'a Attribute>,
514{
515    type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
516
517    fn outer(self) -> Self::Ret {
518        #[cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
519        fn is_outer(attr: &&Attribute) -> bool {
520            match attr.style {
521                AttrStyle::Outer => true,
522                _ => false,
523            }
524        }
525        self.into_iter().filter(is_outer)
526    }
527
528    fn inner(self) -> Self::Ret {
529        #[cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
530        fn is_inner(attr: &&Attribute) -> bool {
531            match attr.style {
532                AttrStyle::Inner(_) => true,
533                _ => false,
534            }
535        }
536        self.into_iter().filter(is_inner)
537    }
538}
539
540#[cfg(feature = "parsing")]
541pub mod parsing {
542    use super::*;
543
544    use ext::IdentExt;
545    use parse::{Parse, ParseStream, Result};
546    #[cfg(feature = "full")]
547    use private;
548
549    pub fn single_parse_inner(input: ParseStream) -> Result<Attribute> {
550        let content;
551        Ok(Attribute {
552            pound_token: input.parse()?,
553            style: AttrStyle::Inner(input.parse()?),
554            bracket_token: bracketed!(content in input),
555            path: content.call(Path::parse_mod_style)?,
556            tts: content.parse()?,
557        })
558    }
559
560    pub fn single_parse_outer(input: ParseStream) -> Result<Attribute> {
561        let content;
562        Ok(Attribute {
563            pound_token: input.parse()?,
564            style: AttrStyle::Outer,
565            bracket_token: bracketed!(content in input),
566            path: content.call(Path::parse_mod_style)?,
567            tts: content.parse()?,
568        })
569    }
570
571    #[cfg(feature = "full")]
572    impl private {
573        pub fn attrs(outer: Vec<Attribute>, inner: Vec<Attribute>) -> Vec<Attribute> {
574            let mut attrs = outer;
575            attrs.extend(inner);
576            attrs
577        }
578    }
579
580    impl Parse for Meta {
581        fn parse(input: ParseStream) -> Result<Self> {
582            let ident = input.call(Ident::parse_any)?;
583            parse_meta_after_ident(ident, input)
584        }
585    }
586
587    impl Parse for MetaList {
588        fn parse(input: ParseStream) -> Result<Self> {
589            let ident = input.call(Ident::parse_any)?;
590            parse_meta_list_after_ident(ident, input)
591        }
592    }
593
594    impl Parse for MetaNameValue {
595        fn parse(input: ParseStream) -> Result<Self> {
596            let ident = input.call(Ident::parse_any)?;
597            parse_meta_name_value_after_ident(ident, input)
598        }
599    }
600
601    impl Parse for NestedMeta {
602        fn parse(input: ParseStream) -> Result<Self> {
603            let ahead = input.fork();
604
605            if ahead.peek(Lit) && !(ahead.peek(LitBool) && ahead.peek2(Token![=])) {
606                input.parse().map(NestedMeta::Literal)
607            } else if ahead.call(Ident::parse_any).is_ok() {
608                input.parse().map(NestedMeta::Meta)
609            } else {
610                Err(input.error("expected identifier or literal"))
611            }
612        }
613    }
614
615    pub fn parse_meta_after_ident(ident: Ident, input: ParseStream) -> Result<Meta> {
616        if input.peek(token::Paren) {
617            parse_meta_list_after_ident(ident, input).map(Meta::List)
618        } else if input.peek(Token![=]) {
619            parse_meta_name_value_after_ident(ident, input).map(Meta::NameValue)
620        } else {
621            Ok(Meta::Word(ident))
622        }
623    }
624
625    fn parse_meta_list_after_ident(ident: Ident, input: ParseStream) -> Result<MetaList> {
626        let content;
627        Ok(MetaList {
628            ident: ident,
629            paren_token: parenthesized!(content in input),
630            nested: content.parse_terminated(NestedMeta::parse)?,
631        })
632    }
633
634    fn parse_meta_name_value_after_ident(
635        ident: Ident,
636        input: ParseStream,
637    ) -> Result<MetaNameValue> {
638        Ok(MetaNameValue {
639            ident: ident,
640            eq_token: input.parse()?,
641            lit: input.parse()?,
642        })
643    }
644}
645
646#[cfg(feature = "printing")]
647mod printing {
648    use super::*;
649    use proc_macro2::TokenStream;
650    use quote::ToTokens;
651
652    impl ToTokens for Attribute {
653        fn to_tokens(&self, tokens: &mut TokenStream) {
654            self.pound_token.to_tokens(tokens);
655            if let AttrStyle::Inner(ref b) = self.style {
656                b.to_tokens(tokens);
657            }
658            self.bracket_token.surround(tokens, |tokens| {
659                self.path.to_tokens(tokens);
660                self.tts.to_tokens(tokens);
661            });
662        }
663    }
664
665    impl ToTokens for MetaList {
666        fn to_tokens(&self, tokens: &mut TokenStream) {
667            self.ident.to_tokens(tokens);
668            self.paren_token.surround(tokens, |tokens| {
669                self.nested.to_tokens(tokens);
670            })
671        }
672    }
673
674    impl ToTokens for MetaNameValue {
675        fn to_tokens(&self, tokens: &mut TokenStream) {
676            self.ident.to_tokens(tokens);
677            self.eq_token.to_tokens(tokens);
678            self.lit.to_tokens(tokens);
679        }
680    }
681}