Merge pull request #513 from carllerche/parse-meta

Implement Parse for meta item types
diff --git a/src/attr.rs b/src/attr.rs
index 2e0cccb..bfc41d9 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -11,7 +11,9 @@
 
 use std::iter;
 
-use proc_macro2::{Delimiter, Spacing, TokenStream, TokenTree};
+#[cfg(not(feature = "parsing"))]
+use proc_macro2::{Delimiter, Spacing, TokenTree};
+use proc_macro2::TokenStream;
 
 #[cfg(feature = "parsing")]
 use parse::{ParseStream, Result};
@@ -146,32 +148,61 @@
 impl Attribute {
     /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
     /// possible.
+    ///
+    /// Deprecated; use `parse_meta` instead.
+    #[doc(hidden)]
     pub fn interpret_meta(&self) -> Option<Meta> {
-        let name = if self.path.segments.len() == 1 {
-            &self.path.segments.first().unwrap().value().ident
-        } else {
-            return None;
-        };
-
-        if self.tts.is_empty() {
-            return Some(Meta::Word(name.clone()));
+        #[cfg(feature = "parsing")]
+        {
+            self.parse_meta().ok()
         }
 
-        let tts = self.tts.clone().into_iter().collect::<Vec<_>>();
+        #[cfg(not(feature = "parsing"))]
+        {
+            let name = if self.path.segments.len() == 1 {
+                &self.path.segments.first().unwrap().value().ident
+            } else {
+                return None;
+            };
 
-        if tts.len() == 1 {
-            if let Some(meta) = Attribute::extract_meta_list(name.clone(), &tts[0]) {
-                return Some(meta);
+            if self.tts.is_empty() {
+                return Some(Meta::Word(name.clone()));
             }
-        }
 
-        if tts.len() == 2 {
-            if let Some(meta) = Attribute::extract_name_value(name.clone(), &tts[0], &tts[1]) {
-                return Some(meta);
+            let tts = self.tts.clone().into_iter().collect::<Vec<_>>();
+
+            if tts.len() == 1 {
+                if let Some(meta) = Attribute::extract_meta_list(name.clone(), &tts[0]) {
+                    return Some(meta);
+                }
             }
+
+            if tts.len() == 2 {
+                if let Some(meta) = Attribute::extract_name_value(name.clone(), &tts[0], &tts[1]) {
+                    return Some(meta);
+                }
+            }
+
+            None
+        }
+    }
+
+    /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
+    /// possible.
+    #[cfg(feature = "parsing")]
+    pub fn parse_meta(&self) -> Result<Meta> {
+        if let Some(ref colon) = self.path.leading_colon {
+            return Err(Error::new(colon.spans[0], "expected meta identifier"));
         }
 
-        None
+        let first_segment = self.path.segments.first().expect("paths have at least one segment");
+        if let Some(colon) = first_segment.punct() {
+            return Err(Error::new(colon.spans[0], "expected meta value"));
+        }
+        let ident = first_segment.value().ident.clone();
+
+        let parser = |input: ParseStream| parsing::parse_meta_after_ident(ident, input);
+        parse::Parser::parse2(parser, self.tts.clone())
     }
 
     /// Parses zero or more outer attributes from the stream.
@@ -200,6 +231,7 @@
         Ok(attrs)
     }
 
+    #[cfg(not(feature = "parsing"))]
     fn extract_meta_list(ident: Ident, tt: &TokenTree) -> Option<Meta> {
         let g = match *tt {
             TokenTree::Group(ref g) => g,
@@ -220,6 +252,7 @@
         }))
     }
 
+    #[cfg(not(feature = "parsing"))]
     fn extract_name_value(ident: Ident, a: &TokenTree, b: &TokenTree) -> Option<Meta> {
         let a = match *a {
             TokenTree::Punct(ref o) => o,
@@ -256,6 +289,7 @@
     }
 }
 
+#[cfg(not(feature = "parsing"))]
 fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
     assert!(!tts.is_empty());
 
@@ -297,6 +331,7 @@
     }
 }
 
+#[cfg(not(feature = "parsing"))]
 fn list_of_nested_meta_items_from_tokens(
     mut tts: &[TokenTree],
 ) -> Option<Punctuated<NestedMeta, Token![,]>> {
@@ -438,6 +473,48 @@
     }
 }
 
+/// Conventional argument type associated with an invocation of an attribute
+/// macro.
+///
+/// For example if we are developing an attribute macro that is intended to be
+/// invoked on function items as follows:
+///
+/// ```rust
+/// # const IGNORE: &str = stringify! {
+/// #[my_attribute(path = "/v1/refresh")]
+/// # };
+/// pub fn refresh() {
+///     /* ... */
+/// }
+/// ```
+///
+/// The implementation of this macro would want to parse its attribute arguments
+/// as type `AttributeArgs`.
+///
+/// ```rust
+/// #[macro_use]
+/// extern crate syn;
+///
+/// extern crate proc_macro;
+///
+/// use proc_macro::TokenStream;
+/// use syn::{AttributeArgs, ItemFn};
+///
+/// # const IGNORE: &str = stringify! {
+/// #[proc_macro_attribute]
+/// # };
+/// pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
+///     let args = parse_macro_input!(args as AttributeArgs);
+///     let input = parse_macro_input!(input as ItemFn);
+///
+///     /* ... */
+/// #   "".parse().unwrap()
+/// }
+/// #
+/// # fn main() {}
+/// ```
+pub type AttributeArgs = Vec<NestedMeta>;
+
 pub trait FilterAttrs<'a> {
     type Ret: Iterator<Item = &'a Attribute>;
 
@@ -476,7 +553,8 @@
 pub mod parsing {
     use super::*;
 
-    use parse::{ParseStream, Result};
+    use ext::IdentExt;
+    use parse::{Parse, ParseStream, Result};
     #[cfg(feature = "full")]
     use private;
 
@@ -510,6 +588,68 @@
             attrs
         }
     }
+
+    impl Parse for Meta {
+        fn parse(input: ParseStream) -> Result<Self> {
+            let ident = input.call(Ident::parse_any)?;
+            parse_meta_after_ident(ident, input)
+        }
+    }
+
+    impl Parse for MetaList {
+        fn parse(input: ParseStream) -> Result<Self> {
+            let ident = input.call(Ident::parse_any)?;
+            parse_meta_list_after_ident(ident, input)
+        }
+    }
+
+    impl Parse for MetaNameValue {
+        fn parse(input: ParseStream) -> Result<Self> {
+            let ident = input.call(Ident::parse_any)?;
+            parse_meta_name_value_after_ident(ident, input)
+        }
+    }
+
+    impl Parse for NestedMeta {
+        fn parse(input: ParseStream) -> Result<Self> {
+            let ahead = input.fork();
+
+            if ahead.peek(Lit) {
+                input.parse().map(NestedMeta::Literal)
+            } else if ahead.call(Ident::parse_any).is_ok() {
+                input.parse().map(NestedMeta::Meta)
+            } else {
+                Err(input.error("expected identifier or literal"))
+            }
+        }
+    }
+
+    pub fn parse_meta_after_ident(ident: Ident, input: ParseStream) -> Result<Meta> {
+        if input.peek(token::Paren) {
+            parse_meta_list_after_ident(ident, input).map(Meta::List)
+        } else if input.peek(Token![=]) {
+            parse_meta_name_value_after_ident(ident, input).map(Meta::NameValue)
+        } else {
+            Ok(Meta::Word(ident))
+        }
+    }
+
+    fn parse_meta_list_after_ident(ident: Ident, input: ParseStream) -> Result<MetaList> {
+        let content;
+        Ok(MetaList {
+            ident: ident,
+            paren_token: parenthesized!(content in input),
+            nested: content.parse_terminated(NestedMeta::parse)?,
+        })
+    }
+
+    fn parse_meta_name_value_after_ident(ident: Ident, input: ParseStream) -> Result<MetaNameValue> {
+        Ok(MetaNameValue {
+            ident: ident,
+            eq_token: input.parse()?,
+            lit: input.parse()?,
+        })
+    }
 }
 
 #[cfg(feature = "printing")]
diff --git a/src/lib.rs b/src/lib.rs
index dc07fea..ae290a5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -327,7 +327,7 @@
 #[cfg(any(feature = "full", feature = "derive"))]
 mod attr;
 #[cfg(any(feature = "full", feature = "derive"))]
-pub use attr::{AttrStyle, Attribute, Meta, MetaList, MetaNameValue, NestedMeta};
+pub use attr::{AttrStyle, Attribute, AttributeArgs, Meta, MetaList, MetaNameValue, NestedMeta};
 
 #[cfg(any(feature = "full", feature = "derive"))]
 mod data;
@@ -448,6 +448,15 @@
 #[doc(hidden)]
 pub mod parse_quote;
 
+// Not public API except the `parse_macro_input!` macro.
+#[cfg(all(
+    not(all(target_arch = "wasm32", target_os = "unknown")),
+    feature = "parsing",
+    feature = "proc-macro"
+))]
+#[doc(hidden)]
+pub mod parse_macro_input;
+
 #[cfg(all(feature = "parsing", feature = "printing"))]
 pub mod spanned;
 
@@ -765,58 +774,3 @@
     file.shebang = shebang;
     Ok(file)
 }
-
-/// Parse the input TokenStream of a macro, triggering a compile error if the
-/// tokens fail to parse.
-///
-/// Refer to the [`parse` module] documentation for more details about parsing
-/// in Syn.
-///
-/// [`parse` module]: parse/index.html
-///
-/// # Intended usage
-///
-/// ```rust
-/// #[macro_use]
-/// extern crate syn;
-///
-/// extern crate proc_macro;
-///
-/// use proc_macro::TokenStream;
-/// use syn::parse::{Parse, ParseStream, Result};
-///
-/// struct MyMacroInput {
-///     /* ... */
-/// }
-///
-/// impl Parse for MyMacroInput {
-///     fn parse(input: ParseStream) -> Result<Self> {
-///         /* ... */
-/// #       Ok(MyMacroInput {})
-///     }
-/// }
-///
-/// # const IGNORE: &str = stringify! {
-/// #[proc_macro]
-/// # };
-/// pub fn my_macro(tokens: TokenStream) -> TokenStream {
-///     let input = parse_macro_input!(tokens as MyMacroInput);
-///
-///     /* ... */
-/// #   "".parse().unwrap()
-/// }
-/// #
-/// # fn main() {}
-/// ```
-#[cfg(feature = "proc-macro")]
-#[macro_export]
-macro_rules! parse_macro_input {
-    ($tokenstream:ident as $ty:ty) => {
-        match $crate::parse::<$ty>($tokenstream) {
-            $crate::export::Ok(data) => data,
-            $crate::export::Err(err) => {
-                return $crate::export::TokenStream::from(err.to_compile_error());
-            }
-        };
-    };
-}
diff --git a/src/parse_macro_input.rs b/src/parse_macro_input.rs
new file mode 100644
index 0000000..3c26787
--- /dev/null
+++ b/src/parse_macro_input.rs
@@ -0,0 +1,102 @@
+/// Parse the input TokenStream of a macro, triggering a compile error if the
+/// tokens fail to parse.
+///
+/// Refer to the [`parse` module] documentation for more details about parsing
+/// in Syn.
+///
+/// [`parse` module]: parse/index.html
+///
+/// # Intended usage
+///
+/// ```rust
+/// #[macro_use]
+/// extern crate syn;
+///
+/// extern crate proc_macro;
+///
+/// use proc_macro::TokenStream;
+/// use syn::parse::{Parse, ParseStream, Result};
+///
+/// struct MyMacroInput {
+///     /* ... */
+/// }
+///
+/// impl Parse for MyMacroInput {
+///     fn parse(input: ParseStream) -> Result<Self> {
+///         /* ... */
+/// #       Ok(MyMacroInput {})
+///     }
+/// }
+///
+/// # const IGNORE: &str = stringify! {
+/// #[proc_macro]
+/// # };
+/// pub fn my_macro(tokens: TokenStream) -> TokenStream {
+///     let input = parse_macro_input!(tokens as MyMacroInput);
+///
+///     /* ... */
+/// #   "".parse().unwrap()
+/// }
+/// #
+/// # fn main() {}
+/// ```
+#[macro_export]
+macro_rules! parse_macro_input {
+    ($tokenstream:ident as $ty:ty) => {
+        match $crate::parse_macro_input::parse::<$ty>($tokenstream) {
+            $crate::export::Ok(data) => data,
+            $crate::export::Err(err) => {
+                return $crate::export::TokenStream::from(err.to_compile_error());
+            }
+        };
+    };
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Can parse any type that implements Parse.
+
+use parse::{Parse, ParseStream, Parser, Result};
+use proc_macro::TokenStream;
+
+// Not public API.
+#[doc(hidden)]
+pub fn parse<T: ParseMacroInput>(token_stream: TokenStream) -> Result<T> {
+    T::parse.parse(token_stream)
+}
+
+// Not public API.
+#[doc(hidden)]
+pub trait ParseMacroInput: Sized {
+    fn parse(input: ParseStream) -> Result<Self>;
+}
+
+impl<T: Parse> ParseMacroInput for T {
+    fn parse(input: ParseStream) -> Result<Self> {
+        <T as Parse>::parse(input)
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Any other types that we want `parse_macro_input!` to be able to parse.
+
+use AttributeArgs;
+
+impl ParseMacroInput for AttributeArgs {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut metas = Vec::new();
+
+        loop {
+            if input.is_empty() {
+                break;
+            }
+            let value = input.parse()?;
+            metas.push(value);
+            if input.is_empty() {
+                break;
+            }
+            input.parse::<Token![,]>()?;
+        }
+
+        Ok(metas)
+    }
+}
diff --git a/tests/test_meta_item.rs b/tests/test_meta_item.rs
index f3c75a9..30b06cc 100644
--- a/tests/test_meta_item.rs
+++ b/tests/test_meta_item.rs
@@ -10,6 +10,7 @@
 
 extern crate proc_macro2;
 extern crate syn;
+extern crate quote;
 
 use proc_macro2::{Ident, Literal, Span};
 use syn::parse::Parser;
@@ -187,9 +188,126 @@
     )
 }
 
+#[test]
+fn test_parse_meta_item_word() {
+    let raw = "hello";
+
+    let expected = Meta::Word(ident("hello"));
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+}
+
+#[test]
+fn test_parse_meta_name_value() {
+    let raw = "foo = 5";
+
+    let expected = MetaNameValue {
+        ident: ident("foo").into(),
+        eq_token: Default::default(),
+        lit: lit(Literal::i32_unsuffixed(5)),
+    };
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+
+    let expected = Meta::NameValue(expected);
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+}
+
+#[test]
+fn test_parse_meta_item_list_lit() {
+    let raw = "foo(5)";
+
+    let expected = MetaList {
+        ident: ident("foo").into(),
+        paren_token: Default::default(),
+        nested: punctuated![NestedMeta::Literal(lit(Literal::i32_unsuffixed(5)))],
+    };
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+
+    let expected = Meta::List(expected);
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+}
+
+#[test]
+fn test_parse_meta_item_multiple() {
+    let raw = "foo(word, name = 5, list(name2 = 6), word2)";
+
+    let expected = MetaList {
+        ident: ident("foo").into(),
+        paren_token: Default::default(),
+        nested: punctuated![
+            NestedMeta::Meta(Meta::Word(ident("word").into())),
+            NestedMeta::Meta(
+                MetaNameValue {
+                    ident: ident("name").into(),
+                    eq_token: Default::default(),
+                    lit: lit(Literal::i32_unsuffixed(5)),
+                }
+                .into(),
+            ),
+            NestedMeta::Meta(
+                MetaList {
+                    ident: ident("list").into(),
+                    paren_token: Default::default(),
+                    nested: punctuated![NestedMeta::Meta(
+                        MetaNameValue {
+                            ident: ident("name2").into(),
+                            eq_token: Default::default(),
+                            lit: lit(Literal::i32_unsuffixed(6)),
+                        }
+                        .into(),
+                    )],
+                }
+                .into(),
+            ),
+            NestedMeta::Meta(Meta::Word(ident("word2").into())),
+        ],
+    };
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+
+    let expected = Meta::List(expected);
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+}
+
+#[test]
+fn test_parse_nested_meta() {
+    let raw = "5";
+
+    let expected = NestedMeta::Literal(lit(Literal::i32_unsuffixed(5)));
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+
+    let raw = "list(name2 = 6)";
+
+    let expected = NestedMeta::Meta(
+        MetaList {
+            ident: ident("list").into(),
+            paren_token: Default::default(),
+            nested: punctuated![NestedMeta::Meta(
+                MetaNameValue {
+                    ident: ident("name2").into(),
+                    eq_token: Default::default(),
+                    lit: lit(Literal::i32_unsuffixed(6)),
+                }
+                .into(),
+            )],
+        }
+        .into(),
+    );
+
+    assert_eq!(expected, syn::parse_str(raw).unwrap());
+}
+
 fn run_test<T: Into<Meta>>(input: &str, expected: T) {
     let attrs = Attribute::parse_outer.parse_str(input).unwrap();
     assert_eq!(attrs.len(), 1);
     let attr = attrs.into_iter().next().unwrap();
-    assert_eq!(expected.into(), attr.interpret_meta().unwrap());
+    let expected = expected.into();
+    assert_eq!(expected, attr.interpret_meta().unwrap());
+    assert_eq!(expected, attr.parse_meta().unwrap());
 }