Repository: dtolnay/syn
Pull request #1272 (2022-12-14 07:47:48)

Update Attribute representation
The previous representation is from before the syntax permitted by the language for attributes was quite as pinned down as it's been since then.


Patch:
diff --git a/src/attr.rs b/src/attr.rs
index 9a69c739..40b4e994 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -1,21 +1,18 @@
 use super::*;
-use crate::punctuated::Punctuated;
 use proc_macro2::TokenStream;
 use std::iter;
 use std::slice;
 
 #[cfg(feature = "parsing")]
-use crate::parse::{Parse, ParseBuffer, ParseStream, Parser, Result};
-#[cfg(feature = "parsing")]
-use crate::punctuated::Pair;
+use crate::parse::{Parse, ParseStream, Parser, Result};
 #[cfg(feature = "parsing")]
 use std::fmt::Write;
 
 ast_struct! {
-    /// An attribute like `#[repr(transparent)]`.
+    /// An attribute, like `#[repr(transparent)]`.
     ///
-    /// *This type is available only if Syn is built with the `"derive"` or `"full"`
-    /// feature.*
+    /// *This type is available only if Syn is built with the `"derive"` or
+    /// `"full"` feature.*
     ///
     /// <br>
     ///
@@ -25,27 +22,52 @@ ast_struct! {
     ///
     /// - Outer attributes like `#[repr(transparent)]`. These appear outside or
     ///   in front of the item they describe.
+    ///
     /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside
     ///   of the item they describe, usually a module.
-    /// - Outer doc comments like `/// # Example`.
-    /// - Inner doc comments like `//! Please file an issue`.
-    /// - Outer block comments `/** # Example */`.
-    /// - Inner block comments `/*! Please file an issue */`.
+    ///
+    /// - Outer one-line doc comments like `/// Example`.
+    ///
+    /// - Inner one-line doc comments like `//! Please file an issue`.
+    ///
+    /// - Outer documentation blocks `/** Example */`.
+    ///
+    /// - Inner documentation blocks `/*! Please file an issue */`.
     ///
     /// The `style` field of type `AttrStyle` distinguishes whether an attribute
-    /// is outer or inner. Doc comments and block comments are promoted to
-    /// attributes, as this is how they are processed by the compiler and by
-    /// `macro_rules!` macros.
+    /// is outer or inner.
+    ///
+    /// Every attribute has a `path` that indicates the intended interpretation
+    /// of the rest of the attribute's contents. The path and the optional
+    /// additional contents are represented together in the `meta` field of the
+    /// attribute in three possible varieties:
     ///
-    /// The `path` field gives the possibly colon-delimited path against which
-    /// the attribute is resolved. It is equal to `"doc"` for desugared doc
-    /// comments. The `tokens` field contains the rest of the attribute body as
-    /// tokens.
+    /// - Meta::Path &mdash; attributes whose information content conveys just a
+    ///   path, for example the `#[test]` attribute.
+    ///
+    /// - Meta::List &mdash; attributes that carry arbitrary tokens after the
+    ///   path, surrounded by a delimiter (parenthesis, bracket, or brace). For
+    ///   example `#[derive(Copy)]` or `#[precondition(x < 5)]`.
+    ///
+    /// - Meta::NameValue &mdash; attributes with an `=` sign after the path,
+    ///   followed by a Rust expression. For example `#[path =
+    ///   "sys/windows.rs"]`.
+    ///
+    /// All doc comments are represented in the NameValue style with a path of
+    /// "doc", as this is how they are processed by the compiler and by
+    /// `macro_rules!` macros.
     ///
     /// ```text
-    /// #[derive(Copy)]      #[crate::precondition x < 5]
-    ///   ^^^^^^~~~~~~         ^^^^^^^^^^^^^^^^^^^ ~~~~~
-    ///   path  tokens                 path        tokens
+    /// #[derive(Copy, Clone)]
+    ///   ~~~~~~Path
+    ///   ^^^^^^^^^^^^^^^^^^^Meta::List
+    ///
+    /// #[path = "sys/windows.rs"]
+    ///   ~~~~Path
+    ///   ^^^^^^^^^^^^^^^^^^^^^^^Meta::NameValue
+    ///
+    /// #[test]
+    ///   ^^^^Meta::Path
     /// ```
     ///
     /// <br>
@@ -95,18 +117,13 @@ ast_struct! {
     ///
     /// The grammar of attributes in Rust is very flexible, which makes the
     /// syntax tree not that useful on its own. In particular, arguments of the
-    /// attribute are held in an arbitrary `tokens: TokenStream`. Macros are
-    /// expected to check the `path` of the attribute, decide whether they
-    /// recognize it, and then parse the remaining tokens according to whatever
-    /// grammar they wish to require for that kind of attribute.
-    ///
-    /// If the attribute you are parsing is expected to conform to the
-    /// conventional structured form of attribute, use [`parse_meta()`] to
-    /// obtain that structured representation. If the attribute follows some
-    /// other grammar of its own, use [`parse_args()`] to parse that into the
-    /// expected data structure.
-    ///
-    /// [`parse_meta()`]: Attribute::parse_meta
+    /// `Meta::List` variety of attribute are held in an arbitrary `tokens:
+    /// TokenStream`. Macros are expected to check the `path` of the attribute,
+    /// decide whether they recognize it, and then parse the remaining tokens
+    /// according to whatever grammar they wish to require for that kind of
+    /// attribute. Use [`parse_args()`] to parse those tokens into the expected
+    /// data structure.
+    ///
     /// [`parse_args()`]: Attribute::parse_args
     ///
     /// <p><br></p>
@@ -152,48 +169,17 @@ ast_struct! {
         pub pound_token: Token![#],
         pub style: AttrStyle,
         pub bracket_token: token::Bracket,
-        pub path: Path,
-        pub tokens: TokenStream,
+        pub meta: Meta,
     }
 }
 
 impl Attribute {
-    /// Parses the content of the attribute, consisting of the path and tokens,
-    /// as a [`Meta`] if possible.
+    /// Returns the path that identifies the interpretation of this attribute.
     ///
-    /// *This function is available only if Syn is built with the `"parsing"`
-    /// feature.*
-    #[cfg(feature = "parsing")]
-    #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
-    pub fn parse_meta(&self) -> Result<Meta> {
-        fn clone_ident_segment(segment: &PathSegment) -> PathSegment {
-            PathSegment {
-                ident: segment.ident.clone(),
-                arguments: PathArguments::None,
-            }
-        }
-
-        let path = Path {
-            leading_colon: self
-                .path
-                .leading_colon
-                .as_ref()
-                .map(|colon| Token![::](colon.spans)),
-            segments: self
-                .path
-                .segments
-                .pairs()
-                .map(|pair| match pair {
-                    Pair::Punctuated(seg, punct) => {
-                        Pair::Punctuated(clone_ident_segment(seg), Token![::](punct.spans))
-                    }
-                    Pair::End(seg) => Pair::End(clone_ident_segment(seg)),
-                })
-                .collect(),
-        };
-
-        let parser = |input: ParseStream| parsing::parse_meta_after_path(path, input);
-        parse::Parser::parse2(parser, self.tokens.clone())
+    /// For example this would return the `test` in `#[test]`, the `derive` in
+    /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
+    pub fn path(&self) -> &Path {
+        self.meta.path()
     }
 
     /// Parse the arguments to the attribute as a syntax tree.
@@ -224,11 +210,23 @@ impl Attribute {
     #[cfg(feature = "parsing")]
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     pub fn parse_args_with<F: Parser>(&self, parser: F) -> Result<F::Output> {
-        let parser = |input: ParseStream| {
-            let args = enter_args(self, input)?;
-            parse::parse_stream(parser, &args)
-        };
-        parser.parse2(self.tokens.clone())
+        match &self.meta {
+            Meta::Path(path) => {
+                let expected = expected_parentheses(&self.style, path);
+                let msg = format!("expected attribute arguments in parentheses: {}", expected);
+                Err(crate::error::new2(
+                    self.pound_token.span,
+                    self.bracket_token.span,
+                    msg,
+                ))
+            }
+            Meta::NameValue(meta) => {
+                let expected = expected_parentheses(&self.style, &meta.path);
+                let msg = format!("expected parentheses: {}", expected);
+                Err(Error::new(meta.eq_token.span, msg))
+            }
+            Meta::List(meta) => parser.parse2(meta.tokens.clone()),
+        }
     }
 
     /// Parses zero or more outer attributes from the stream.
@@ -259,57 +257,25 @@ impl Attribute {
 }
 
 #[cfg(feature = "parsing")]
-fn expected_parentheses(attr: &Attribute) -> String {
+fn expected_parentheses(style: &AttrStyle, path: &Path) -> String {
     let mut suggestion = String::new();
-    match attr.style {
+    match style {
         AttrStyle::Outer => suggestion.push('#'),
         AttrStyle::Inner(_) => suggestion.push_str("#!"),
     }
     suggestion.push('[');
-    for (i, segment) in attr.path.segments.iter().enumerate() {
-        if i > 0 || attr.path.leading_colon.is_some() {
+
+    for (i, segment) in path.segments.iter().enumerate() {
+        if i > 0 || path.leading_colon.is_some() {
             suggestion.push_str("::");
         }
         write!(suggestion, "{}", segment.ident).unwrap();
     }
+
     suggestion.push_str("(...)]");
     suggestion
 }
 
-#[cfg(feature = "parsing")]
-fn enter_args<'a>(attr: &Attribute, input: ParseStream<'a>) -> Result<ParseBuffer<'a>> {
-    if input.is_empty() {
-        let expected = expected_parentheses(attr);
-        let msg = format!("expected attribute arguments in parentheses: {}", expected);
-        return Err(crate::error::new2(
-            attr.pound_token.span,
-            attr.bracket_token.span,
-            msg,
-        ));
-    } else if input.peek(Token![=]) {
-        let expected = expected_parentheses(attr);
-        let msg = format!("expected parentheses: {}", expected);
-        return Err(input.error(msg));
-    };
-
-    let content;
-    if input.peek(token::Paren) {
-        parenthesized!(content in input);
-    } else if input.peek(token::Bracket) {
-        bracketed!(content in input);
-    } else if input.peek(token::Brace) {
-        braced!(content in input);
-    } else {
-        return Err(input.error("unexpected token in attribute arguments"));
-    }
-
-    if input.is_empty() {
-        Ok(content)
-    } else {
-        Err(input.error("unexpected token in attribute arguments"))
-    }
-}
-
 ast_enum! {
     /// Distinguishes between attributes that decorate an item and attributes
     /// that are contained within an item.
@@ -379,8 +345,8 @@ ast_struct! {
     #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
     pub struct MetaList {
         pub path: Path,
-        pub paren_token: token::Paren,
-        pub nested: Punctuated<NestedMeta, Token![,]>,
+        pub delimiter: MacroDelimiter,
+        pub tokens: TokenStream,
     }
 }
 
@@ -393,12 +359,12 @@ ast_struct! {
     pub struct MetaNameValue {
         pub path: Path,
         pub eq_token: Token![=],
-        pub lit: Lit,
+        pub value: Expr,
     }
 }
 
 impl Meta {
-    /// Returns the identifier that begins this structured meta item.
+    /// Returns the path that begins this structured meta item.
     ///
     /// For example this would return the `test` in `#[test]`, the `derive` in
     /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
@@ -411,22 +377,6 @@ impl Meta {
     }
 }
 
-ast_enum_of_structs! {
-    /// Element of a compile-time attribute list.
-    ///
-    /// *This type is available only if Syn is built with the `"derive"` or `"full"`
-    /// feature.*
-    #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
-    pub enum NestedMeta {
-        /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
-        /// would be a nested `Meta::Path`.
-        Meta(Meta),
-
-        /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
-        Lit(Lit),
-    }
-}
-
 pub trait FilterAttrs<'a> {
     type Ret: Iterator<Item = &'a Attribute>;
 
@@ -461,7 +411,6 @@ impl<'a> FilterAttrs<'a> for &'a [Attribute] {
 #[cfg(feature = "parsing")]
 pub mod parsing {
     use super::*;
-    use crate::ext::IdentExt;
     use crate::parse::{Parse, ParseStream, Result};
 
     pub fn parse_inner(input: ParseStream, attrs: &mut Vec<Attribute>) -> Result<()> {
@@ -477,8 +426,7 @@ pub mod parsing {
             pound_token: input.parse()?,
             style: AttrStyle::Inner(input.parse()?),
             bracket_token: bracketed!(content in input),
-            path: content.call(Path::parse_mod_style)?,
-            tokens: content.parse()?,
+            meta: content.parse()?,
         })
     }
 
@@ -488,40 +436,14 @@ pub mod parsing {
             pound_token: input.parse()?,
             style: AttrStyle::Outer,
             bracket_token: bracketed!(content in input),
-            path: content.call(Path::parse_mod_style)?,
-            tokens: content.parse()?,
-        })
-    }
-
-    // Like Path::parse_mod_style but accepts keywords in the path.
-    fn parse_meta_path(input: ParseStream) -> Result<Path> {
-        Ok(Path {
-            leading_colon: input.parse()?,
-            segments: {
-                let mut segments = Punctuated::new();
-                while input.peek(Ident::peek_any) {
-                    let ident = Ident::parse_any(input)?;
-                    segments.push_value(PathSegment::from(ident));
-                    if !input.peek(Token![::]) {
-                        break;
-                    }
-                    let punct = input.parse()?;
-                    segments.push_punct(punct);
-                }
-                if segments.is_empty() {
-                    return Err(input.error("expected path"));
-                } else if segments.trailing_punct() {
-                    return Err(input.error("expected path segment"));
-                }
-                segments
-            },
+            meta: content.parse()?,
         })
     }
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for Meta {
         fn parse(input: ParseStream) -> Result<Self> {
-            let path = input.call(parse_meta_path)?;
+            let path = input.call(Path::parse_mod_style)?;
             parse_meta_after_path(path, input)
         }
     }
@@ -529,7 +451,7 @@ pub mod parsing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for MetaList {
         fn parse(input: ParseStream) -> Result<Self> {
-            let path = input.call(parse_meta_path)?;
+            let path = input.call(Path::parse_mod_style)?;
             parse_meta_list_after_path(path, input)
         }
     }
@@ -537,28 +459,13 @@ pub mod parsing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for MetaNameValue {
         fn parse(input: ParseStream) -> Result<Self> {
-            let path = input.call(parse_meta_path)?;
+            let path = input.call(Path::parse_mod_style)?;
             parse_meta_name_value_after_path(path, input)
         }
     }
 
-    #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
-    impl Parse for NestedMeta {
-        fn parse(input: ParseStream) -> Result<Self> {
-            if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) {
-                input.parse().map(NestedMeta::Lit)
-            } else if input.peek(Ident::peek_any)
-                || input.peek(Token![::]) && input.peek3(Ident::peek_any)
-            {
-                input.parse().map(NestedMeta::Meta)
-            } else {
-                Err(input.error("expected identifier or literal"))
-            }
-        }
-    }
-
     pub fn parse_meta_after_path(path: Path, input: ParseStream) -> Result<Meta> {
-        if input.peek(token::Paren) {
+        if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) {
             parse_meta_list_after_path(path, input).map(Meta::List)
         } else if input.peek(Token![=]) {
             parse_meta_name_value_after_path(path, input).map(Meta::NameValue)
@@ -568,11 +475,11 @@ pub mod parsing {
     }
 
     fn parse_meta_list_after_path(path: Path, input: ParseStream) -> Result<MetaList> {
-        let content;
+        let (delimiter, tokens) = mac::parse_delimiter(input)?;
         Ok(MetaList {
             path,
-            paren_token: parenthesized!(content in input),
-            nested: content.parse_terminated(NestedMeta::parse, Token![,])?,
+            delimiter,
+            tokens,
         })
     }
 
@@ -580,7 +487,7 @@ pub mod parsing {
         Ok(MetaNameValue {
             path,
             eq_token: input.parse()?,
-            lit: input.parse()?,
+            value: input.parse()?,
         })
     }
 }
@@ -599,8 +506,7 @@ mod printing {
                 b.to_tokens(tokens);
             }
             self.bracket_token.surround(tokens, |tokens| {
-                self.path.to_tokens(tokens);
-                self.tokens.to_tokens(tokens);
+                self.meta.to_tokens(tokens);
             });
         }
     }
@@ -609,9 +515,7 @@ mod printing {
     impl ToTokens for MetaList {
         fn to_tokens(&self, tokens: &mut TokenStream) {
             self.path.to_tokens(tokens);
-            self.paren_token.surround(tokens, |tokens| {
-                self.nested.to_tokens(tokens);
-            });
+            self.delimiter.surround(tokens, self.tokens.clone());
         }
     }
 
@@ -620,7 +524,7 @@ mod printing {
         fn to_tokens(&self, tokens: &mut TokenStream) {
             self.path.to_tokens(tokens);
             self.eq_token.to_tokens(tokens);
-            self.lit.to_tokens(tokens);
+            self.value.to_tokens(tokens);
         }
     }
 }
diff --git a/src/gen/clone.rs b/src/gen/clone.rs
index 4c1cef50..3a65617f 100644
--- a/src/gen/clone.rs
+++ b/src/gen/clone.rs
@@ -57,8 +57,7 @@ impl Clone for Attribute {
             pound_token: self.pound_token.clone(),
             style: self.style.clone(),
             bracket_token: self.bracket_token.clone(),
-            path: self.path.clone(),
-            tokens: self.tokens.clone(),
+            meta: self.meta.clone(),
         }
     }
 }
@@ -1407,8 +1406,8 @@ impl Clone for MetaList {
     fn clone(&self) -> Self {
         MetaList {
             path: self.path.clone(),
-            paren_token: self.paren_token.clone(),
-            nested: self.nested.clone(),
+            delimiter: self.delimiter.clone(),
+            tokens: self.tokens.clone(),
         }
     }
 }
@@ -1419,7 +1418,7 @@ impl Clone for MetaNameValue {
         MetaNameValue {
             path: self.path.clone(),
             eq_token: self.eq_token.clone(),
-            lit: self.lit.clone(),
+            value: self.value.clone(),
         }
     }
 }
@@ -1437,16 +1436,6 @@ impl Clone for MethodTurbofish {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "clone-impls")))]
-impl Clone for NestedMeta {
-    fn clone(&self) -> Self {
-        match self {
-            NestedMeta::Meta(v0) => NestedMeta::Meta(v0.clone()),
-            NestedMeta::Lit(v0) => NestedMeta::Lit(v0.clone()),
-        }
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
-#[cfg_attr(doc_cfg, doc(cfg(feature = "clone-impls")))]
 impl Clone for ParenthesizedGenericArguments {
     fn clone(&self) -> Self {
         ParenthesizedGenericArguments {
diff --git a/src/gen/debug.rs b/src/gen/debug.rs
index b9eb74de..ea444236 100644
--- a/src/gen/debug.rs
+++ b/src/gen/debug.rs
@@ -61,8 +61,7 @@ impl Debug for Attribute {
         formatter.field("pound_token", &self.pound_token);
         formatter.field("style", &self.style);
         formatter.field("bracket_token", &self.bracket_token);
-        formatter.field("path", &self.path);
-        formatter.field("tokens", &self.tokens);
+        formatter.field("meta", &self.meta);
         formatter.finish()
     }
 }
@@ -1949,8 +1948,8 @@ impl Debug for MetaList {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         let mut formatter = formatter.debug_struct("MetaList");
         formatter.field("path", &self.path);
-        formatter.field("paren_token", &self.paren_token);
-        formatter.field("nested", &self.nested);
+        formatter.field("delimiter", &self.delimiter);
+        formatter.field("tokens", &self.tokens);
         formatter.finish()
     }
 }
@@ -1961,7 +1960,7 @@ impl Debug for MetaNameValue {
         let mut formatter = formatter.debug_struct("MetaNameValue");
         formatter.field("path", &self.path);
         formatter.field("eq_token", &self.eq_token);
-        formatter.field("lit", &self.lit);
+        formatter.field("value", &self.value);
         formatter.finish()
     }
 }
@@ -1979,24 +1978,6 @@ impl Debug for MethodTurbofish {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
-impl Debug for NestedMeta {
-    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            NestedMeta::Meta(v0) => {
-                let mut formatter = formatter.debug_tuple("Meta");
-                formatter.field(v0);
-                formatter.finish()
-            }
-            NestedMeta::Lit(v0) => {
-                let mut formatter = formatter.debug_tuple("Lit");
-                formatter.field(v0);
-                formatter.finish()
-            }
-        }
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
-#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Debug for ParenthesizedGenericArguments {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         let mut formatter = formatter.debug_struct("ParenthesizedGenericArguments");
diff --git a/src/gen/eq.rs b/src/gen/eq.rs
index b3b10b2a..30abd41e 100644
--- a/src/gen/eq.rs
+++ b/src/gen/eq.rs
@@ -56,8 +56,7 @@ impl Eq for Attribute {}
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl PartialEq for Attribute {
     fn eq(&self, other: &Self) -> bool {
-        self.style == other.style && self.path == other.path
-            && TokenStreamHelper(&self.tokens) == TokenStreamHelper(&other.tokens)
+        self.style == other.style && self.meta == other.meta
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
@@ -1336,7 +1335,8 @@ impl Eq for MetaList {}
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl PartialEq for MetaList {
     fn eq(&self, other: &Self) -> bool {
-        self.path == other.path && self.nested == other.nested
+        self.path == other.path && self.delimiter == other.delimiter
+            && TokenStreamHelper(&self.tokens) == TokenStreamHelper(&other.tokens)
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
@@ -1346,7 +1346,7 @@ impl Eq for MetaNameValue {}
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl PartialEq for MetaNameValue {
     fn eq(&self, other: &Self) -> bool {
-        self.path == other.path && self.lit == other.lit
+        self.path == other.path && self.value == other.value
     }
 }
 #[cfg(feature = "full")]
@@ -1361,20 +1361,6 @@ impl PartialEq for MethodTurbofish {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
-impl Eq for NestedMeta {}
-#[cfg(any(feature = "derive", feature = "full"))]
-#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
-impl PartialEq for NestedMeta {
-    fn eq(&self, other: &Self) -> bool {
-        match (self, other) {
-            (NestedMeta::Meta(self0), NestedMeta::Meta(other0)) => self0 == other0,
-            (NestedMeta::Lit(self0), NestedMeta::Lit(other0)) => self0 == other0,
-            _ => false,
-        }
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
-#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Eq for ParenthesizedGenericArguments {}
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
diff --git a/src/gen/fold.rs b/src/gen/fold.rs
index 3e35d280..9d68c0ca 100644
--- a/src/gen/fold.rs
+++ b/src/gen/fold.rs
@@ -494,10 +494,6 @@ pub trait Fold {
         fold_method_turbofish(self, i)
     }
     #[cfg(any(feature = "derive", feature = "full"))]
-    fn fold_nested_meta(&mut self, i: NestedMeta) -> NestedMeta {
-        fold_nested_meta(self, i)
-    }
-    #[cfg(any(feature = "derive", feature = "full"))]
     fn fold_parenthesized_generic_arguments(
         &mut self,
         i: ParenthesizedGenericArguments,
@@ -844,8 +840,7 @@ where
         pound_token: Token![#](tokens_helper(f, &node.pound_token.spans)),
         style: f.fold_attr_style(node.style),
         bracket_token: Bracket(tokens_helper(f, &node.bracket_token.span)),
-        path: f.fold_path(node.path),
-        tokens: node.tokens,
+        meta: f.fold_meta(node.meta),
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
@@ -2420,8 +2415,8 @@ where
 {
     MetaList {
         path: f.fold_path(node.path),
-        paren_token: Paren(tokens_helper(f, &node.paren_token.span)),
-        nested: FoldHelper::lift(node.nested, |it| f.fold_nested_meta(it)),
+        delimiter: f.fold_macro_delimiter(node.delimiter),
+        tokens: node.tokens,
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
@@ -2432,7 +2427,7 @@ where
     MetaNameValue {
         path: f.fold_path(node.path),
         eq_token: Token![=](tokens_helper(f, &node.eq_token.spans)),
-        lit: f.fold_lit(node.lit),
+        value: f.fold_expr(node.value),
     }
 }
 #[cfg(feature = "full")]
@@ -2448,16 +2443,6 @@ where
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
-pub fn fold_nested_meta<F>(f: &mut F, node: NestedMeta) -> NestedMeta
-where
-    F: Fold + ?Sized,
-{
-    match node {
-        NestedMeta::Meta(_binding_0) => NestedMeta::Meta(f.fold_meta(_binding_0)),
-        NestedMeta::Lit(_binding_0) => NestedMeta::Lit(f.fold_lit(_binding_0)),
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
 pub fn fold_parenthesized_generic_arguments<F>(
     f: &mut F,
     node: ParenthesizedGenericArguments,
diff --git a/src/gen/hash.rs b/src/gen/hash.rs
index 65093541..0bb2d1dc 100644
--- a/src/gen/hash.rs
+++ b/src/gen/hash.rs
@@ -65,8 +65,7 @@ impl Hash for Attribute {
         H: Hasher,
     {
         self.style.hash(state);
-        self.path.hash(state);
-        TokenStreamHelper(&self.tokens).hash(state);
+        self.meta.hash(state);
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
@@ -1798,7 +1797,8 @@ impl Hash for MetaList {
         H: Hasher,
     {
         self.path.hash(state);
-        self.nested.hash(state);
+        self.delimiter.hash(state);
+        TokenStreamHelper(&self.tokens).hash(state);
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
@@ -1809,7 +1809,7 @@ impl Hash for MetaNameValue {
         H: Hasher,
     {
         self.path.hash(state);
-        self.lit.hash(state);
+        self.value.hash(state);
     }
 }
 #[cfg(feature = "full")]
@@ -1824,25 +1824,6 @@ impl Hash for MethodTurbofish {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
-impl Hash for NestedMeta {
-    fn hash<H>(&self, state: &mut H)
-    where
-        H: Hasher,
-    {
-        match self {
-            NestedMeta::Meta(v0) => {
-                state.write_u8(0u8);
-                v0.hash(state);
-            }
-            NestedMeta::Lit(v0) => {
-                state.write_u8(1u8);
-                v0.hash(state);
-            }
-        }
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
-#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Hash for ParenthesizedGenericArguments {
     fn hash<H>(&self, state: &mut H)
     where
diff --git a/src/gen/visit.rs b/src/gen/visit.rs
index c45ded63..b2167031 100644
--- a/src/gen/visit.rs
+++ b/src/gen/visit.rs
@@ -493,10 +493,6 @@ pub trait Visit<'ast> {
         visit_method_turbofish(self, i);
     }
     #[cfg(any(feature = "derive", feature = "full"))]
-    fn visit_nested_meta(&mut self, i: &'ast NestedMeta) {
-        visit_nested_meta(self, i);
-    }
-    #[cfg(any(feature = "derive", feature = "full"))]
     fn visit_parenthesized_generic_arguments(
         &mut self,
         i: &'ast ParenthesizedGenericArguments,
@@ -845,8 +841,7 @@ where
     tokens_helper(v, &node.pound_token.spans);
     v.visit_attr_style(&node.style);
     tokens_helper(v, &node.bracket_token.span);
-    v.visit_path(&node.path);
-    skip!(node.tokens);
+    v.visit_meta(&node.meta);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_bare_fn_arg<'ast, V>(v: &mut V, node: &'ast BareFnArg)
@@ -2684,14 +2679,8 @@ where
     V: Visit<'ast> + ?Sized,
 {
     v.visit_path(&node.path);
-    tokens_helper(v, &node.paren_token.span);
-    for el in Punctuated::pairs(&node.nested) {
-        let (it, p) = el.into_tuple();
-        v.visit_nested_meta(it);
-        if let Some(p) = p {
-            tokens_helper(v, &p.spans);
-        }
-    }
+    v.visit_macro_delimiter(&node.delimiter);
+    skip!(node.tokens);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_meta_name_value<'ast, V>(v: &mut V, node: &'ast MetaNameValue)
@@ -2700,7 +2689,7 @@ where
 {
     v.visit_path(&node.path);
     tokens_helper(v, &node.eq_token.spans);
-    v.visit_lit(&node.lit);
+    v.visit_expr(&node.value);
 }
 #[cfg(feature = "full")]
 pub fn visit_method_turbofish<'ast, V>(v: &mut V, node: &'ast MethodTurbofish)
@@ -2719,20 +2708,6 @@ where
     tokens_helper(v, &node.gt_token.spans);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
-pub fn visit_nested_meta<'ast, V>(v: &mut V, node: &'ast NestedMeta)
-where
-    V: Visit<'ast> + ?Sized,
-{
-    match node {
-        NestedMeta::Meta(_binding_0) => {
-            v.visit_meta(_binding_0);
-        }
-        NestedMeta::Lit(_binding_0) => {
-            v.visit_lit(_binding_0);
-        }
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_parenthesized_generic_arguments<'ast, V>(
     v: &mut V,
     node: &'ast ParenthesizedGenericArguments,
diff --git a/src/gen/visit_mut.rs b/src/gen/visit_mut.rs
index 25437cae..1bb2ea15 100644
--- a/src/gen/visit_mut.rs
+++ b/src/gen/visit_mut.rs
@@ -494,10 +494,6 @@ pub trait VisitMut {
         visit_method_turbofish_mut(self, i);
     }
     #[cfg(any(feature = "derive", feature = "full"))]
-    fn visit_nested_meta_mut(&mut self, i: &mut NestedMeta) {
-        visit_nested_meta_mut(self, i);
-    }
-    #[cfg(any(feature = "derive", feature = "full"))]
     fn visit_parenthesized_generic_arguments_mut(
         &mut self,
         i: &mut ParenthesizedGenericArguments,
@@ -846,8 +842,7 @@ where
     tokens_helper(v, &mut node.pound_token.spans);
     v.visit_attr_style_mut(&mut node.style);
     tokens_helper(v, &mut node.bracket_token.span);
-    v.visit_path_mut(&mut node.path);
-    skip!(node.tokens);
+    v.visit_meta_mut(&mut node.meta);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_bare_fn_arg_mut<V>(v: &mut V, node: &mut BareFnArg)
@@ -2684,14 +2679,8 @@ where
     V: VisitMut + ?Sized,
 {
     v.visit_path_mut(&mut node.path);
-    tokens_helper(v, &mut node.paren_token.span);
-    for el in Punctuated::pairs_mut(&mut node.nested) {
-        let (it, p) = el.into_tuple();
-        v.visit_nested_meta_mut(it);
-        if let Some(p) = p {
-            tokens_helper(v, &mut p.spans);
-        }
-    }
+    v.visit_macro_delimiter_mut(&mut node.delimiter);
+    skip!(node.tokens);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_meta_name_value_mut<V>(v: &mut V, node: &mut MetaNameValue)
@@ -2700,7 +2689,7 @@ where
 {
     v.visit_path_mut(&mut node.path);
     tokens_helper(v, &mut node.eq_token.spans);
-    v.visit_lit_mut(&mut node.lit);
+    v.visit_expr_mut(&mut node.value);
 }
 #[cfg(feature = "full")]
 pub fn visit_method_turbofish_mut<V>(v: &mut V, node: &mut MethodTurbofish)
@@ -2719,20 +2708,6 @@ where
     tokens_helper(v, &mut node.gt_token.spans);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
-pub fn visit_nested_meta_mut<V>(v: &mut V, node: &mut NestedMeta)
-where
-    V: VisitMut + ?Sized,
-{
-    match node {
-        NestedMeta::Meta(_binding_0) => {
-            v.visit_meta_mut(_binding_0);
-        }
-        NestedMeta::Lit(_binding_0) => {
-            v.visit_lit_mut(_binding_0);
-        }
-    }
-}
-#[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_parenthesized_generic_arguments_mut<V>(
     v: &mut V,
     node: &mut ParenthesizedGenericArguments,
diff --git a/src/lib.rs b/src/lib.rs
index 11cdfd4d..4dae9fde 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -316,7 +316,7 @@ pub use crate::ident::Ident;
 #[cfg(any(feature = "full", feature = "derive"))]
 mod attr;
 #[cfg(any(feature = "full", feature = "derive"))]
-pub use crate::attr::{AttrStyle, Attribute, Meta, MetaList, MetaNameValue, NestedMeta};
+pub use crate::attr::{AttrStyle, Attribute, Meta, MetaList, MetaNameValue};
 
 mod bigint;
 
diff --git a/src/lit.rs b/src/lit.rs
index 130b40ed..c39b7439 100644
--- a/src/lit.rs
+++ b/src/lit.rs
@@ -142,8 +142,7 @@ impl LitStr {
     /// # Example
     ///
     /// ```
-    /// use proc_macro2::Span;
-    /// use syn::{Attribute, Error, Ident, Lit, Meta, MetaNameValue, Path, Result};
+    /// use syn::{Attribute, Error, Expr, Lit, Meta, Path, Result};
     ///
     /// // Parses the path from an attribute that looks like:
     /// //
@@ -151,19 +150,20 @@ impl LitStr {
     /// //
     /// // or returns `None` if the input is some other attribute.
     /// fn get_path(attr: &Attribute) -> Result<Option<Path>> {
-    ///     if !attr.path.is_ident("path") {
+    ///     if !attr.path().is_ident("path") {
     ///         return Ok(None);
     ///     }
     ///
-    ///     match attr.parse_meta()? {
-    ///         Meta::NameValue(MetaNameValue { lit: Lit::Str(lit_str), .. }) => {
-    ///             lit_str.parse().map(Some)
-    ///         }
-    ///         _ => {
-    ///             let message = "expected #[path = \"...\"]";
-    ///             Err(Error::new_spanned(attr, message))
+    ///     if let Meta::NameValue(meta) = &attr.meta {
+    ///         if let Expr::Lit(expr) = &meta.value {
+    ///             if let Lit::Str(lit_str) = &expr.lit {
+    ///                 return lit_str.parse().map(Some);
+    ///             }
     ///         }
     ///     }
+    ///
+    ///     let message = "expected #[path = \"...\"]";
+    ///     Err(Error::new_spanned(attr, message))
     /// }
     /// ```
     #[cfg(feature = "parsing")]
diff --git a/src/parse.rs b/src/parse.rs
index 5ceb7233..95911eb9 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -1237,13 +1237,6 @@ pub trait Parser: Sized {
         let _ = scope;
         self.parse2(tokens)
     }
-
-    // Not public API.
-    #[doc(hidden)]
-    #[cfg(any(feature = "full", feature = "derive"))]
-    fn __parse_stream(self, input: ParseStream) -> Result<Self::Output> {
-        input.parse().and_then(|tokens| self.parse2(tokens))
-    }
 }
 
 fn tokens_to_parse_buffer(tokens: &TokenBuffer) -> ParseBuffer {
@@ -1285,11 +1278,6 @@ where
             Ok(node)
         }
     }
-
-    #[cfg(any(feature = "full", feature = "derive"))]
-    fn __parse_stream(self, input: ParseStream) -> Result<Self::Output> {
-        self(input)
-    }
 }
 
 #[cfg(any(feature = "full", feature = "derive"))]
@@ -1297,11 +1285,6 @@ pub(crate) fn parse_scoped<F: Parser>(f: F, scope: Span, tokens: TokenStream) ->
     f.__parse_scoped(scope, tokens)
 }
 
-#[cfg(any(feature = "full", feature = "derive"))]
-pub(crate) fn parse_stream<F: Parser>(f: F, input: ParseStream) -> Result<F::Output> {
-    f.__parse_stream(input)
-}
-
 /// An empty syntax tree node that consumes no tokens when parsed.
 ///
 /// This is useful for attribute macros that want to ensure they are not
diff --git a/src/path.rs b/src/path.rs
index ca349e7e..d0129448 100644
--- a/src/path.rs
+++ b/src/path.rs
@@ -552,17 +552,18 @@ pub mod parsing {
         /// # Example
         ///
         /// ```
-        /// use syn::{Attribute, Error, Meta, NestedMeta, Result};
+        /// use proc_macro2::TokenStream;
+        /// use syn::{Attribute, Error, Meta, Result};
         /// # use std::iter::FromIterator;
         ///
-        /// fn get_serde_meta_items(attr: &Attribute) -> Result<Vec<NestedMeta>> {
-        ///     if attr.path.is_ident("serde") {
-        ///         match attr.parse_meta()? {
-        ///             Meta::List(meta) => Ok(Vec::from_iter(meta.nested)),
+        /// fn get_serde_meta_item(attr: &Attribute) -> Result<Option<&TokenStream>> {
+        ///     if attr.path().is_ident("serde") {
+        ///         match &attr.meta {
+        ///             Meta::List(meta) => Ok(Some(&meta.tokens)),
         ///             bad => Err(Error::new_spanned(bad, "unrecognized attribute")),
         ///         }
         ///     } else {
-        ///         Ok(Vec::new())
+        ///         Ok(None)
         ///     }
         /// }
         /// ```
diff --git a/syn.json b/syn.json
index 13fb0703..62cba985 100644
--- a/syn.json
+++ b/syn.json
@@ -130,11 +130,8 @@
         "bracket_token": {
           "group": "Bracket"
         },
-        "path": {
-          "syn": "Path"
-        },
-        "tokens": {
-          "proc_macro2": "TokenStream"
+        "meta": {
+          "syn": "Meta"
         }
       }
     },
@@ -3545,16 +3542,11 @@
         "path": {
           "syn": "Path"
         },
-        "paren_token": {
-          "group": "Paren"
+        "delimiter": {
+          "syn": "MacroDelimiter"
         },
-        "nested": {
-          "punctuated": {
-            "element": {
-              "syn": "NestedMeta"
-            },
-            "punct": "Comma"
-          }
+        "tokens": {
+          "proc_macro2": "TokenStream"
         }
       }
     },
@@ -3573,8 +3565,8 @@
         "eq_token": {
           "token": "Eq"
         },
-        "lit": {
-          "syn": "Lit"
+        "value": {
+          "syn": "Expr"
         }
       }
     },
@@ -3605,27 +3597,6 @@
         }
       }
     },
-    {
-      "ident": "NestedMeta",
-      "features": {
-        "any": [
-          "derive",
-          "full"
-        ]
-      },
-      "variants": {
-        "Meta": [
-          {
-            "syn": "Meta"
-          }
-        ],
-        "Lit": [
-          {
-            "syn": "Lit"
-          }
-        ]
-      }
-    },
     {
       "ident": "ParenthesizedGenericArguments",
       "features": {


Test patch:
diff --git a/tests/debug/gen.rs b/tests/debug/gen.rs
index 9f8c4076..1108de86 100644
--- a/tests/debug/gen.rs
+++ b/tests/debug/gen.rs
@@ -105,8 +105,7 @@ impl Debug for Lite<syn::Attribute> {
         let _val = &self.value;
         let mut formatter = formatter.debug_struct("Attribute");
         formatter.field("style", Lite(&_val.style));
-        formatter.field("path", Lite(&_val.path));
-        formatter.field("tokens", Lite(&_val.tokens));
+        formatter.field("meta", Lite(&_val.meta));
         formatter.finish()
     }
 }
@@ -3582,15 +3581,14 @@ impl Debug for Lite<syn::Meta> {
             syn::Meta::List(_val) => {
                 let mut formatter = formatter.debug_struct("Meta::List");
                 formatter.field("path", Lite(&_val.path));
-                if !_val.nested.is_empty() {
-                    formatter.field("nested", Lite(&_val.nested));
-                }
+                formatter.field("delimiter", Lite(&_val.delimiter));
+                formatter.field("tokens", Lite(&_val.tokens));
                 formatter.finish()
             }
             syn::Meta::NameValue(_val) => {
                 let mut formatter = formatter.debug_struct("Meta::NameValue");
                 formatter.field("path", Lite(&_val.path));
-                formatter.field("lit", Lite(&_val.lit));
+                formatter.field("value", Lite(&_val.value));
                 formatter.finish()
             }
         }
@@ -3601,9 +3599,8 @@ impl Debug for Lite<syn::MetaList> {
         let _val = &self.value;
         let mut formatter = formatter.debug_struct("MetaList");
         formatter.field("path", Lite(&_val.path));
-        if !_val.nested.is_empty() {
-            formatter.field("nested", Lite(&_val.nested));
-        }
+        formatter.field("delimiter", Lite(&_val.delimiter));
+        formatter.field("tokens", Lite(&_val.tokens));
         formatter.finish()
     }
 }
@@ -3612,7 +3609,7 @@ impl Debug for Lite<syn::MetaNameValue> {
         let _val = &self.value;
         let mut formatter = formatter.debug_struct("MetaNameValue");
         formatter.field("path", Lite(&_val.path));
-        formatter.field("lit", Lite(&_val.lit));
+        formatter.field("value", Lite(&_val.value));
         formatter.finish()
     }
 }
@@ -3626,27 +3623,6 @@ impl Debug for Lite<syn::MethodTurbofish> {
         formatter.finish()
     }
 }
-impl Debug for Lite<syn::NestedMeta> {
-    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        let _val = &self.value;
-        match _val {
-            syn::NestedMeta::Meta(_val) => {
-                formatter.write_str("Meta")?;
-                formatter.write_str("(")?;
-                Debug::fmt(Lite(_val), formatter)?;
-                formatter.write_str(")")?;
-                Ok(())
-            }
-            syn::NestedMeta::Lit(_val) => {
-                formatter.write_str("Lit")?;
-                formatter.write_str("(")?;
-                Debug::fmt(Lite(_val), formatter)?;
-                formatter.write_str(")")?;
-                Ok(())
-            }
-        }
-    }
-}
 impl Debug for Lite<syn::ParenthesizedGenericArguments> {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         let _val = &self.value;
diff --git a/tests/test_attribute.rs b/tests/test_attribute.rs
index c26bd090..c3052b0b 100644
--- a/tests/test_attribute.rs
+++ b/tests/test_attribute.rs
@@ -34,7 +34,9 @@ fn test_meta_item_name_value() {
                 },
             ],
         },
-        lit: 5,
+        value: Expr::Lit {
+            lit: 5,
+        },
     }
     "###);
 }
@@ -53,8 +55,10 @@ fn test_meta_item_bool_value() {
                 },
             ],
         },
-        lit: Lit::Bool {
-            value: true,
+        value: Expr::Lit {
+            lit: Lit::Bool {
+                value: true,
+            },
         },
     }
     "###);
@@ -71,8 +75,10 @@ fn test_meta_item_bool_value() {
                 },
             ],
         },
-        lit: Lit::Bool {
-            value: false,
+        value: Expr::Lit {
+            lit: Lit::Bool {
+                value: false,
+            },
         },
     }
     "###);
@@ -92,9 +98,8 @@ fn test_meta_item_list_lit() {
                 },
             ],
         },
-        nested: [
-            Lit(5),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`5`),
     }
     "###);
 }
@@ -113,16 +118,8 @@ fn test_meta_item_list_word() {
                 },
             ],
         },
-        nested: [
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "bar",
-                        arguments: None,
-                    },
-                ],
-            })),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`bar`),
     }
     "###);
 }
@@ -141,19 +138,8 @@ fn test_meta_item_list_name_value() {
                 },
             ],
         },
-        nested: [
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "bar",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: 5,
-            }),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`bar = 5`),
     }
     "###);
 }
@@ -172,21 +158,8 @@ fn test_meta_item_list_bool_value() {
                 },
             ],
         },
-        nested: [
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "bar",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: Lit::Bool {
-                    value: true,
-                },
-            }),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`bar = true`),
     }
     "###);
 }
@@ -205,58 +178,8 @@ fn test_meta_item_multiple() {
                 },
             ],
         },
-        nested: [
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "word",
-                        arguments: None,
-                    },
-                ],
-            })),
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "name",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: 5,
-            }),
-            Meta(Meta::List {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "list",
-                            arguments: None,
-                        },
-                    ],
-                },
-                nested: [
-                    Meta(Meta::NameValue {
-                        path: Path {
-                            segments: [
-                                PathSegment {
-                                    ident: "name2",
-                                    arguments: None,
-                                },
-                            ],
-                        },
-                        lit: 6,
-                    }),
-                ],
-            }),
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "word2",
-                        arguments: None,
-                    },
-                ],
-            })),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`word , name = 5 , list (name2 = 6) , word2`),
     }
     "###);
 }
@@ -275,11 +198,8 @@ fn test_bool_lit() {
                 },
             ],
         },
-        nested: [
-            Lit(Lit::Bool {
-                value: true,
-            }),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`true`),
     }
     "###);
 }
@@ -298,30 +218,8 @@ fn test_negative_lit() {
                 },
             ],
         },
-        nested: [
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "min",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: -1,
-            }),
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "max",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: 200,
-            }),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`min = - 1 , max = 200`),
     }
     "###);
 }
@@ -332,5 +230,5 @@ fn test(input: &str) -> Meta {
     assert_eq!(attrs.len(), 1);
     let attr = attrs.into_iter().next().unwrap();
 
-    attr.parse_meta().unwrap()
+    attr.meta
 }
diff --git a/tests/test_derive_input.rs b/tests/test_derive_input.rs
index 1eff0118..29fdc0c1 100644
--- a/tests/test_derive_input.rs
+++ b/tests/test_derive_input.rs
@@ -40,15 +40,18 @@ fn test_struct() {
         attrs: [
             Attribute {
                 style: Outer,
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "derive",
-                            arguments: None,
-                        },
-                    ],
+                meta: Meta::List {
+                    path: Path {
+                        segments: [
+                            PathSegment {
+                                ident: "derive",
+                                arguments: None,
+                            },
+                        ],
+                    },
+                    delimiter: Paren,
+                    tokens: TokenStream(`Debug , Clone`),
                 },
-                tokens: TokenStream(`(Debug , Clone)`),
             },
         ],
         vis: Visibility::Public,
@@ -106,7 +109,7 @@ fn test_struct() {
     }
     "###);
 
-    snapshot!(input.attrs[0].parse_meta().unwrap(), @r###"
+    snapshot!(&input.attrs[0].meta, @r###"
     Meta::List {
         path: Path {
             segments: [
@@ -116,24 +119,8 @@ fn test_struct() {
                 },
             ],
         },
-        nested: [
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "Debug",
-                        arguments: None,
-                    },
-                ],
-            })),
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "Clone",
-                        arguments: None,
-                    },
-                ],
-            })),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`Debug , Clone`),
     }
     "###);
 }
@@ -213,27 +200,30 @@ fn test_enum() {
         attrs: [
             Attribute {
                 style: Outer,
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "doc",
-                            arguments: None,
-                        },
-                    ],
+                meta: Meta::NameValue {
+                    path: Path {
+                        segments: [
+                            PathSegment {
+                                ident: "doc",
+                                arguments: None,
+                            },
+                        ],
+                    },
+                    value: Expr::Lit {
+                        lit: " See the std::result module documentation for details.",
+                    },
                 },
-                tokens: TokenStream(`= r" See the std::result module documentation for details."`),
             },
             Attribute {
                 style: Outer,
-                path: Path {
+                meta: Path(Path {
                     segments: [
                         PathSegment {
                             ident: "must_use",
                             arguments: None,
                         },
                     ],
-                },
-                tokens: TokenStream(``),
+                }),
             },
         ],
         vis: Visibility::Public,
@@ -323,11 +313,7 @@ fn test_enum() {
     }
     "###);
 
-    let meta_items: Vec<_> = input
-        .attrs
-        .into_iter()
-        .map(|attr| attr.parse_meta().unwrap())
-        .collect();
+    let meta_items: Vec<_> = input.attrs.into_iter().map(|attr| attr.meta).collect();
 
     snapshot!(meta_items, @r###"
     [
@@ -340,7 +326,9 @@ fn test_enum() {
                     },
                 ],
             },
-            lit: " See the std::result module documentation for details.",
+            value: Expr::Lit {
+                lit: " See the std::result module documentation for details.",
+            },
         },
         Path(Path {
             segments: [
@@ -354,48 +342,6 @@ fn test_enum() {
     "###);
 }
 
-#[test]
-fn test_attr_with_path() {
-    let input = quote! {
-        #[::attr_args::identity
-            fn main() { assert_eq!(foo(), "Hello, world!"); }]
-        struct Dummy;
-    };
-
-    snapshot!(input as DeriveInput, @r###"
-    DeriveInput {
-        attrs: [
-            Attribute {
-                style: Outer,
-                path: Path {
-                    leading_colon: Some,
-                    segments: [
-                        PathSegment {
-                            ident: "attr_args",
-                            arguments: None,
-                        },
-                        PathSegment {
-                            ident: "identity",
-                            arguments: None,
-                        },
-                    ],
-                },
-                tokens: TokenStream(`fn main () { assert_eq ! (foo () , "Hello, world!") ; }`),
-            },
-        ],
-        vis: Inherited,
-        ident: "Dummy",
-        generics: Generics,
-        data: Data::Struct {
-            fields: Unit,
-            semi_token: Some,
-        },
-    }
-    "###);
-
-    assert!(input.attrs[0].parse_meta().is_err());
-}
-
 #[test]
 fn test_attr_with_non_mod_style_path() {
     let input = quote! {
@@ -403,33 +349,7 @@ fn test_attr_with_non_mod_style_path() {
         struct S;
     };
 
-    snapshot!(input as DeriveInput, @r###"
-    DeriveInput {
-        attrs: [
-            Attribute {
-                style: Outer,
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "inert",
-                            arguments: None,
-                        },
-                    ],
-                },
-                tokens: TokenStream(`< T >`),
-            },
-        ],
-        vis: Inherited,
-        ident: "S",
-        generics: Generics,
-        data: Data::Struct {
-            fields: Unit,
-            semi_token: Some,
-        },
-    }
-    "###);
-
-    assert!(input.attrs[0].parse_meta().is_err());
+    syn::parse2::<DeriveInput>(input).unwrap_err();
 }
 
 #[test]
@@ -444,7 +364,7 @@ fn test_attr_with_mod_style_path_with_self() {
         attrs: [
             Attribute {
                 style: Outer,
-                path: Path {
+                meta: Path(Path {
                     segments: [
                         PathSegment {
                             ident: "foo",
@@ -455,8 +375,7 @@ fn test_attr_with_mod_style_path_with_self() {
                             arguments: None,
                         },
                     ],
-                },
-                tokens: TokenStream(``),
+                }),
             },
         ],
         vis: Inherited,
@@ -469,7 +388,7 @@ fn test_attr_with_mod_style_path_with_self() {
     }
     "###);
 
-    snapshot!(input.attrs[0].parse_meta().unwrap(), @r###"
+    snapshot!(&input.attrs[0].meta, @r###"
     Path(Path {
         segments: [
             PathSegment {
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index e5b151fd..dbed4cf0 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -135,15 +135,14 @@ fn test_macro_variable_func() {
         attrs: [
             Attribute {
                 style: Outer,
-                path: Path {
+                meta: Path(Path {
                     segments: [
                         PathSegment {
                             ident: "outside",
                             arguments: None,
                         },
                     ],
-                },
-                tokens: TokenStream(``),
+                }),
             },
         ],
         func: Expr::Group {
@@ -151,15 +150,14 @@ fn test_macro_variable_func() {
                 attrs: [
                     Attribute {
                         style: Outer,
-                        path: Path {
+                        meta: Path(Path {
                             segments: [
                                 PathSegment {
                                     ident: "inside",
                                     arguments: None,
                                 },
                             ],
-                        },
-                        tokens: TokenStream(``),
+                        }),
                     },
                 ],
                 path: Path {
@@ -262,15 +260,14 @@ fn test_macro_variable_match_arm() {
                         attrs: [
                             Attribute {
                                 style: Outer,
-                                path: Path {
+                                meta: Path(Path {
                                     segments: [
                                         PathSegment {
                                             ident: "a",
                                             arguments: None,
                                         },
                                     ],
-                                },
-                                tokens: TokenStream(``),
+                                }),
                             },
                         ],
                     },
diff --git a/tests/test_generics.rs b/tests/test_generics.rs
index 330f02f4..51c74aac 100644
--- a/tests/test_generics.rs
+++ b/tests/test_generics.rs
@@ -39,15 +39,14 @@ fn test_split_for_impl() {
                     attrs: [
                         Attribute {
                             style: Outer,
-                            path: Path {
+                            meta: Path(Path {
                                 segments: [
                                     PathSegment {
                                         ident: "may_dangle",
                                         arguments: None,
                                     },
                                 ],
-                            },
-                            tokens: TokenStream(``),
+                            }),
                         },
                     ],
                     ident: "T",
diff --git a/tests/test_item.rs b/tests/test_item.rs
index 96df4b1a..91c9f70e 100644
--- a/tests/test_item.rs
+++ b/tests/test_item.rs
@@ -22,15 +22,14 @@ fn test_macro_variable_attr() {
         attrs: [
             Attribute {
                 style: Outer,
-                path: Path {
+                meta: Path(Path {
                     segments: [
                         PathSegment {
                             ident: "test",
                             arguments: None,
                         },
                     ],
-                },
-                tokens: TokenStream(``),
+                }),
             },
         ],
         vis: Inherited,
diff --git a/tests/test_meta.rs b/tests/test_meta.rs
index 9b3f30de..97a70db6 100644
--- a/tests/test_meta.rs
+++ b/tests/test_meta.rs
@@ -3,7 +3,7 @@
 #[macro_use]
 mod macros;
 
-use syn::{Meta, MetaList, MetaNameValue, NestedMeta};
+use syn::{Meta, MetaList, MetaNameValue};
 
 #[test]
 fn test_parse_meta_item_word() {
@@ -36,7 +36,9 @@ fn test_parse_meta_name_value() {
                 },
             ],
         },
-        lit: 5,
+        value: Expr::Lit {
+            lit: 5,
+        },
     }
     "###);
 
@@ -50,79 +52,9 @@ fn test_parse_meta_name_value() {
                 },
             ],
         },
-        lit: 5,
-    }
-    "###);
-
-    assert_eq!(meta, inner.into());
-}
-
-#[test]
-fn test_parse_meta_name_value_with_keyword() {
-    let input = "static = 5";
-    let (inner, meta) = (input, input);
-
-    snapshot!(inner as MetaNameValue, @r###"
-    MetaNameValue {
-        path: Path {
-            segments: [
-                PathSegment {
-                    ident: "static",
-                    arguments: None,
-                },
-            ],
+        value: Expr::Lit {
+            lit: 5,
         },
-        lit: 5,
-    }
-    "###);
-
-    snapshot!(meta as Meta, @r###"
-    Meta::NameValue {
-        path: Path {
-            segments: [
-                PathSegment {
-                    ident: "static",
-                    arguments: None,
-                },
-            ],
-        },
-        lit: 5,
-    }
-    "###);
-
-    assert_eq!(meta, inner.into());
-}
-
-#[test]
-fn test_parse_meta_name_value_with_bool() {
-    let input = "true = 5";
-    let (inner, meta) = (input, input);
-
-    snapshot!(inner as MetaNameValue, @r###"
-    MetaNameValue {
-        path: Path {
-            segments: [
-                PathSegment {
-                    ident: "true",
-                    arguments: None,
-                },
-            ],
-        },
-        lit: 5,
-    }
-    "###);
-
-    snapshot!(meta as Meta, @r###"
-    Meta::NameValue {
-        path: Path {
-            segments: [
-                PathSegment {
-                    ident: "true",
-                    arguments: None,
-                },
-            ],
-        },
-        lit: 5,
     }
     "###);
 
@@ -144,9 +76,8 @@ fn test_parse_meta_item_list_lit() {
                 },
             ],
         },
-        nested: [
-            Lit(5),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`5`),
     }
     "###);
 
@@ -160,9 +91,8 @@ fn test_parse_meta_item_list_lit() {
                 },
             ],
         },
-        nested: [
-            Lit(5),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`5`),
     }
     "###);
 
@@ -184,58 +114,8 @@ fn test_parse_meta_item_multiple() {
                 },
             ],
         },
-        nested: [
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "word",
-                        arguments: None,
-                    },
-                ],
-            })),
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "name",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: 5,
-            }),
-            Meta(Meta::List {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "list",
-                            arguments: None,
-                        },
-                    ],
-                },
-                nested: [
-                    Meta(Meta::NameValue {
-                        path: Path {
-                            segments: [
-                                PathSegment {
-                                    ident: "name2",
-                                    arguments: None,
-                                },
-                            ],
-                        },
-                        lit: 6,
-                    }),
-                ],
-            }),
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "word2",
-                        arguments: None,
-                    },
-                ],
-            })),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`word , name = 5 , list (name2 = 6) , word2`),
     }
     "###);
 
@@ -249,97 +129,14 @@ fn test_parse_meta_item_multiple() {
                 },
             ],
         },
-        nested: [
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "word",
-                        arguments: None,
-                    },
-                ],
-            })),
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "name",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: 5,
-            }),
-            Meta(Meta::List {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "list",
-                            arguments: None,
-                        },
-                    ],
-                },
-                nested: [
-                    Meta(Meta::NameValue {
-                        path: Path {
-                            segments: [
-                                PathSegment {
-                                    ident: "name2",
-                                    arguments: None,
-                                },
-                            ],
-                        },
-                        lit: 6,
-                    }),
-                ],
-            }),
-            Meta(Path(Path {
-                segments: [
-                    PathSegment {
-                        ident: "word2",
-                        arguments: None,
-                    },
-                ],
-            })),
-        ],
+        delimiter: Paren,
+        tokens: TokenStream(`word , name = 5 , list (name2 = 6) , word2`),
     }
     "###);
 
     assert_eq!(meta, inner.into());
 }
 
-#[test]
-fn test_parse_nested_meta() {
-    let input = "5";
-    snapshot!(input as NestedMeta, @"Lit(5)");
-
-    let input = "list(name2 = 6)";
-    snapshot!(input as NestedMeta, @r###"
-    Meta(Meta::List {
-        path: Path {
-            segments: [
-                PathSegment {
-                    ident: "list",
-                    arguments: None,
-                },
-            ],
-        },
-        nested: [
-            Meta(Meta::NameValue {
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "name2",
-                            arguments: None,
-                        },
-                    ],
-                },
-                lit: 6,
-            }),
-        ],
-    })
-    "###);
-}
-
 #[test]
 fn test_parse_path() {
     let input = "::serde::Serialize";
@@ -358,21 +155,4 @@ fn test_parse_path() {
         ],
     })
     "###);
-
-    let input = "::serde::Serialize";
-    snapshot!(input as NestedMeta, @r###"
-    Meta(Path(Path {
-        leading_colon: Some,
-        segments: [
-            PathSegment {
-                ident: "serde",
-                arguments: None,
-            },
-            PathSegment {
-                ident: "Serialize",
-                arguments: None,
-            },
-        ],
-    }))
-    "###);
 }
diff --git a/tests/test_precedence.rs b/tests/test_precedence.rs
index 96441186..745197dd 100644
--- a/tests/test_precedence.rs
+++ b/tests/test_precedence.rs
@@ -349,7 +349,10 @@ fn librustc_brackets(mut librustc_expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
 /// form of the resulting expression.
 fn syn_brackets(syn_expr: syn::Expr) -> syn::Expr {
     use syn::fold::{fold_expr, fold_generic_argument, fold_generic_method_argument, Fold};
-    use syn::{token, Expr, ExprParen, GenericArgument, GenericMethodArgument, Pat, Stmt, Type};
+    use syn::{
+        token, Expr, ExprParen, GenericArgument, GenericMethodArgument, MetaNameValue, Pat, Stmt,
+        Type,
+    };
 
     struct ParenthesizeEveryExpr;
     impl Fold for ParenthesizeEveryExpr {
@@ -401,6 +404,11 @@ fn syn_brackets(syn_expr: syn::Expr) -> syn::Expr {
             }
         }
 
+        fn fold_meta_name_value(&mut self, meta: MetaNameValue) -> MetaNameValue {
+            // Don't turn #[p = "..."] into #[p = ("...")].
+            meta
+        }
+
         // We don't want to look at expressions that might appear in patterns or
         // types yet. We'll look into comparing those in the future. For now
         // focus on expressions appearing in other places.
diff --git a/tests/test_shebang.rs b/tests/test_shebang.rs
index dc26b9aa..2f56a8de 100644
--- a/tests/test_shebang.rs
+++ b/tests/test_shebang.rs
@@ -32,15 +32,18 @@ fn test_comment() {
         attrs: [
             Attribute {
                 style: Inner,
-                path: Path {
-                    segments: [
-                        PathSegment {
-                            ident: "allow",
-                            arguments: None,
-                        },
-                    ],
+                meta: Meta::List {
+                    path: Path {
+                        segments: [
+                            PathSegment {
+                                ident: "allow",
+                                arguments: None,
+                            },
+                        ],
+                    },
+                    delimiter: Paren,
+                    tokens: TokenStream(`dead_code`),
                 },
-                tokens: TokenStream(`(dead_code)`),
             },
         ],
         items: [