about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAaron Hill <aa1ronham@gmail.com>2020-11-28 18:33:17 -0500
committerAaron Hill <aa1ronham@gmail.com>2021-04-11 01:31:36 -0400
commita93c4f05de5c5cdd8158e23de7c3cf86a548447f (patch)
tree4e7229d20b567a57d2144b22c394efbaab7a811d
parent25ea6be13e308e72687916cb7af785cd1c09801c (diff)
downloadrust-a93c4f05de5c5cdd8158e23de7c3cf86a548447f.tar.gz
rust-a93c4f05de5c5cdd8158e23de7c3cf86a548447f.zip
Implement token-based handling of attributes during expansion
This PR modifies the macro expansion infrastructure to handle attributes
in a fully token-based manner. As a result:

* Derives macros no longer lose spans when their input is modified
  by eager cfg-expansion. This is accomplished by performing eager
  cfg-expansion on the token stream that we pass to the derive
  proc-macro
* Inner attributes now preserve spans in all cases, including when we
  have multiple inner attributes in a row.

This is accomplished through the following changes:

* New structs `AttrAnnotatedTokenStream` and `AttrAnnotatedTokenTree` are introduced.
  These are very similar to a normal `TokenTree`, but they also track
  the position of attributes and attribute targets within the stream.
  They are built when we collect tokens during parsing.
  An `AttrAnnotatedTokenStream` is converted to a regular `TokenStream` when
  we invoke a macro.
* Token capturing and `LazyTokenStream` are modified to work with
  `AttrAnnotatedTokenStream`. A new `ReplaceRange` type is introduced, which
  is created during the parsing of a nested AST node to make the 'outer'
  AST node aware of the attributes and attribute target stored deeper in the token stream.
* When we need to perform eager cfg-expansion (either due to `#[derive]` or `#[cfg_eval]`),
we tokenize and reparse our target, capturing additional information about the locations of
`#[cfg]` and `#[cfg_attr]` attributes at any depth within the target.
This is a performance optimization, allowing us to perform less work
in the typical case where captured tokens never have eager cfg-expansion run.
-rw-r--r--compiler/rustc_ast/src/ast_like.rs102
-rw-r--r--compiler/rustc_ast/src/attr/mod.rs14
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs49
-rw-r--r--compiler/rustc_ast/src/tokenstream.rs160
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs52
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_eval.rs222
-rw-r--r--compiler/rustc_expand/src/base.rs79
-rw-r--r--compiler/rustc_expand/src/config.rs121
-rw-r--r--compiler/rustc_expand/src/expand.rs38
-rw-r--r--compiler/rustc_expand/src/proc_macro.rs2
-rw-r--r--compiler/rustc_parse/src/lib.rs75
-rw-r--r--compiler/rustc_parse/src/parser/attr.rs22
-rw-r--r--compiler/rustc_parse/src/parser/attr_wrapper.rs517
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs12
-rw-r--r--compiler/rustc_parse/src/parser/item.rs54
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs147
-rw-r--r--compiler/rustc_parse/src/parser/nonterminal.rs4
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs65
-rw-r--r--compiler/rustc_session/src/utils.rs55
-rw-r--r--src/test/ui/proc-macro/attr-complex-fn.stdout38
-rw-r--r--src/test/ui/proc-macro/attribute-after-derive.stdout24
-rw-r--r--src/test/ui/proc-macro/cfg-eval-inner.stdout109
-rw-r--r--src/test/ui/proc-macro/cfg-eval.stdout56
-rw-r--r--src/test/ui/proc-macro/expand-to-derive.stdout47
-rw-r--r--src/test/ui/proc-macro/inner-attrs.rs2
-rw-r--r--src/test/ui/proc-macro/inner-attrs.stdout453
-rw-r--r--src/test/ui/proc-macro/issue-75930-derive-cfg.stdout275
-rw-r--r--src/test/ui/proc-macro/issue-78675-captured-inner-attrs.stdout20
-rw-r--r--src/test/ui/proc-macro/macro-rules-derive-cfg.stdout73
-rw-r--r--src/test/ui/proc-macro/nested-derive-cfg.stdout47
-rw-r--r--src/test/ui/proc-macro/simple-tuple.rs19
-rw-r--r--src/test/ui/proc-macro/simple-tuple.stdout79
-rw-r--r--src/test/ui/proc-macro/weird-braces.stdout196
33 files changed, 2041 insertions, 1187 deletions
diff --git a/compiler/rustc_ast/src/ast_like.rs b/compiler/rustc_ast/src/ast_like.rs
index 63bc7c49a99..945a44ab663 100644
--- a/compiler/rustc_ast/src/ast_like.rs
+++ b/compiler/rustc_ast/src/ast_like.rs
@@ -1,20 +1,32 @@
 use super::ptr::P;
+use super::token::Nonterminal;
 use super::tokenstream::LazyTokenStream;
 use super::{Arm, ExprField, FieldDef, GenericParam, Param, PatField, Variant};
-use super::{AssocItem, Expr, ForeignItem, Item, Local};
+use super::{AssocItem, Expr, ForeignItem, Item, Local, MacCallStmt};
 use super::{AttrItem, AttrKind, Block, Pat, Path, Ty, Visibility};
 use super::{AttrVec, Attribute, Stmt, StmtKind};
 
+use std::fmt::Debug;
+
 /// An `AstLike` represents an AST node (or some wrapper around
 /// and AST node) which stores some combination of attributes
 /// and tokens.
-pub trait AstLike: Sized {
+pub trait AstLike: Sized + Debug {
+    /// This is `true` if this `AstLike` might support 'custom' (proc-macro) inner
+    /// attributes. Attributes like `#![cfg]` and `#![cfg_attr]` are not
+    /// considered 'custom' attributes
+    ///
+    /// If this is `false`, then this `AstLike` definitely does
+    /// not support 'custom' inner attributes, which enables some optimizations
+    /// during token collection.
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool;
     fn attrs(&self) -> &[Attribute];
     fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<Attribute>));
     fn tokens_mut(&mut self) -> Option<&mut Option<LazyTokenStream>>;
 }
 
 impl<T: AstLike + 'static> AstLike for P<T> {
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = T::SUPPORTS_CUSTOM_INNER_ATTRS;
     fn attrs(&self) -> &[Attribute] {
         (**self).attrs()
     }
@@ -26,6 +38,55 @@ impl<T: AstLike + 'static> AstLike for P<T> {
     }
 }
 
+impl AstLike for crate::token::Nonterminal {
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = true;
+    fn attrs(&self) -> &[Attribute] {
+        match self {
+            Nonterminal::NtItem(item) => item.attrs(),
+            Nonterminal::NtStmt(stmt) => stmt.attrs(),
+            Nonterminal::NtExpr(expr) | Nonterminal::NtLiteral(expr) => expr.attrs(),
+            Nonterminal::NtPat(_)
+            | Nonterminal::NtTy(_)
+            | Nonterminal::NtMeta(_)
+            | Nonterminal::NtPath(_)
+            | Nonterminal::NtVis(_)
+            | Nonterminal::NtTT(_)
+            | Nonterminal::NtBlock(_)
+            | Nonterminal::NtIdent(..)
+            | Nonterminal::NtLifetime(_) => &[],
+        }
+    }
+    fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<Attribute>)) {
+        match self {
+            Nonterminal::NtItem(item) => item.visit_attrs(f),
+            Nonterminal::NtStmt(stmt) => stmt.visit_attrs(f),
+            Nonterminal::NtExpr(expr) | Nonterminal::NtLiteral(expr) => expr.visit_attrs(f),
+            Nonterminal::NtPat(_)
+            | Nonterminal::NtTy(_)
+            | Nonterminal::NtMeta(_)
+            | Nonterminal::NtPath(_)
+            | Nonterminal::NtVis(_)
+            | Nonterminal::NtTT(_)
+            | Nonterminal::NtBlock(_)
+            | Nonterminal::NtIdent(..)
+            | Nonterminal::NtLifetime(_) => {}
+        }
+    }
+    fn tokens_mut(&mut self) -> Option<&mut Option<LazyTokenStream>> {
+        match self {
+            Nonterminal::NtItem(item) => item.tokens_mut(),
+            Nonterminal::NtStmt(stmt) => stmt.tokens_mut(),
+            Nonterminal::NtExpr(expr) | Nonterminal::NtLiteral(expr) => expr.tokens_mut(),
+            Nonterminal::NtPat(pat) => pat.tokens_mut(),
+            Nonterminal::NtTy(ty) => ty.tokens_mut(),
+            Nonterminal::NtMeta(attr_item) => attr_item.tokens_mut(),
+            Nonterminal::NtPath(path) => path.tokens_mut(),
+            Nonterminal::NtVis(vis) => vis.tokens_mut(),
+            _ => panic!("Called tokens_mut on {:?}", self),
+        }
+    }
+}
+
 fn visit_attrvec(attrs: &mut AttrVec, f: impl FnOnce(&mut Vec<Attribute>)) {
     crate::mut_visit::visit_clobber(attrs, |attrs| {
         let mut vec = attrs.into();
@@ -35,6 +96,10 @@ fn visit_attrvec(attrs: &mut AttrVec, f: impl FnOnce(&mut Vec<Attribute>)) {
 }
 
 impl AstLike for StmtKind {
+    // This might be an `StmtKind::Item`, which contains
+    // an item that supports inner attrs
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = true;
+
     fn attrs(&self) -> &[Attribute] {
         match self {
             StmtKind::Local(local) => local.attrs(),
@@ -66,6 +131,8 @@ impl AstLike for StmtKind {
 }
 
 impl AstLike for Stmt {
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = StmtKind::SUPPORTS_CUSTOM_INNER_ATTRS;
+
     fn attrs(&self) -> &[Attribute] {
         self.kind.attrs()
     }
@@ -79,6 +146,8 @@ impl AstLike for Stmt {
 }
 
 impl AstLike for Attribute {
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
+
     fn attrs(&self) -> &[Attribute] {
         &[]
     }
@@ -94,6 +163,8 @@ impl AstLike for Attribute {
 }
 
 impl<T: AstLike> AstLike for Option<T> {
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = T::SUPPORTS_CUSTOM_INNER_ATTRS;
+
     fn attrs(&self) -> &[Attribute] {
         self.as_ref().map(|inner| inner.attrs()).unwrap_or(&[])
     }
@@ -127,8 +198,13 @@ impl VecOrAttrVec for AttrVec {
 }
 
 macro_rules! derive_has_tokens_and_attrs {
-    ($($ty:path),*) => { $(
+    (
+        const SUPPORTS_CUSTOM_INNER_ATTRS: bool = $inner_attrs:literal;
+        $($ty:path),*
+    ) => { $(
         impl AstLike for $ty {
+            const SUPPORTS_CUSTOM_INNER_ATTRS: bool = $inner_attrs;
+
             fn attrs(&self) -> &[Attribute] {
                 &self.attrs
             }
@@ -140,6 +216,7 @@ macro_rules! derive_has_tokens_and_attrs {
             fn tokens_mut(&mut self) -> Option<&mut Option<LazyTokenStream>> {
                 Some(&mut self.tokens)
             }
+
         }
     )* }
 }
@@ -147,6 +224,8 @@ macro_rules! derive_has_tokens_and_attrs {
 macro_rules! derive_has_attrs_no_tokens {
     ($($ty:path),*) => { $(
         impl AstLike for $ty {
+            const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
+
             fn attrs(&self) -> &[Attribute] {
                 &self.attrs
             }
@@ -165,12 +244,13 @@ macro_rules! derive_has_attrs_no_tokens {
 macro_rules! derive_has_tokens_no_attrs {
     ($($ty:path),*) => { $(
         impl AstLike for $ty {
+            const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
+
             fn attrs(&self) -> &[Attribute] {
                 &[]
             }
 
             fn visit_attrs(&mut self, _f: impl FnOnce(&mut Vec<Attribute>)) {}
-
             fn tokens_mut(&mut self) -> Option<&mut Option<LazyTokenStream>> {
                 Some(&mut self.tokens)
             }
@@ -178,10 +258,18 @@ macro_rules! derive_has_tokens_no_attrs {
     )* }
 }
 
-// These AST nodes support both inert and active
-// attributes, so they also have tokens.
+// These ast nodes support both active and inert attributes,
+// so they have tokens collected to pass to proc macros
+derive_has_tokens_and_attrs! {
+    // Both `Item` and `AssocItem` can have bodies, which
+    // can contain inner attributes
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = true;
+    Item, AssocItem, ForeignItem
+}
+
 derive_has_tokens_and_attrs! {
-    Item, Expr, Local, AssocItem, ForeignItem
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
+    Local, MacCallStmt, Expr
 }
 
 // These ast nodes only support inert attributes, so they don't
diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs
index 0fbe4d0120c..41121d095f3 100644
--- a/compiler/rustc_ast/src/attr/mod.rs
+++ b/compiler/rustc_ast/src/attr/mod.rs
@@ -6,7 +6,9 @@ use crate::ast::{Lit, LitKind};
 use crate::ast::{MacArgs, MacDelimiter, MetaItem, MetaItemKind, NestedMetaItem};
 use crate::ast::{Path, PathSegment};
 use crate::token::{self, CommentKind, Token};
-use crate::tokenstream::{DelimSpan, LazyTokenStream, TokenStream, TokenTree, TreeAndSpacing};
+use crate::tokenstream::{AttrAnnotatedTokenStream, AttrAnnotatedTokenTree};
+use crate::tokenstream::{DelimSpan, Spacing, TokenTree, TreeAndSpacing};
+use crate::tokenstream::{LazyTokenStream, TokenStream};
 
 use rustc_index::bit_set::GrowableBitSet;
 use rustc_span::source_map::BytePos;
@@ -268,14 +270,18 @@ impl Attribute {
         }
     }
 
-    pub fn tokens(&self) -> TokenStream {
+    pub fn tokens(&self) -> AttrAnnotatedTokenStream {
         match self.kind {
             AttrKind::Normal(_, ref tokens) => tokens
                 .as_ref()
                 .unwrap_or_else(|| panic!("attribute is missing tokens: {:?}", self))
                 .create_token_stream(),
-            AttrKind::DocComment(comment_kind, data) => TokenStream::from(TokenTree::Token(
-                Token::new(token::DocComment(comment_kind, self.style, data), self.span),
+            AttrKind::DocComment(comment_kind, data) => AttrAnnotatedTokenStream::from((
+                AttrAnnotatedTokenTree::Token(Token::new(
+                    token::DocComment(comment_kind, self.style, data),
+                    self.span,
+                )),
+                Spacing::Alone,
             )),
         }
     }
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index b1840f475aa..05f57f978c7 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -631,6 +631,33 @@ pub fn noop_flat_map_param<T: MutVisitor>(mut param: Param, vis: &mut T) -> Smal
 }
 
 // No `noop_` prefix because there isn't a corresponding method in `MutVisitor`.
+pub fn visit_attr_annotated_tt<T: MutVisitor>(tt: &mut AttrAnnotatedTokenTree, vis: &mut T) {
+    match tt {
+        AttrAnnotatedTokenTree::Token(token) => {
+            visit_token(token, vis);
+        }
+        AttrAnnotatedTokenTree::Delimited(DelimSpan { open, close }, _delim, tts) => {
+            vis.visit_span(open);
+            vis.visit_span(close);
+            visit_attr_annotated_tts(tts, vis);
+        }
+        AttrAnnotatedTokenTree::Attributes(data) => {
+            for attr in &mut *data.attrs {
+                match &mut attr.kind {
+                    AttrKind::Normal(_, attr_tokens) => {
+                        visit_lazy_tts(attr_tokens, vis);
+                    }
+                    AttrKind::DocComment(..) => {
+                        vis.visit_span(&mut attr.span);
+                    }
+                }
+            }
+            visit_lazy_tts_opt_mut(Some(&mut data.tokens), vis);
+        }
+    }
+}
+
+// No `noop_` prefix because there isn't a corresponding method in `MutVisitor`.
 pub fn visit_tt<T: MutVisitor>(tt: &mut TokenTree, vis: &mut T) {
     match tt {
         TokenTree::Token(token) => {
@@ -652,16 +679,30 @@ pub fn visit_tts<T: MutVisitor>(TokenStream(tts): &mut TokenStream, vis: &mut T)
     }
 }
 
-pub fn visit_lazy_tts<T: MutVisitor>(lazy_tts: &mut Option<LazyTokenStream>, vis: &mut T) {
+pub fn visit_attr_annotated_tts<T: MutVisitor>(
+    AttrAnnotatedTokenStream(tts): &mut AttrAnnotatedTokenStream,
+    vis: &mut T,
+) {
+    if vis.token_visiting_enabled() && !tts.is_empty() {
+        let tts = Lrc::make_mut(tts);
+        visit_vec(tts, |(tree, _is_joint)| visit_attr_annotated_tt(tree, vis));
+    }
+}
+
+pub fn visit_lazy_tts_opt_mut<T: MutVisitor>(lazy_tts: Option<&mut LazyTokenStream>, vis: &mut T) {
     if vis.token_visiting_enabled() {
-        visit_opt(lazy_tts, |lazy_tts| {
+        if let Some(lazy_tts) = lazy_tts {
             let mut tts = lazy_tts.create_token_stream();
-            visit_tts(&mut tts, vis);
+            visit_attr_annotated_tts(&mut tts, vis);
             *lazy_tts = LazyTokenStream::new(tts);
-        })
+        }
     }
 }
 
+pub fn visit_lazy_tts<T: MutVisitor>(lazy_tts: &mut Option<LazyTokenStream>, vis: &mut T) {
+    visit_lazy_tts_opt_mut(lazy_tts.as_mut(), vis);
+}
+
 // No `noop_` prefix because there isn't a corresponding method in `MutVisitor`.
 // Applies ident visitor if it's an ident; applies other visits to interpolated nodes.
 // In practice the ident part is not actually used by specific visitors right now,
diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs
index 1c26668779f..8318b242cae 100644
--- a/compiler/rustc_ast/src/tokenstream.rs
+++ b/compiler/rustc_ast/src/tokenstream.rs
@@ -14,6 +14,7 @@
 //! ownership of the original.
 
 use crate::token::{self, DelimToken, Token, TokenKind};
+use crate::AttrVec;
 
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_data_structures::sync::{self, Lrc};
@@ -123,11 +124,11 @@ where
 }
 
 pub trait CreateTokenStream: sync::Send + sync::Sync {
-    fn create_token_stream(&self) -> TokenStream;
+    fn create_token_stream(&self) -> AttrAnnotatedTokenStream;
 }
 
-impl CreateTokenStream for TokenStream {
-    fn create_token_stream(&self) -> TokenStream {
+impl CreateTokenStream for AttrAnnotatedTokenStream {
+    fn create_token_stream(&self) -> AttrAnnotatedTokenStream {
         self.clone()
     }
 }
@@ -143,14 +144,14 @@ impl LazyTokenStream {
         LazyTokenStream(Lrc::new(Box::new(inner)))
     }
 
-    pub fn create_token_stream(&self) -> TokenStream {
+    pub fn create_token_stream(&self) -> AttrAnnotatedTokenStream {
         self.0.create_token_stream()
     }
 }
 
 impl fmt::Debug for LazyTokenStream {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        fmt::Debug::fmt("LazyTokenStream", f)
+        write!(f, "LazyTokenStream({:?})", self.create_token_stream())
     }
 }
 
@@ -173,6 +174,145 @@ impl<CTX> HashStable<CTX> for LazyTokenStream {
     }
 }
 
+/// A `AttrAnnotatedTokenStream` is similar to a `TokenStream`, but with extra
+/// information about the tokens for attribute targets. This is used
+/// during expansion to perform early cfg-expansion, and to process attributes
+/// during proc-macro invocations.
+#[derive(Clone, Debug, Default, Encodable, Decodable)]
+pub struct AttrAnnotatedTokenStream(pub Lrc<Vec<(AttrAnnotatedTokenTree, Spacing)>>);
+
+/// Like `TokenTree`, but for `AttrAnnotatedTokenStream`
+#[derive(Clone, Debug, Encodable, Decodable)]
+pub enum AttrAnnotatedTokenTree {
+    Token(Token),
+    Delimited(DelimSpan, DelimToken, AttrAnnotatedTokenStream),
+    /// Stores the attributes for an attribute target,
+    /// along with the tokens for that attribute target.
+    /// See `AttributesData` for more information
+    Attributes(AttributesData),
+}
+
+impl AttrAnnotatedTokenStream {
+    pub fn new(tokens: Vec<(AttrAnnotatedTokenTree, Spacing)>) -> AttrAnnotatedTokenStream {
+        AttrAnnotatedTokenStream(Lrc::new(tokens))
+    }
+
+    /// Converts this `AttrAnnotatedTokenStream` to a plain `TokenStream
+    /// During conversion, `AttrAnnotatedTokenTree::Attributes` get 'flattened'
+    /// back to a `TokenStream` of the form `outer_attr attr_target`.
+    /// If there are inner attributes, they are inserted into the proper
+    /// place in the attribute target tokens.
+    pub fn to_tokenstream(&self) -> TokenStream {
+        let trees: Vec<_> = self
+            .0
+            .iter()
+            .flat_map(|tree| match &tree.0 {
+                AttrAnnotatedTokenTree::Token(inner) => {
+                    smallvec![(TokenTree::Token(inner.clone()), tree.1)].into_iter()
+                }
+                AttrAnnotatedTokenTree::Delimited(span, delim, stream) => smallvec![(
+                    TokenTree::Delimited(*span, *delim, stream.to_tokenstream()),
+                    tree.1,
+                )]
+                .into_iter(),
+                AttrAnnotatedTokenTree::Attributes(data) => {
+                    let mut outer_attrs = Vec::new();
+                    let mut inner_attrs = Vec::new();
+                    let attrs: Vec<_> = data.attrs.clone().into();
+                    for attr in attrs {
+                        match attr.style {
+                            crate::AttrStyle::Outer => {
+                                assert!(
+                                    inner_attrs.len() == 0,
+                                    "Found outer attribute {:?} after inner attrs {:?}",
+                                    attr,
+                                    inner_attrs
+                                );
+                                outer_attrs.push(attr);
+                            }
+                            crate::AttrStyle::Inner => {
+                                inner_attrs.push(attr);
+                            }
+                        }
+                    }
+
+                    let mut target_tokens: Vec<_> = data
+                        .tokens
+                        .create_token_stream()
+                        .to_tokenstream()
+                        .0
+                        .iter()
+                        .cloned()
+                        .collect();
+                    if !inner_attrs.is_empty() {
+                        let mut found = false;
+                        // Check the last two trees (to account for a trailing semi)
+                        for (tree, _) in target_tokens.iter_mut().rev().take(2) {
+                            if let TokenTree::Delimited(span, delim, delim_tokens) = tree {
+                                // Inner attributes are only supported on extern blocks, functions, impls,
+                                // and modules. All of these have their inner attributes placed at
+                                // the beginning of the rightmost outermost braced group:
+                                // e.g. fn foo() { #![my_attr} }
+                                //
+                                // Therefore, we can insert them back into the right location
+                                // without needing to do any extra position tracking.
+                                //
+                                // Note: Outline modules are an exception - they can
+                                // have attributes like `#![my_attr]` at the start of a file.
+                                // Support for custom attributes in this position is not
+                                // properly implemented - we always synthesize fake tokens,
+                                // so we never reach this code.
+
+                                let mut builder = TokenStreamBuilder::new();
+                                for inner_attr in &inner_attrs {
+                                    builder.push(inner_attr.tokens().to_tokenstream());
+                                }
+                                builder.push(delim_tokens.clone());
+                                *tree = TokenTree::Delimited(*span, *delim, builder.build());
+                                found = true;
+                                break;
+                            }
+                        }
+
+                        assert!(
+                            found,
+                            "Failed to find trailing delimited group in: {:?}",
+                            target_tokens
+                        );
+                    }
+                    let mut flat: SmallVec<[_; 1]> = SmallVec::new();
+                    for attr in outer_attrs {
+                        // FIXME: Make this more efficient
+                        flat.extend(attr.tokens().to_tokenstream().0.clone().iter().cloned());
+                    }
+                    flat.extend(target_tokens);
+                    flat.into_iter()
+                }
+            })
+            .collect();
+        TokenStream::new(trees)
+    }
+}
+
+/// Stores the tokens for an attribute target, along
+/// with its attributes.
+///
+/// This is constructed during parsing when we need to capture
+/// tokens.
+///
+/// For example, `#[cfg(FALSE)] struct Foo {}` would
+/// have an `attrs` field contaiing the `#[cfg(FALSE)]` attr,
+/// and a `tokens` field storing the (unparesd) tokens `struct Foo {}`
+#[derive(Clone, Debug, Encodable, Decodable)]
+pub struct AttributesData {
+    /// Attributes, both outer and inner.
+    /// These are stored in the original order that they were parsed in.
+    pub attrs: AttrVec,
+    /// The underlying tokens for the attribute target that `attrs`
+    /// are applied to
+    pub tokens: LazyTokenStream,
+}
+
 /// A `TokenStream` is an abstract sequence of tokens, organized into [`TokenTree`]s.
 ///
 /// The goal is for procedural macros to work with `TokenStream`s and `TokenTree`s
@@ -235,6 +375,12 @@ impl TokenStream {
     }
 }
 
+impl From<(AttrAnnotatedTokenTree, Spacing)> for AttrAnnotatedTokenStream {
+    fn from((tree, spacing): (AttrAnnotatedTokenTree, Spacing)) -> AttrAnnotatedTokenStream {
+        AttrAnnotatedTokenStream::new(vec![(tree, spacing)])
+    }
+}
+
 impl From<TokenTree> for TokenStream {
     fn from(tree: TokenTree) -> TokenStream {
         TokenStream::new(vec![(tree, Spacing::Alone)])
@@ -457,6 +603,10 @@ impl Cursor {
         }
     }
 
+    pub fn index(&self) -> usize {
+        self.index
+    }
+
     pub fn append(&mut self, new_stream: TokenStream) {
         if new_stream.is_empty() {
             return;
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index cd5d116964f..44056df4ab9 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -37,8 +37,8 @@
 #![recursion_limit = "256"]
 
 use rustc_ast::node_id::NodeMap;
-use rustc_ast::token::{self, DelimToken, Nonterminal, Token};
-use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, DelimSpan, TokenStream, TokenTree};
+use rustc_ast::token::{self, Token};
+use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, TokenStream, TokenTree};
 use rustc_ast::visit::{self, AssocCtxt, Visitor};
 use rustc_ast::walk_list;
 use rustc_ast::{self as ast, *};
@@ -56,7 +56,7 @@ use rustc_hir::{ConstArg, GenericArg, ParamName};
 use rustc_index::vec::{Idx, IndexVec};
 use rustc_session::lint::builtin::{BARE_TRAIT_OBJECTS, MISSING_ABI};
 use rustc_session::lint::{BuiltinLintDiagnostics, LintBuffer};
-use rustc_session::parse::ParseSess;
+use rustc_session::utils::{FlattenNonterminals, NtToTokenstream};
 use rustc_session::Session;
 use rustc_span::hygiene::ExpnId;
 use rustc_span::source_map::{respan, DesugaringKind};
@@ -213,8 +213,6 @@ pub trait ResolverAstLowering {
     ) -> LocalDefId;
 }
 
-type NtToTokenstream = fn(&Nonterminal, &ParseSess, CanSynthesizeMissingTokens) -> TokenStream;
-
 /// Context of `impl Trait` in code, which determines whether it is allowed in an HIR subtree,
 /// and if so, what meaning it has.
 #[derive(Debug)]
@@ -403,42 +401,6 @@ enum AnonymousLifetimeMode {
     PassThrough,
 }
 
-struct TokenStreamLowering<'a> {
-    parse_sess: &'a ParseSess,
-    synthesize_tokens: CanSynthesizeMissingTokens,
-    nt_to_tokenstream: NtToTokenstream,
-}
-
-impl<'a> TokenStreamLowering<'a> {
-    fn lower_token_stream(&mut self, tokens: TokenStream) -> TokenStream {
-        tokens.into_trees().flat_map(|tree| self.lower_token_tree(tree).into_trees()).collect()
-    }
-
-    fn lower_token_tree(&mut self, tree: TokenTree) -> TokenStream {
-        match tree {
-            TokenTree::Token(token) => self.lower_token(token),
-            TokenTree::Delimited(span, delim, tts) => {
-                TokenTree::Delimited(span, delim, self.lower_token_stream(tts)).into()
-            }
-        }
-    }
-
-    fn lower_token(&mut self, token: Token) -> TokenStream {
-        match token.kind {
-            token::Interpolated(nt) => {
-                let tts = (self.nt_to_tokenstream)(&nt, self.parse_sess, self.synthesize_tokens);
-                TokenTree::Delimited(
-                    DelimSpan::from_single(token.span),
-                    DelimToken::NoDelim,
-                    self.lower_token_stream(tts),
-                )
-                .into()
-            }
-            _ => TokenTree::Token(token).into(),
-        }
-    }
-}
-
 impl<'a, 'hir> LoweringContext<'a, 'hir> {
     fn lower_crate(mut self, c: &Crate) -> hir::Crate<'hir> {
         /// Full-crate AST visitor that inserts into a fresh
@@ -1037,12 +999,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                     }
                 }
 
-                let tokens = TokenStreamLowering {
+                let tokens = FlattenNonterminals {
                     parse_sess: &self.sess.parse_sess,
                     synthesize_tokens: CanSynthesizeMissingTokens::Yes,
                     nt_to_tokenstream: self.nt_to_tokenstream,
                 }
-                .lower_token(token.clone());
+                .process_token(token.clone());
                 MacArgs::Eq(eq_span, unwrap_single_token(self.sess, tokens, token.span))
             }
         }
@@ -1053,12 +1015,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         tokens: TokenStream,
         synthesize_tokens: CanSynthesizeMissingTokens,
     ) -> TokenStream {
-        TokenStreamLowering {
+        FlattenNonterminals {
             parse_sess: &self.sess.parse_sess,
             synthesize_tokens,
             nt_to_tokenstream: self.nt_to_tokenstream,
         }
-        .lower_token_stream(tokens)
+        .process_token_stream(tokens)
     }
 
     /// Given an associated type constraint like one of these:
diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs
index 025872df017..79dc857074d 100644
--- a/compiler/rustc_builtin_macros/src/cfg_eval.rs
+++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs
@@ -1,11 +1,18 @@
 use crate::util::check_builtin_macro_attribute;
 
-use rustc_ast::mut_visit::{self, MutVisitor};
-use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, AstLike};
+use rustc_ast as ast;
+use rustc_ast::mut_visit::MutVisitor;
+use rustc_ast::tokenstream::CanSynthesizeMissingTokens;
+use rustc_ast::visit::Visitor;
+use rustc_ast::{mut_visit, visit};
+use rustc_ast::{AstLike, Attribute};
 use rustc_expand::base::{Annotatable, ExtCtxt};
 use rustc_expand::config::StripUnconfigured;
 use rustc_expand::configure;
+use rustc_parse::parser::ForceCollect;
+use rustc_session::utils::FlattenNonterminals;
+
+use rustc_ast::ptr::P;
 use rustc_span::symbol::sym;
 use rustc_span::Span;
 use smallvec::SmallVec;
@@ -22,74 +29,179 @@ crate fn expand(
 
 crate fn cfg_eval(ecx: &ExtCtxt<'_>, annotatable: Annotatable) -> Vec<Annotatable> {
     let mut visitor = CfgEval {
-        cfg: StripUnconfigured { sess: ecx.sess, features: ecx.ecfg.features, modified: false },
+        cfg: &mut StripUnconfigured {
+            sess: ecx.sess,
+            features: ecx.ecfg.features,
+            config_tokens: true,
+        },
     };
-    let mut annotatable = visitor.configure_annotatable(annotatable);
-    if visitor.cfg.modified {
-        // Erase the tokens if cfg-stripping modified the item
-        // This will cause us to synthesize fake tokens
-        // when `nt_to_tokenstream` is called on this item.
-        if let Some(tokens) = annotatable.tokens_mut() {
-            *tokens = None;
+    let annotatable = visitor.configure_annotatable(annotatable);
+    vec![annotatable]
+}
+
+struct CfgEval<'a, 'b> {
+    cfg: &'a mut StripUnconfigured<'b>,
+}
+
+fn flat_map_annotatable(vis: &mut impl MutVisitor, annotatable: Annotatable) -> Annotatable {
+    // Since the item itself has already been configured by the InvocationCollector,
+    // we know that fold result vector will contain exactly one element
+    match annotatable {
+        Annotatable::Item(item) => Annotatable::Item(vis.flat_map_item(item).pop().unwrap()),
+        Annotatable::TraitItem(item) => {
+            Annotatable::TraitItem(vis.flat_map_trait_item(item).pop().unwrap())
+        }
+        Annotatable::ImplItem(item) => {
+            Annotatable::ImplItem(vis.flat_map_impl_item(item).pop().unwrap())
+        }
+        Annotatable::ForeignItem(item) => {
+            Annotatable::ForeignItem(vis.flat_map_foreign_item(item).pop().unwrap())
         }
+        Annotatable::Stmt(stmt) => {
+            Annotatable::Stmt(stmt.map(|stmt| vis.flat_map_stmt(stmt).pop().unwrap()))
+        }
+        Annotatable::Expr(mut expr) => Annotatable::Expr({
+            vis.visit_expr(&mut expr);
+            expr
+        }),
+        Annotatable::Arm(arm) => Annotatable::Arm(vis.flat_map_arm(arm).pop().unwrap()),
+        Annotatable::ExprField(field) => {
+            Annotatable::ExprField(vis.flat_map_expr_field(field).pop().unwrap())
+        }
+        Annotatable::PatField(fp) => {
+            Annotatable::PatField(vis.flat_map_pat_field(fp).pop().unwrap())
+        }
+        Annotatable::GenericParam(param) => {
+            Annotatable::GenericParam(vis.flat_map_generic_param(param).pop().unwrap())
+        }
+        Annotatable::Param(param) => Annotatable::Param(vis.flat_map_param(param).pop().unwrap()),
+        Annotatable::FieldDef(sf) => {
+            Annotatable::FieldDef(vis.flat_map_field_def(sf).pop().unwrap())
+        }
+        Annotatable::Variant(v) => Annotatable::Variant(vis.flat_map_variant(v).pop().unwrap()),
+    }
+}
+
+struct CfgFinder {
+    has_cfg_or_cfg_attr: bool,
+}
+
+impl CfgFinder {
+    fn has_cfg_or_cfg_attr(annotatable: &Annotatable) -> bool {
+        let mut finder = CfgFinder { has_cfg_or_cfg_attr: false };
+        match annotatable {
+            Annotatable::Item(item) => finder.visit_item(&item),
+            Annotatable::TraitItem(item) => finder.visit_assoc_item(&item, visit::AssocCtxt::Trait),
+            Annotatable::ImplItem(item) => finder.visit_assoc_item(&item, visit::AssocCtxt::Impl),
+            Annotatable::ForeignItem(item) => finder.visit_foreign_item(&item),
+            Annotatable::Stmt(stmt) => finder.visit_stmt(&stmt),
+            Annotatable::Expr(expr) => finder.visit_expr(&expr),
+            Annotatable::Arm(arm) => finder.visit_arm(&arm),
+            Annotatable::ExprField(field) => finder.visit_expr_field(&field),
+            Annotatable::PatField(field) => finder.visit_pat_field(&field),
+            Annotatable::GenericParam(param) => finder.visit_generic_param(&param),
+            Annotatable::Param(param) => finder.visit_param(&param),
+            Annotatable::FieldDef(field) => finder.visit_field_def(&field),
+            Annotatable::Variant(variant) => finder.visit_variant(&variant),
+        };
+        finder.has_cfg_or_cfg_attr
     }
-    vec![annotatable]
 }
 
-struct CfgEval<'a> {
-    cfg: StripUnconfigured<'a>,
+impl<'ast> visit::Visitor<'ast> for CfgFinder {
+    fn visit_attribute(&mut self, attr: &'ast Attribute) {
+        // We want short-circuiting behavior, so don't use the '|=' operator.
+        self.has_cfg_or_cfg_attr = self.has_cfg_or_cfg_attr
+            || attr
+                .ident()
+                .map_or(false, |ident| ident.name == sym::cfg || ident.name == sym::cfg_attr);
+    }
 }
 
-impl CfgEval<'_> {
+impl CfgEval<'_, '_> {
     fn configure<T: AstLike>(&mut self, node: T) -> Option<T> {
         self.cfg.configure(node)
     }
 
-    fn configure_annotatable(&mut self, annotatable: Annotatable) -> Annotatable {
-        // Since the item itself has already been configured by the InvocationCollector,
-        // we know that fold result vector will contain exactly one element
-        match annotatable {
-            Annotatable::Item(item) => Annotatable::Item(self.flat_map_item(item).pop().unwrap()),
-            Annotatable::TraitItem(item) => {
-                Annotatable::TraitItem(self.flat_map_trait_item(item).pop().unwrap())
-            }
-            Annotatable::ImplItem(item) => {
-                Annotatable::ImplItem(self.flat_map_impl_item(item).pop().unwrap())
-            }
-            Annotatable::ForeignItem(item) => {
-                Annotatable::ForeignItem(self.flat_map_foreign_item(item).pop().unwrap())
-            }
-            Annotatable::Stmt(stmt) => {
-                Annotatable::Stmt(stmt.map(|stmt| self.flat_map_stmt(stmt).pop().unwrap()))
-            }
-            Annotatable::Expr(mut expr) => Annotatable::Expr({
-                self.visit_expr(&mut expr);
-                expr
-            }),
-            Annotatable::Arm(arm) => Annotatable::Arm(self.flat_map_arm(arm).pop().unwrap()),
-            Annotatable::ExprField(field) => {
-                Annotatable::ExprField(self.flat_map_expr_field(field).pop().unwrap())
-            }
-            Annotatable::PatField(fp) => {
-                Annotatable::PatField(self.flat_map_pat_field(fp).pop().unwrap())
-            }
-            Annotatable::GenericParam(param) => {
-                Annotatable::GenericParam(self.flat_map_generic_param(param).pop().unwrap())
-            }
-            Annotatable::Param(param) => {
-                Annotatable::Param(self.flat_map_param(param).pop().unwrap())
-            }
-            Annotatable::FieldDef(sf) => {
-                Annotatable::FieldDef(self.flat_map_field_def(sf).pop().unwrap())
+    pub fn configure_annotatable(&mut self, mut annotatable: Annotatable) -> Annotatable {
+        // Tokenizing and re-parsing the `Annotatable` can have a significant
+        // performance impact, so try to avoid it if possible
+        if !CfgFinder::has_cfg_or_cfg_attr(&annotatable) {
+            return annotatable;
+        }
+
+        // The majority of parsed attribute targets will never need to have early cfg-expansion
+        // run (e.g. they are not part of a `#[derive]` or `#[cfg_eval]` macro inoput).
+        // Therefore, we normally do not capture the necessary information about `#[cfg]`
+        // and `#[cfg_attr]` attributes during parsing.
+        //
+        // Therefore, when we actually *do* run early cfg-expansion, we need to tokenize
+        // and re-parse the attribute target, this time capturing information about
+        // the location of `#[cfg]` and `#[cfg_attr]` in the token stream. The tokenization
+        // process is lossless, so this process is invisible to proc-macros.
+
+        // FIXME - get rid of this clone
+        let nt = annotatable.clone().into_nonterminal();
+
+        let mut orig_tokens = rustc_parse::nt_to_tokenstream(
+            &nt,
+            &self.cfg.sess.parse_sess,
+            CanSynthesizeMissingTokens::No,
+        );
+
+        // 'Flatten' all nonterminals (i.e. `TokenKind::Interpolated`)
+        // to `None`-delimited groups containing the corresponding tokens. This
+        // is normally delayed until the proc-macro server actually needs to
+        // provide a `TokenKind::Interpolated` to a proc-macro. We do this earlier,
+        // so that we can handle cases like:
+        //
+        // ```rust
+        // #[cfg_eval] #[cfg] $item
+        //```
+        //
+        // where `$item` is `#[cfg_attr] struct Foo {}`. We want to make
+        // sure to evaluate *all* `#[cfg]` and `#[cfg_attr]` attributes - the simplest
+        // way to do this is to do a single parse of a stream without any nonterminals.
+        let mut flatten = FlattenNonterminals {
+            nt_to_tokenstream: rustc_parse::nt_to_tokenstream,
+            parse_sess: &self.cfg.sess.parse_sess,
+            synthesize_tokens: CanSynthesizeMissingTokens::No,
+        };
+        orig_tokens = flatten.process_token_stream(orig_tokens);
+
+        // Re-parse the tokens, setting the `capture_cfg` flag to save extra information
+        // to the captured `AttrAnnotatedTokenStream` (specifically, we capture
+        // `AttrAnnotatedTokenTree::AttributesData` for all occurences of `#[cfg]` and `#[cfg_attr]`)
+        let mut parser =
+            rustc_parse::stream_to_parser(&self.cfg.sess.parse_sess, orig_tokens, None);
+        parser.capture_cfg = true;
+        annotatable = match annotatable {
+            Annotatable::Item(_) => {
+                Annotatable::Item(parser.parse_item(ForceCollect::Yes).unwrap().unwrap())
             }
-            Annotatable::Variant(v) => {
-                Annotatable::Variant(self.flat_map_variant(v).pop().unwrap())
+            Annotatable::TraitItem(_) => Annotatable::TraitItem(
+                parser.parse_trait_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
+            ),
+            Annotatable::ImplItem(_) => Annotatable::ImplItem(
+                parser.parse_impl_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
+            ),
+            Annotatable::ForeignItem(_) => Annotatable::ForeignItem(
+                parser.parse_foreign_item(ForceCollect::Yes).unwrap().unwrap().unwrap(),
+            ),
+            Annotatable::Stmt(_) => {
+                Annotatable::Stmt(P(parser.parse_stmt(ForceCollect::Yes).unwrap().unwrap()))
             }
-        }
+            Annotatable::Expr(_) => Annotatable::Expr(parser.parse_expr_force_collect().unwrap()),
+            _ => unreachable!(),
+        };
+
+        // Now that we have our re-parsed `AttrAnnotatedTokenStream`, recursively configuring
+        // our attribute target will correctly the tokens as well.
+        flat_map_annotatable(self, annotatable)
     }
 }
 
-impl MutVisitor for CfgEval<'_> {
+impl MutVisitor for CfgEval<'_, '_> {
     fn visit_expr(&mut self, expr: &mut P<ast::Expr>) {
         self.cfg.configure_expr(expr);
         mut_visit::noop_visit_expr(expr, self);
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index a2035ee3c6e..59505842816 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -3,7 +3,7 @@ use crate::module::DirOwnership;
 
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Nonterminal};
-use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, LazyTokenStream, TokenStream};
+use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, TokenStream};
 use rustc_ast::visit::{AssocCtxt, Visitor};
 use rustc_ast::{self as ast, AstLike, Attribute, Item, NodeId, PatKind};
 use rustc_attr::{self as attr, Deprecation, Stability};
@@ -46,26 +46,26 @@ pub enum Annotatable {
     Variant(ast::Variant),
 }
 
-impl AstLike for Annotatable {
-    fn attrs(&self) -> &[Attribute] {
+impl Annotatable {
+    pub fn span(&self) -> Span {
         match *self {
-            Annotatable::Item(ref item) => &item.attrs,
-            Annotatable::TraitItem(ref trait_item) => &trait_item.attrs,
-            Annotatable::ImplItem(ref impl_item) => &impl_item.attrs,
-            Annotatable::ForeignItem(ref foreign_item) => &foreign_item.attrs,
-            Annotatable::Stmt(ref stmt) => stmt.attrs(),
-            Annotatable::Expr(ref expr) => &expr.attrs,
-            Annotatable::Arm(ref arm) => &arm.attrs,
-            Annotatable::ExprField(ref field) => &field.attrs,
-            Annotatable::PatField(ref fp) => &fp.attrs,
-            Annotatable::GenericParam(ref gp) => &gp.attrs,
-            Annotatable::Param(ref p) => &p.attrs,
-            Annotatable::FieldDef(ref sf) => &sf.attrs,
-            Annotatable::Variant(ref v) => &v.attrs(),
+            Annotatable::Item(ref item) => item.span,
+            Annotatable::TraitItem(ref trait_item) => trait_item.span,
+            Annotatable::ImplItem(ref impl_item) => impl_item.span,
+            Annotatable::ForeignItem(ref foreign_item) => foreign_item.span,
+            Annotatable::Stmt(ref stmt) => stmt.span,
+            Annotatable::Expr(ref expr) => expr.span,
+            Annotatable::Arm(ref arm) => arm.span,
+            Annotatable::ExprField(ref field) => field.span,
+            Annotatable::PatField(ref fp) => fp.pat.span,
+            Annotatable::GenericParam(ref gp) => gp.ident.span,
+            Annotatable::Param(ref p) => p.span,
+            Annotatable::FieldDef(ref sf) => sf.span,
+            Annotatable::Variant(ref v) => v.span,
         }
     }
 
-    fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<Attribute>)) {
+    pub fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<Attribute>)) {
         match self {
             Annotatable::Item(item) => item.visit_attrs(f),
             Annotatable::TraitItem(trait_item) => trait_item.visit_attrs(f),
@@ -83,44 +83,6 @@ impl AstLike for Annotatable {
         }
     }
 
-    fn tokens_mut(&mut self) -> Option<&mut Option<LazyTokenStream>> {
-        match self {
-            Annotatable::Item(item) => item.tokens_mut(),
-            Annotatable::TraitItem(trait_item) => trait_item.tokens_mut(),
-            Annotatable::ImplItem(impl_item) => impl_item.tokens_mut(),
-            Annotatable::ForeignItem(foreign_item) => foreign_item.tokens_mut(),
-            Annotatable::Stmt(stmt) => stmt.tokens_mut(),
-            Annotatable::Expr(expr) => expr.tokens_mut(),
-            Annotatable::Arm(arm) => arm.tokens_mut(),
-            Annotatable::ExprField(field) => field.tokens_mut(),
-            Annotatable::PatField(fp) => fp.tokens_mut(),
-            Annotatable::GenericParam(gp) => gp.tokens_mut(),
-            Annotatable::Param(p) => p.tokens_mut(),
-            Annotatable::FieldDef(sf) => sf.tokens_mut(),
-            Annotatable::Variant(v) => v.tokens_mut(),
-        }
-    }
-}
-
-impl Annotatable {
-    pub fn span(&self) -> Span {
-        match *self {
-            Annotatable::Item(ref item) => item.span,
-            Annotatable::TraitItem(ref trait_item) => trait_item.span,
-            Annotatable::ImplItem(ref impl_item) => impl_item.span,
-            Annotatable::ForeignItem(ref foreign_item) => foreign_item.span,
-            Annotatable::Stmt(ref stmt) => stmt.span,
-            Annotatable::Expr(ref expr) => expr.span,
-            Annotatable::Arm(ref arm) => arm.span,
-            Annotatable::ExprField(ref field) => field.span,
-            Annotatable::PatField(ref fp) => fp.pat.span,
-            Annotatable::GenericParam(ref gp) => gp.ident.span,
-            Annotatable::Param(ref p) => p.span,
-            Annotatable::FieldDef(ref sf) => sf.span,
-            Annotatable::Variant(ref v) => v.span,
-        }
-    }
-
     pub fn visit_with<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) {
         match self {
             Annotatable::Item(item) => visitor.visit_item(item),
@@ -139,7 +101,7 @@ impl Annotatable {
         }
     }
 
-    crate fn into_nonterminal(self) -> Nonterminal {
+    pub fn into_nonterminal(self) -> Nonterminal {
         match self {
             Annotatable::Item(item) => token::NtItem(item),
             Annotatable::TraitItem(item) | Annotatable::ImplItem(item) => {
@@ -161,10 +123,7 @@ impl Annotatable {
     }
 
     crate fn into_tokens(self, sess: &ParseSess) -> TokenStream {
-        // Tokens of an attribute target may be invalidated by some outer `#[derive]` performing
-        // "full configuration" (attributes following derives on the same item should be the most
-        // common case), that's why synthesizing tokens is allowed.
-        nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::Yes)
+        nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::No)
     }
 
     pub fn expect_item(self) -> P<ast::Item> {
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index a23731cf309..03c83f9c07b 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -2,8 +2,10 @@
 
 use rustc_ast::ptr::P;
 use rustc_ast::token::{DelimToken, Token, TokenKind};
-use rustc_ast::tokenstream::{DelimSpan, LazyTokenStream, Spacing, TokenStream, TokenTree};
-use rustc_ast::{self as ast, AstLike, AttrItem, Attribute, MetaItem};
+use rustc_ast::tokenstream::{AttrAnnotatedTokenStream, AttrAnnotatedTokenTree};
+use rustc_ast::tokenstream::{DelimSpan, Spacing};
+use rustc_ast::tokenstream::{LazyTokenStream, TokenTree};
+use rustc_ast::{self as ast, AstLike, AttrItem, AttrStyle, Attribute, MetaItem};
 use rustc_attr as attr;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::map_in_place::MapInPlace;
@@ -23,7 +25,10 @@ use rustc_span::{Span, DUMMY_SP};
 pub struct StripUnconfigured<'a> {
     pub sess: &'a Session,
     pub features: Option<&'a Features>,
-    pub modified: bool,
+    /// If `true`, perform cfg-stripping on attached tokens.
+    /// This is only used for the input to derive macros,
+    /// which needs eager expansion of `cfg` and `cfg_attr`
+    pub config_tokens: bool,
 }
 
 fn get_features(
@@ -194,7 +199,7 @@ fn get_features(
 
 // `cfg_attr`-process the crate's attributes and compute the crate's features.
 pub fn features(sess: &Session, mut krate: ast::Crate) -> (ast::Crate, Features) {
-    let mut strip_unconfigured = StripUnconfigured { sess, features: None, modified: false };
+    let mut strip_unconfigured = StripUnconfigured { sess, features: None, config_tokens: false };
 
     let unconfigured_attrs = krate.attrs.clone();
     let diag = &sess.parse_sess.span_diagnostic;
@@ -241,24 +246,83 @@ impl<'a> StripUnconfigured<'a> {
     pub fn configure<T: AstLike>(&mut self, mut node: T) -> Option<T> {
         self.process_cfg_attrs(&mut node);
         if self.in_cfg(node.attrs()) {
+            self.try_configure_tokens(&mut node);
             Some(node)
         } else {
-            self.modified = true;
             None
         }
     }
 
+    fn try_configure_tokens<T: AstLike>(&mut self, node: &mut T) {
+        if self.config_tokens {
+            if let Some(Some(tokens)) = node.tokens_mut() {
+                let attr_annotated_tokens = tokens.create_token_stream();
+                *tokens = LazyTokenStream::new(self.configure_tokens(&attr_annotated_tokens));
+            }
+        }
+    }
+
     fn configure_krate_attrs(
         &mut self,
         mut attrs: Vec<ast::Attribute>,
     ) -> Option<Vec<ast::Attribute>> {
         attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr));
-        if self.in_cfg(&attrs) {
-            Some(attrs)
-        } else {
-            self.modified = true;
-            None
+        if self.in_cfg(&attrs) { Some(attrs) } else { None }
+    }
+
+    /// Performs cfg-expansion on `stream`, producing a new `AttrAnnotatedTokenStream`.
+    /// This is only used during the invocation of `derive` proc-macros,
+    /// which require that we cfg-expand their entire input.
+    /// Normal cfg-expansion operates on parsed AST nodes via the `configure` method
+    fn configure_tokens(&mut self, stream: &AttrAnnotatedTokenStream) -> AttrAnnotatedTokenStream {
+        fn can_skip(stream: &AttrAnnotatedTokenStream) -> bool {
+            stream.0.iter().all(|(tree, _spacing)| match tree {
+                AttrAnnotatedTokenTree::Attributes(_) => false,
+                AttrAnnotatedTokenTree::Token(_) => true,
+                AttrAnnotatedTokenTree::Delimited(_, _, inner) => can_skip(inner),
+            })
+        }
+
+        if can_skip(stream) {
+            return stream.clone();
         }
+
+        let trees: Vec<_> = stream
+            .0
+            .iter()
+            .flat_map(|(tree, spacing)| match tree.clone() {
+                AttrAnnotatedTokenTree::Attributes(mut data) => {
+                    let mut attrs: Vec<_> = std::mem::take(&mut data.attrs).into();
+                    attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr));
+                    data.attrs = attrs.into();
+
+                    if self.in_cfg(&data.attrs) {
+                        data.tokens = LazyTokenStream::new(
+                            self.configure_tokens(&data.tokens.create_token_stream()),
+                        );
+                        Some((AttrAnnotatedTokenTree::Attributes(data), *spacing)).into_iter()
+                    } else {
+                        None.into_iter()
+                    }
+                }
+                AttrAnnotatedTokenTree::Delimited(sp, delim, mut inner) => {
+                    inner = self.configure_tokens(&inner);
+                    Some((AttrAnnotatedTokenTree::Delimited(sp, delim, inner), *spacing))
+                        .into_iter()
+                }
+                AttrAnnotatedTokenTree::Token(token) => {
+                    if let TokenKind::Interpolated(nt) = token.kind {
+                        panic!(
+                            "Nonterminal should have been flattened at {:?}: {:?}",
+                            token.span, nt
+                        );
+                    } else {
+                        Some((AttrAnnotatedTokenTree::Token(token), *spacing)).into_iter()
+                    }
+                }
+            })
+            .collect();
+        AttrAnnotatedTokenStream::new(trees)
     }
 
     /// Parse and expand all `cfg_attr` attributes into a list of attributes
@@ -285,9 +349,6 @@ impl<'a> StripUnconfigured<'a> {
             return vec![attr];
         }
 
-        // A `#[cfg_attr]` either gets removed, or replaced with a new attribute
-        self.modified = true;
-
         let (cfg_predicate, expanded_attrs) = match self.parse_cfg_attr(&attr) {
             None => return vec![],
             Some(r) => r,
@@ -311,7 +372,7 @@ impl<'a> StripUnconfigured<'a> {
         expanded_attrs
             .into_iter()
             .flat_map(|(item, span)| {
-                let orig_tokens = attr.tokens();
+                let orig_tokens = attr.tokens().to_tokenstream();
 
                 // We are taking an attribute of the form `#[cfg_attr(pred, attr)]`
                 // and producing an attribute of the form `#[attr]`. We
@@ -321,25 +382,34 @@ impl<'a> StripUnconfigured<'a> {
 
                 // Use the `#` in `#[cfg_attr(pred, attr)]` as the `#` token
                 // for `attr` when we expand it to `#[attr]`
-                let pound_token = orig_tokens.trees().next().unwrap();
-                if !matches!(pound_token, TokenTree::Token(Token { kind: TokenKind::Pound, .. })) {
-                    panic!("Bad tokens for attribute {:?}", attr);
+                let mut orig_trees = orig_tokens.trees();
+                let pound_token = match orig_trees.next().unwrap() {
+                    TokenTree::Token(token @ Token { kind: TokenKind::Pound, .. }) => token,
+                    _ => panic!("Bad tokens for attribute {:?}", attr),
+                };
+                let pound_span = pound_token.span;
+
+                let mut trees = vec![(AttrAnnotatedTokenTree::Token(pound_token), Spacing::Alone)];
+                if attr.style == AttrStyle::Inner {
+                    // For inner attributes, we do the same thing for the `!` in `#![some_attr]`
+                    let bang_token = match orig_trees.next().unwrap() {
+                        TokenTree::Token(token @ Token { kind: TokenKind::Not, .. }) => token,
+                        _ => panic!("Bad tokens for attribute {:?}", attr),
+                    };
+                    trees.push((AttrAnnotatedTokenTree::Token(bang_token), Spacing::Alone));
                 }
                 // We don't really have a good span to use for the syntheized `[]`
                 // in `#[attr]`, so just use the span of the `#` token.
-                let bracket_group = TokenTree::Delimited(
-                    DelimSpan::from_single(pound_token.span()),
+                let bracket_group = AttrAnnotatedTokenTree::Delimited(
+                    DelimSpan::from_single(pound_span),
                     DelimToken::Bracket,
                     item.tokens
                         .as_ref()
                         .unwrap_or_else(|| panic!("Missing tokens for {:?}", item))
                         .create_token_stream(),
                 );
-                let tokens = Some(LazyTokenStream::new(TokenStream::new(vec![
-                    (pound_token, Spacing::Alone),
-                    (bracket_group, Spacing::Alone),
-                ])));
-
+                trees.push((bracket_group, Spacing::Alone));
+                let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees)));
                 self.process_cfg_attr(attr::mk_attr_from_item(item, tokens, attr.style, span))
             })
             .collect()
@@ -457,7 +527,8 @@ impl<'a> StripUnconfigured<'a> {
             self.sess.parse_sess.span_diagnostic.span_err(attr.span, msg);
         }
 
-        self.process_cfg_attrs(expr)
+        self.process_cfg_attrs(expr);
+        self.try_configure_tokens(&mut *expr);
     }
 }
 
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 27274f706e1..529ef7e4611 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -12,7 +12,7 @@ use rustc_ast::ptr::P;
 use rustc_ast::token;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::visit::{self, AssocCtxt, Visitor};
-use rustc_ast::{AstLike, AttrItem, AttrStyle, Block, Inline, ItemKind, LitKind, MacArgs};
+use rustc_ast::{AstLike, AttrItem, Block, Inline, ItemKind, LitKind, MacArgs};
 use rustc_ast::{MacCallStmt, MacStmtStyle, MetaItemKind, ModKind, NestedMetaItem};
 use rustc_ast::{NodeId, PatKind, Path, StmtKind, Unsafe};
 use rustc_ast_pretty::pprust;
@@ -611,10 +611,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
 
         let invocations = {
             let mut collector = InvocationCollector {
+                // Non-derive macro invocations cannot see the results of cfg expansion - they
+                // will either be removed along with the item, or invoked before the cfg/cfg_attr
+                // attribute is expanded. Therefore, we don't need to configure the tokens
+                // Derive macros *can* see the results of cfg-expansion - they are handled
+                // specially in `fully_expand_fragment`
                 cfg: StripUnconfigured {
                     sess: &self.cx.sess,
                     features: self.cx.ecfg.features,
-                    modified: false,
+                    config_tokens: false,
                 },
                 cx: self.cx,
                 invocations: Vec::new(),
@@ -709,13 +714,26 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 SyntaxExtensionKind::Attr(expander) => {
                     self.gate_proc_macro_input(&item);
                     self.gate_proc_macro_attr_item(span, &item);
-                    let tokens = match attr.style {
-                        AttrStyle::Outer => item.into_tokens(&self.cx.sess.parse_sess),
-                        // FIXME: Properly collect tokens for inner attributes
-                        AttrStyle::Inner => rustc_parse::fake_token_stream(
+                    let mut fake_tokens = false;
+                    if let Annotatable::Item(item_inner) = &item {
+                        if let ItemKind::Mod(_, mod_kind) = &item_inner.kind {
+                            // FIXME: Collect tokens and use them instead of generating
+                            // fake ones. These are unstable, so it needs to be
+                            // fixed prior to stabilization
+                            // Fake tokens when we are invoking an inner attribute, and:
+                            fake_tokens = matches!(attr.style, ast::AttrStyle::Inner) &&
+                                // We are invoking an attribute on the crate root, or an outline
+                                // module
+                                (item_inner.ident.name.is_empty() || !matches!(mod_kind, ast::ModKind::Loaded(_, Inline::Yes, _)));
+                        }
+                    }
+                    let tokens = if fake_tokens {
+                        rustc_parse::fake_token_stream(
                             &self.cx.sess.parse_sess,
                             &item.into_nonterminal(),
-                        ),
+                        )
+                    } else {
+                        item.into_tokens(&self.cx.sess.parse_sess)
                     };
                     let attr_item = attr.unwrap_normal_item();
                     if let MacArgs::Eq(..) = attr_item.args {
@@ -897,21 +915,21 @@ pub fn parse_ast_fragment<'a>(
         }
         AstFragmentKind::TraitItems => {
             let mut items = SmallVec::new();
-            while let Some(item) = this.parse_trait_item()? {
+            while let Some(item) = this.parse_trait_item(ForceCollect::No)? {
                 items.extend(item);
             }
             AstFragment::TraitItems(items)
         }
         AstFragmentKind::ImplItems => {
             let mut items = SmallVec::new();
-            while let Some(item) = this.parse_impl_item()? {
+            while let Some(item) = this.parse_impl_item(ForceCollect::No)? {
                 items.extend(item);
             }
             AstFragment::ImplItems(items)
         }
         AstFragmentKind::ForeignItems => {
             let mut items = SmallVec::new();
-            while let Some(item) = this.parse_foreign_item()? {
+            while let Some(item) = this.parse_foreign_item(ForceCollect::No)? {
                 items.extend(item);
             }
             AstFragment::ForeignItems(items)
diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs
index 61b776ff2d2..3f84979ac05 100644
--- a/compiler/rustc_expand/src/proc_macro.rs
+++ b/compiler/rustc_expand/src/proc_macro.rs
@@ -94,7 +94,7 @@ impl MultiItemModifier for ProcMacroDerive {
         {
             TokenTree::token(token::Interpolated(Lrc::new(item)), DUMMY_SP).into()
         } else {
-            nt_to_tokenstream(&item, &ecx.sess.parse_sess, CanSynthesizeMissingTokens::Yes)
+            nt_to_tokenstream(&item, &ecx.sess.parse_sess, CanSynthesizeMissingTokens::No)
         };
 
         let server = proc_macro_server::Rustc::new(ecx);
diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs
index 9fead30c4a1..905077a48e2 100644
--- a/compiler/rustc_parse/src/lib.rs
+++ b/compiler/rustc_parse/src/lib.rs
@@ -1,5 +1,6 @@
 //! The main parser interface.
 
+#![feature(array_windows)]
 #![feature(crate_visibility_modifier)]
 #![feature(bindings_after_at)]
 #![feature(iter_order_by)]
@@ -9,9 +10,12 @@
 #![recursion_limit = "256"]
 
 use rustc_ast as ast;
-use rustc_ast::token::{self, Nonterminal};
-use rustc_ast::tokenstream::{self, CanSynthesizeMissingTokens, LazyTokenStream, TokenStream};
+use rustc_ast::token::{self, Nonterminal, Token, TokenKind};
+use rustc_ast::tokenstream::{self, AttributesData, CanSynthesizeMissingTokens, LazyTokenStream};
+use rustc_ast::tokenstream::{AttrAnnotatedTokenStream, AttrAnnotatedTokenTree};
+use rustc_ast::tokenstream::{Spacing, TokenStream};
 use rustc_ast::AstLike;
+use rustc_ast::Attribute;
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::{Diagnostic, FatalError, Level, PResult};
@@ -21,8 +25,6 @@ use rustc_span::{FileName, SourceFile, Span};
 use std::path::Path;
 use std::str;
 
-use tracing::debug;
-
 pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");
 
 #[macro_use]
@@ -255,19 +257,23 @@ pub fn nt_to_tokenstream(
     // before we fall back to the stringification.
 
     let convert_tokens =
-        |tokens: Option<&LazyTokenStream>| tokens.as_ref().map(|t| t.create_token_stream());
+        |tokens: Option<&LazyTokenStream>| Some(tokens?.create_token_stream().to_tokenstream());
 
     let tokens = match *nt {
-        Nonterminal::NtItem(ref item) => prepend_attrs(sess, &item.attrs, nt, item.tokens.as_ref()),
+        Nonterminal::NtItem(ref item) => prepend_attrs(&item.attrs, item.tokens.as_ref()),
         Nonterminal::NtBlock(ref block) => convert_tokens(block.tokens.as_ref()),
         Nonterminal::NtStmt(ref stmt) => {
-            let do_prepend = |tokens| prepend_attrs(sess, stmt.attrs(), nt, tokens);
             if let ast::StmtKind::Empty = stmt.kind {
-                let tokens: TokenStream =
-                    tokenstream::TokenTree::token(token::Semi, stmt.span).into();
-                do_prepend(Some(&LazyTokenStream::new(tokens)))
+                let tokens = AttrAnnotatedTokenStream::new(vec![(
+                    tokenstream::AttrAnnotatedTokenTree::Token(Token::new(
+                        TokenKind::Semi,
+                        stmt.span,
+                    )),
+                    Spacing::Alone,
+                )]);
+                prepend_attrs(&stmt.attrs(), Some(&LazyTokenStream::new(tokens)))
             } else {
-                do_prepend(stmt.tokens())
+                prepend_attrs(&stmt.attrs(), stmt.tokens())
             }
         }
         Nonterminal::NtPat(ref pat) => convert_tokens(pat.tokens.as_ref()),
@@ -283,10 +289,7 @@ pub fn nt_to_tokenstream(
         Nonterminal::NtVis(ref vis) => convert_tokens(vis.tokens.as_ref()),
         Nonterminal::NtTT(ref tt) => Some(tt.clone().into()),
         Nonterminal::NtExpr(ref expr) | Nonterminal::NtLiteral(ref expr) => {
-            if expr.tokens.is_none() {
-                debug!("missing tokens for expr {:?}", expr);
-            }
-            prepend_attrs(sess, &expr.attrs, nt, expr.tokens.as_ref())
+            prepend_attrs(&expr.attrs, expr.tokens.as_ref())
         }
     };
 
@@ -295,34 +298,30 @@ pub fn nt_to_tokenstream(
     } else if matches!(synthesize_tokens, CanSynthesizeMissingTokens::Yes) {
         return fake_token_stream(sess, nt);
     } else {
-        panic!("Missing tokens for nt at {:?}: {:?}", nt.span(), pprust::nonterminal_to_string(nt));
+        panic!(
+            "Missing tokens for nt {:?} at {:?}: {:?}",
+            nt,
+            nt.span(),
+            pprust::nonterminal_to_string(nt)
+        );
     }
 }
 
+fn prepend_attrs(attrs: &[Attribute], tokens: Option<&LazyTokenStream>) -> Option<TokenStream> {
+    let tokens = tokens?;
+    if attrs.is_empty() {
+        return Some(tokens.create_token_stream().to_tokenstream());
+    }
+    let attr_data = AttributesData { attrs: attrs.to_vec().into(), tokens: tokens.clone() };
+    let wrapped = AttrAnnotatedTokenStream::new(vec![(
+        AttrAnnotatedTokenTree::Attributes(attr_data),
+        Spacing::Alone,
+    )]);
+    Some(wrapped.to_tokenstream())
+}
+
 pub fn fake_token_stream(sess: &ParseSess, nt: &Nonterminal) -> TokenStream {
     let source = pprust::nonterminal_to_string(nt);
     let filename = FileName::macro_expansion_source_code(&source);
     parse_stream_from_source_str(filename, source, sess, Some(nt.span()))
 }
-
-fn prepend_attrs(
-    sess: &ParseSess,
-    attrs: &[ast::Attribute],
-    nt: &Nonterminal,
-    tokens: Option<&tokenstream::LazyTokenStream>,
-) -> Option<tokenstream::TokenStream> {
-    if attrs.is_empty() {
-        return Some(tokens?.create_token_stream());
-    }
-    let mut builder = tokenstream::TokenStreamBuilder::new();
-    for attr in attrs {
-        // FIXME: Correctly handle tokens for inner attributes.
-        // For now, we fall back to reparsing the original AST node
-        if attr.style == ast::AttrStyle::Inner {
-            return Some(fake_token_stream(sess, nt));
-        }
-        builder.push(attr.tokens());
-    }
-    builder.push(tokens?.create_token_stream());
-    Some(builder.build())
-}
diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs
index 95d4a48b845..ee6ff4dba39 100644
--- a/compiler/rustc_parse/src/parser/attr.rs
+++ b/compiler/rustc_parse/src/parser/attr.rs
@@ -1,10 +1,11 @@
-use super::{AttrWrapper, Parser, PathStyle};
+use super::{AttrWrapper, Capturing, Parser, PathStyle};
 use rustc_ast as ast;
 use rustc_ast::attr;
 use rustc_ast::token::{self, Nonterminal};
 use rustc_ast_pretty::pprust;
 use rustc_errors::{error_code, PResult};
 use rustc_span::{sym, Span};
+use std::convert::TryInto;
 
 use tracing::debug;
 
@@ -29,6 +30,7 @@ impl<'a> Parser<'a> {
     pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> {
         let mut attrs: Vec<ast::Attribute> = Vec::new();
         let mut just_parsed_doc_comment = false;
+        let start_pos = self.token_cursor.num_next_calls;
         loop {
             debug!("parse_outer_attributes: self.token={:?}", self.token);
             let attr = if self.check(&token::Pound) {
@@ -74,7 +76,7 @@ impl<'a> Parser<'a> {
                 break;
             }
         }
-        Ok(AttrWrapper::new(attrs))
+        Ok(AttrWrapper::new(attrs.into(), start_pos))
     }
 
     /// Matches `attribute = # ! [ meta_item ]`.
@@ -177,6 +179,7 @@ impl<'a> Parser<'a> {
     crate fn parse_inner_attributes(&mut self) -> PResult<'a, Vec<ast::Attribute>> {
         let mut attrs: Vec<ast::Attribute> = vec![];
         loop {
+            let start_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap();
             // Only try to parse if it is an inner attribute (has `!`).
             let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) {
                 Some(self.parse_attribute(InnerAttrPolicy::Permitted)?)
@@ -191,6 +194,18 @@ impl<'a> Parser<'a> {
                 None
             };
             if let Some(attr) = attr {
+                let end_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap();
+                // If we are currently capturing tokens, mark the location of this inner attribute.
+                // If capturing ends up creating a `LazyTokenStream`, we will include
+                // this replace range with it, removing the inner attribute from the final
+                // `AttrAnnotatedTokenStream`. Inner attributes are stored in the parsed AST note.
+                // During macro expansion, they are selectively inserted back into the
+                // token stream (the first inner attribute is remoevd each time we invoke the
+                // corresponding macro).
+                let range = start_pos..end_pos;
+                if let Capturing::Yes = self.capture_state.capturing {
+                    self.capture_state.inner_attr_ranges.insert(attr.id, (range, vec![]));
+                }
                 attrs.push(attr);
             } else {
                 break;
@@ -311,6 +326,9 @@ pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool {
     // One of the attributes may either itself be a macro,
     // or expand to macro attributes (`cfg_attr`).
     attrs.iter().any(|attr| {
+        if attr.is_doc_comment() {
+            return false;
+        }
         attr.ident().map_or(true, |ident| {
             ident.name == sym::cfg_attr || !rustc_feature::is_builtin_attr_name(ident.name)
         })
diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs
index 36a0fda6458..35759a396e8 100644
--- a/compiler/rustc_parse/src/parser/attr_wrapper.rs
+++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs
@@ -1,12 +1,14 @@
-use super::attr;
-use super::{ForceCollect, Parser, TokenCursor, TrailingToken};
-use rustc_ast::token::{self, Token, TokenKind};
-use rustc_ast::tokenstream::{CreateTokenStream, TokenStream, TokenTree, TreeAndSpacing};
-use rustc_ast::tokenstream::{DelimSpan, LazyTokenStream, Spacing};
-use rustc_ast::AstLike;
+use super::{Capturing, FlatToken, ForceCollect, Parser, ReplaceRange, TokenCursor, TrailingToken};
+use rustc_ast::token::{self, DelimToken, Token, TokenKind};
+use rustc_ast::tokenstream::{AttrAnnotatedTokenStream, AttributesData, CreateTokenStream};
+use rustc_ast::tokenstream::{AttrAnnotatedTokenTree, DelimSpan, LazyTokenStream, Spacing};
 use rustc_ast::{self as ast};
+use rustc_ast::{AstLike, AttrVec, Attribute};
 use rustc_errors::PResult;
-use rustc_span::{Span, DUMMY_SP};
+use rustc_span::{sym, Span, DUMMY_SP};
+
+use std::convert::TryInto;
+use std::ops::Range;
 
 /// A wrapper type to ensure that the parser handles outer attributes correctly.
 /// When we parse outer attributes, we need to ensure that we capture tokens
@@ -23,23 +25,158 @@ use rustc_span::{Span, DUMMY_SP};
 /// cannot directly access the `attrs` field
 #[derive(Debug, Clone)]
 pub struct AttrWrapper {
-    attrs: Vec<ast::Attribute>,
+    attrs: AttrVec,
+    // The start of the outer attributes in the token cursor.
+    // This allows us to create a `ReplaceRange` for the entire attribute
+    // target, including outer attributes.
+    start_pos: usize,
 }
 
+// This struct is passed around very frequently,
+// so make sure it doesn't accidentally get larger
+#[cfg(target_arch = "x86_64")]
+rustc_data_structures::static_assert_size!(AttrWrapper, 16);
+
 impl AttrWrapper {
-    pub fn empty() -> AttrWrapper {
-        AttrWrapper { attrs: vec![] }
+    pub(super) fn new(attrs: AttrVec, start_pos: usize) -> AttrWrapper {
+        AttrWrapper { attrs, start_pos }
     }
-    pub fn new(attrs: Vec<ast::Attribute>) -> AttrWrapper {
-        AttrWrapper { attrs }
+    pub fn empty() -> AttrWrapper {
+        AttrWrapper { attrs: AttrVec::new(), start_pos: usize::MAX }
     }
     // FIXME: Delay span bug here?
-    pub(crate) fn take_for_recovery(self) -> Vec<ast::Attribute> {
+    pub(crate) fn take_for_recovery(self) -> AttrVec {
         self.attrs
     }
+
+    // FIXME: require passing an NT to prevent misuse of this method
+    pub(crate) fn prepend_to_nt_inner(self, attrs: &mut Vec<Attribute>) {
+        let mut self_attrs: Vec<_> = self.attrs.into();
+        std::mem::swap(attrs, &mut self_attrs);
+        attrs.extend(self_attrs);
+    }
+
     pub fn is_empty(&self) -> bool {
         self.attrs.is_empty()
     }
+
+    pub fn maybe_needs_tokens(&self) -> bool {
+        crate::parser::attr::maybe_needs_tokens(&self.attrs)
+    }
+}
+
+/// Returns `true` if `attrs` contains a `cfg` or `cfg_attr` attribute
+fn has_cfg_or_cfg_attr(attrs: &[Attribute]) -> bool {
+    // NOTE: Builtin attributes like `cfg` and `cfg_attr` cannot be renamed via imports.
+    // Therefore, the absence of a literal `cfg` or `cfg_attr` guarantees that
+    // we don't need to do any eager expansion.
+    attrs.iter().any(|attr| {
+        attr.ident().map_or(false, |ident| ident.name == sym::cfg || ident.name == sym::cfg_attr)
+    })
+}
+
+// Produces a `TokenStream` on-demand. Using `cursor_snapshot`
+// and `num_calls`, we can reconstruct the `TokenStream` seen
+// by the callback. This allows us to avoid producing a `TokenStream`
+// if it is never needed - for example, a captured `macro_rules!`
+// argument that is never passed to a proc macro.
+// In practice token stream creation happens rarely compared to
+// calls to `collect_tokens` (see some statistics in #78736),
+// so we are doing as little up-front work as possible.
+//
+// This also makes `Parser` very cheap to clone, since
+// there is no intermediate collection buffer to clone.
+#[derive(Clone)]
+struct LazyTokenStreamImpl {
+    start_token: (Token, Spacing),
+    cursor_snapshot: TokenCursor,
+    num_calls: usize,
+    break_last_token: bool,
+    replace_ranges: Box<[ReplaceRange]>,
+}
+
+#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+rustc_data_structures::static_assert_size!(LazyTokenStreamImpl, 144);
+
+impl CreateTokenStream for LazyTokenStreamImpl {
+    fn create_token_stream(&self) -> AttrAnnotatedTokenStream {
+        // The token produced by the final call to `next` or `next_desugared`
+        // was not actually consumed by the callback. The combination
+        // of chaining the initial token and using `take` produces the desired
+        // result - we produce an empty `TokenStream` if no calls were made,
+        // and omit the final token otherwise.
+        let mut cursor_snapshot = self.cursor_snapshot.clone();
+        let tokens =
+            std::iter::once((FlatToken::Token(self.start_token.0.clone()), self.start_token.1))
+                .chain((0..self.num_calls).map(|_| {
+                    let token = if cursor_snapshot.desugar_doc_comments {
+                        cursor_snapshot.next_desugared()
+                    } else {
+                        cursor_snapshot.next()
+                    };
+                    (FlatToken::Token(token.0), token.1)
+                }))
+                .take(self.num_calls);
+
+        if !self.replace_ranges.is_empty() {
+            let mut tokens: Vec<_> = tokens.collect();
+            let mut replace_ranges = self.replace_ranges.clone();
+            replace_ranges.sort_by_key(|(range, _)| range.start);
+
+            #[cfg(debug_assertions)]
+            {
+                for [(range, tokens), (next_range, next_tokens)] in replace_ranges.array_windows() {
+                    assert!(
+                        range.end <= next_range.start || range.end >= next_range.end,
+                        "Replace ranges should either be disjoint or nested: ({:?}, {:?}) ({:?}, {:?})",
+                        range,
+                        tokens,
+                        next_range,
+                        next_tokens,
+                    );
+                }
+            }
+
+            // Process the replace ranges, starting from the highest start
+            // position and working our way back. If have tokens like:
+            //
+            // `#[cfg(FALSE)]` struct Foo { #[cfg(FALSE)] field: bool }`
+            //
+            // Then we will generate replace ranges for both
+            // the `#[cfg(FALSE)] field: bool` and the entire
+            // `#[cfg(FALSE)]` struct Foo { #[cfg(FALSE)] field: bool }`
+            //
+            // By starting processing from the replace range with the greatest
+            // start position, we ensure that any replace range which encloses
+            // another replace range will capture the *replaced* tokens for the inner
+            // range, not the original tokens.
+            for (range, new_tokens) in replace_ranges.iter().rev() {
+                assert!(!range.is_empty(), "Cannot replace an empty range: {:?}", range);
+                // Replace ranges are only allowed to decrease the number of tokens.
+                assert!(
+                    range.len() >= new_tokens.len(),
+                    "Range {:?} has greater len than {:?}",
+                    range,
+                    new_tokens
+                );
+
+                // Replace any removed tokens with `FlatToken::Empty`.
+                // This keeps the total length of `tokens` constant throughout the
+                // replacement process, allowing us to use all of the `ReplaceRanges` entries
+                // without adjusting indices.
+                let filler = std::iter::repeat((FlatToken::Empty, Spacing::Alone))
+                    .take(range.len() - new_tokens.len());
+
+                tokens.splice(
+                    (range.start as usize)..(range.end as usize),
+                    new_tokens.clone().into_iter().chain(filler),
+                );
+            }
+            make_token_stream(tokens.into_iter(), self.break_last_token)
+        } else {
+            make_token_stream(tokens, self.break_last_token)
+        }
+    }
 }
 
 impl<'a> Parser<'a> {
@@ -65,106 +202,195 @@ impl<'a> Parser<'a> {
         force_collect: ForceCollect,
         f: impl FnOnce(&mut Self, Vec<ast::Attribute>) -> PResult<'a, (R, TrailingToken)>,
     ) -> PResult<'a, R> {
-        if matches!(force_collect, ForceCollect::No) && !attr::maybe_needs_tokens(&attrs.attrs) {
-            return Ok(f(self, attrs.attrs)?.0);
+        // We only bail out when nothing could possibly observe the collected tokens:
+        // 1. We cannot be force collecting tokens (since force-collecting requires tokens
+        //    by definition
+        if matches!(force_collect, ForceCollect::No)
+            // None of our outer attributes can require tokens (e.g. a proc-macro)
+            && !attrs.maybe_needs_tokens()
+            // If our target supports custom inner attributes, then we cannot bail
+            // out early, since we may need to capture tokens for a custom inner attribute
+            // invocation.
+            && !R::SUPPORTS_CUSTOM_INNER_ATTRS
+            // Never bail out early in `capture_cfg` mode, since there might be `#[cfg]`
+            // or `#[cfg_attr]` attributes.
+            && !self.capture_cfg
+        {
+            return Ok(f(self, attrs.attrs.into())?.0);
         }
+
         let start_token = (self.token.clone(), self.token_spacing);
         let cursor_snapshot = self.token_cursor.clone();
 
-        let (mut ret, trailing_token) = f(self, attrs.attrs)?;
-        let tokens = match ret.tokens_mut() {
-            Some(tokens) if tokens.is_none() => tokens,
-            _ => return Ok(ret),
-        };
+        let has_outer_attrs = !attrs.attrs.is_empty();
+        let prev_capturing = std::mem::replace(&mut self.capture_state.capturing, Capturing::Yes);
+        let replace_ranges_start = self.capture_state.replace_ranges.len();
+
+        let ret = f(self, attrs.attrs.into());
+
+        self.capture_state.capturing = prev_capturing;
+
+        let (mut ret, trailing) = ret?;
 
-        // Produces a `TokenStream` on-demand. Using `cursor_snapshot`
-        // and `num_calls`, we can reconstruct the `TokenStream` seen
-        // by the callback. This allows us to avoid producing a `TokenStream`
-        // if it is never needed - for example, a captured `macro_rules!`
-        // argument that is never passed to a proc macro.
-        // In practice token stream creation happens rarely compared to
-        // calls to `collect_tokens` (see some statistics in #78736),
-        // so we are doing as little up-front work as possible.
-        //
-        // This also makes `Parser` very cheap to clone, since
-        // there is no intermediate collection buffer to clone.
-        #[derive(Clone)]
-        struct LazyTokenStreamImpl {
-            start_token: (Token, Spacing),
-            cursor_snapshot: TokenCursor,
-            num_calls: usize,
-            desugar_doc_comments: bool,
-            append_unglued_token: Option<TreeAndSpacing>,
+        // When we're not in `capture-cfg` mode, then bail out early if:
+        // 1. Our target doesn't support tokens at all (e.g we're parsing an `NtIdent`)
+        //    so there's nothing for us to do.
+        // 2. Our target already has tokens set (e.g. we've parsed something
+        // like `#[my_attr] $item`. The actual parsing code takes care of prepending
+        // any attributes to the nonterminal, so we don't need to modify the
+        // already captured tokens.
+        // Note that this check is independent of `force_collect`- if we already
+        // have tokens, or can't even store them, then there's never a need to
+        // force collection of new tokens.
+        if !self.capture_cfg && matches!(ret.tokens_mut(), None | Some(Some(_))) {
+            return Ok(ret);
+        }
+
+        // This is very similar to the bail out check at the start of this function.
+        // Now that we've parsed an AST node, we have more information available.
+        if matches!(force_collect, ForceCollect::No)
+            // We now have inner attributes available, so this check is more precise
+            // than `attrs.maybe_needs_tokens()` at the start of the function.
+            // As a result, we don't need to check `R::SUPPORTS_CUSTOM_INNER_ATTRS`
+            && !crate::parser::attr::maybe_needs_tokens(ret.attrs())
+            // Subtle: We call `has_cfg_or_cfg_attr` with the attrs from `ret`.
+            // This ensures that we consider inner attributes (e.g. `#![cfg]`),
+            // which require us to have tokens available
+            // We also call `has_cfg_or_cfg_attr` at the beginning of this function,
+            // but we only bail out if there's no possibility of inner attributes
+            // (!R::SUPPORTS_CUSTOM_INNER_ATTRS)
+            // We only catpure about `#[cfg]` or `#[cfg_attr]` in `capture_cfg`
+            // mode - during normal parsing, we don't need any special capturing
+            // for those attributes, since they're builtin.
+            && !(self.capture_cfg && has_cfg_or_cfg_attr(ret.attrs()))
+        {
+            return Ok(ret);
         }
-        impl CreateTokenStream for LazyTokenStreamImpl {
-            fn create_token_stream(&self) -> TokenStream {
-                if self.num_calls == 0 {
-                    return TokenStream::new(vec![]);
-                }
 
-                let mut cursor_snapshot = self.cursor_snapshot.clone();
-                // Don't skip `None` delimiters, since we want to pass them to
-                // proc macros. Normally, we'll end up capturing `TokenKind::Interpolated`,
-                // which gets converted to a `None`-delimited group when we invoke
-                // a proc-macro. However, it's possible to already have a `None`-delimited
-                // group in the stream (such as when parsing the output of a proc-macro,
-                // or in certain unusual cases with cross-crate `macro_rules!` macros).
-                cursor_snapshot.skip_none_delims = false;
-
-                // The token produced by the final call to `next` or `next_desugared`
-                // was not actually consumed by the callback.
-                let num_calls = self.num_calls - 1;
-                let mut i = 0;
-                let tokens =
-                    std::iter::once(self.start_token.clone()).chain(std::iter::from_fn(|| {
-                        if i >= num_calls {
-                            return None;
-                        }
-
-                        let token = if self.desugar_doc_comments {
-                            cursor_snapshot.next_desugared()
-                        } else {
-                            cursor_snapshot.next()
-                        };
-
-                        // When the `LazyTokenStreamImpl` was original produced, we did *not*
-                        // include `NoDelim` tokens in `num_calls`, since they are normally ignored
-                        // by the parser. Therefore, we only increment our counter for other types of tokens.
-                        if !matches!(
-                            token.0.kind,
-                            token::OpenDelim(token::NoDelim) | token::CloseDelim(token::NoDelim)
-                        ) {
-                            i += 1;
-                        }
-                        Some(token)
-                    }));
-
-                make_token_stream(tokens, self.append_unglued_token.clone())
+        let mut inner_attr_replace_ranges = Vec::new();
+        // Take the captured ranges for any inner attributes that we parsed.
+        for inner_attr in ret.attrs().iter().filter(|a| a.style == ast::AttrStyle::Inner) {
+            if let Some(attr_range) = self.capture_state.inner_attr_ranges.remove(&inner_attr.id) {
+                inner_attr_replace_ranges.push(attr_range);
+            } else {
+                self.sess
+                    .span_diagnostic
+                    .delay_span_bug(inner_attr.span, "Missing token range for attribute");
             }
         }
 
-        let mut num_calls = self.token_cursor.num_next_calls - cursor_snapshot.num_next_calls;
-        match trailing_token {
+        let replace_ranges_end = self.capture_state.replace_ranges.len();
+
+        let cursor_snapshot_next_calls = cursor_snapshot.num_next_calls;
+        let mut end_pos = self.token_cursor.num_next_calls;
+
+        // Capture a trailing token if requested by the callback 'f'
+        match trailing {
             TrailingToken::None => {}
             TrailingToken::Semi => {
                 assert_eq!(self.token.kind, token::Semi);
-                num_calls += 1;
+                end_pos += 1;
             }
             TrailingToken::MaybeComma => {
                 if self.token.kind == token::Comma {
-                    num_calls += 1;
+                    end_pos += 1;
                 }
             }
         }
 
-        *tokens = Some(LazyTokenStream::new(LazyTokenStreamImpl {
+        // If we 'broke' the last token (e.g. breaking a '>>' token to two '>' tokens),
+        // then extend the range of captured tokens to include it, since the parser
+        // was not actually bumped past it. When the `LazyTokenStream` gets converted
+        // into a `AttrAnnotatedTokenStream`, we will create the proper token.
+        if self.token_cursor.break_last_token {
+            assert_eq!(
+                trailing,
+                TrailingToken::None,
+                "Cannot set `break_last_token` and have trailing token"
+            );
+            end_pos += 1;
+        }
+
+        let num_calls = end_pos - cursor_snapshot_next_calls;
+
+        // If we have no attributes, then we will never need to
+        // use any replace ranges.
+        let replace_ranges: Box<[ReplaceRange]> = if ret.attrs().is_empty() && !self.capture_cfg {
+            Box::new([])
+        } else {
+            // Grab any replace ranges that occur *inside* the current AST node.
+            // We will perform the actual replacement when we convert the `LazyTokenStream`
+            // to a `AttrAnnotatedTokenStream`
+            let start_calls: u32 = cursor_snapshot_next_calls.try_into().unwrap();
+            self.capture_state.replace_ranges[replace_ranges_start..replace_ranges_end]
+                .iter()
+                .cloned()
+                .chain(inner_attr_replace_ranges.clone().into_iter())
+                .map(|(range, tokens)| {
+                    ((range.start - start_calls)..(range.end - start_calls), tokens)
+                })
+                .collect()
+        };
+
+        let tokens = LazyTokenStream::new(LazyTokenStreamImpl {
             start_token,
             num_calls,
             cursor_snapshot,
-            desugar_doc_comments: self.desugar_doc_comments,
-            append_unglued_token: self.token_cursor.append_unglued_token.clone(),
-        }));
+            break_last_token: self.token_cursor.break_last_token,
+            replace_ranges,
+        });
+
+        // If we support tokens at all
+        if let Some(target_tokens) = ret.tokens_mut() {
+            if let Some(target_tokens) = target_tokens {
+                assert!(
+                    !self.capture_cfg,
+                    "Encountered existing tokens with capture_cfg set: {:?}",
+                    target_tokens
+                );
+            } else {
+                // Store se our newly captured tokens into the AST node
+                *target_tokens = Some(tokens.clone());
+            };
+        }
 
+        let final_attrs = ret.attrs();
+
+        // If `capture_cfg` is set and we're inside a recursive call to
+        // `collect_tokens_trailing_token`, then we need to register a replace range
+        // if we have `#[cfg]` or `#[cfg_attr]`. This allows us to run eager cfg-expansion
+        // on the captured token stream.
+        if self.capture_cfg
+            && matches!(self.capture_state.capturing, Capturing::Yes)
+            && has_cfg_or_cfg_attr(&final_attrs)
+        {
+            let attr_data = AttributesData { attrs: final_attrs.to_vec().into(), tokens };
+
+            // Replace the entire AST node that we just parsed, including attributes,
+            // with a `FlatToken::AttrTarget`. If this AST node is inside an item
+            // that has `#[derive]`, then this will allow us to cfg-expand this
+            // AST node.
+            let start_pos =
+                if has_outer_attrs { attrs.start_pos } else { cursor_snapshot_next_calls };
+            let new_tokens = vec![(FlatToken::AttrTarget(attr_data), Spacing::Alone)];
+
+            assert!(
+                !self.token_cursor.break_last_token,
+                "Should not have unglued last token with cfg attr"
+            );
+            let range: Range<u32> = (start_pos.try_into().unwrap())..(end_pos.try_into().unwrap());
+            self.capture_state.replace_ranges.push((range, new_tokens));
+            self.capture_state.replace_ranges.extend(inner_attr_replace_ranges);
+        }
+
+        // Only clear our `replace_ranges` when we're finished capturing entirely.
+        if matches!(self.capture_state.capturing, Capturing::No) {
+            self.capture_state.replace_ranges.clear();
+            // We don't clear `inner_attr_ranges`, as doing so repeatedly
+            // had a measureable performance impact. Most inner attributes that
+            // we insert will get removed - when we drop the parser, we'll free
+            // up the memory used by any attributes that we didn't remove from the map.
+        }
         Ok(ret)
     }
 }
@@ -172,43 +398,112 @@ impl<'a> Parser<'a> {
 /// Converts a flattened iterator of tokens (including open and close delimiter tokens)
 /// into a `TokenStream`, creating a `TokenTree::Delimited` for each matching pair
 /// of open and close delims.
+// FIXME(#67062): Currently, we don't parse `None`-delimited groups correctly,
+// which can cause us to end up with mismatched `None` delimiters in our
+// captured tokens. This function contains several hacks to work around this -
+// essentially, we throw away mismatched `None` delimiters when we encounter them.
+// Once we properly parse `None` delimiters, they can be captured just like any
+// other tokens, and these hacks can be removed.
 fn make_token_stream(
-    tokens: impl Iterator<Item = (Token, Spacing)>,
-    append_unglued_token: Option<TreeAndSpacing>,
-) -> TokenStream {
+    mut iter: impl Iterator<Item = (FlatToken, Spacing)>,
+    break_last_token: bool,
+) -> AttrAnnotatedTokenStream {
     #[derive(Debug)]
     struct FrameData {
         open: Span,
-        inner: Vec<(TokenTree, Spacing)>,
+        open_delim: DelimToken,
+        inner: Vec<(AttrAnnotatedTokenTree, Spacing)>,
     }
-    let mut stack = vec![FrameData { open: DUMMY_SP, inner: vec![] }];
-    for (token, spacing) in tokens {
+    let mut stack =
+        vec![FrameData { open: DUMMY_SP, open_delim: DelimToken::NoDelim, inner: vec![] }];
+    let mut token_and_spacing = iter.next();
+    while let Some((token, spacing)) = token_and_spacing {
         match token {
-            Token { kind: TokenKind::OpenDelim(_), span } => {
-                stack.push(FrameData { open: span, inner: vec![] });
+            FlatToken::Token(Token { kind: TokenKind::OpenDelim(delim), span }) => {
+                stack.push(FrameData { open: span, open_delim: delim, inner: vec![] });
             }
-            Token { kind: TokenKind::CloseDelim(delim), span } => {
-                let frame_data = stack.pop().expect("Token stack was empty!");
+            FlatToken::Token(Token { kind: TokenKind::CloseDelim(delim), span }) => {
+                // HACK: If we enconter a mismatched `None` delimiter at the top
+                // level, just ignore it.
+                if matches!(delim, DelimToken::NoDelim)
+                    && (stack.len() == 1
+                        || !matches!(stack.last_mut().unwrap().open_delim, DelimToken::NoDelim))
+                {
+                    token_and_spacing = iter.next();
+                    continue;
+                }
+                let frame_data = stack
+                    .pop()
+                    .unwrap_or_else(|| panic!("Token stack was empty for token: {:?}", token));
+
+                // HACK: If our current frame has a mismatched opening `None` delimiter,
+                // merge our current frame with the one above it. That is, transform
+                // `[ { < first second } third ]` into `[ { first second } third ]`
+                if !matches!(delim, DelimToken::NoDelim)
+                    && matches!(frame_data.open_delim, DelimToken::NoDelim)
+                {
+                    stack.last_mut().unwrap().inner.extend(frame_data.inner);
+                    // Process our closing delimiter again, this time at the previous
+                    // frame in the stack
+                    token_and_spacing = Some((token, spacing));
+                    continue;
+                }
+
+                assert_eq!(
+                    frame_data.open_delim, delim,
+                    "Mismatched open/close delims: open={:?} close={:?}",
+                    frame_data.open, span
+                );
                 let dspan = DelimSpan::from_pair(frame_data.open, span);
-                let stream = TokenStream::new(frame_data.inner);
-                let delimited = TokenTree::Delimited(dspan, delim, stream);
+                let stream = AttrAnnotatedTokenStream::new(frame_data.inner);
+                let delimited = AttrAnnotatedTokenTree::Delimited(dspan, delim, stream);
                 stack
                     .last_mut()
-                    .unwrap_or_else(|| panic!("Bottom token frame is missing for tokens!"))
+                    .unwrap_or_else(|| {
+                        panic!("Bottom token frame is missing for token: {:?}", token)
+                    })
                     .inner
                     .push((delimited, Spacing::Alone));
             }
-            token => {
-                stack
-                    .last_mut()
-                    .expect("Bottom token frame is missing!")
-                    .inner
-                    .push((TokenTree::Token(token), spacing));
-            }
+            FlatToken::Token(token) => stack
+                .last_mut()
+                .expect("Bottom token frame is missing!")
+                .inner
+                .push((AttrAnnotatedTokenTree::Token(token), spacing)),
+            FlatToken::AttrTarget(data) => stack
+                .last_mut()
+                .expect("Bottom token frame is missing!")
+                .inner
+                .push((AttrAnnotatedTokenTree::Attributes(data), spacing)),
+            FlatToken::Empty => {}
         }
+        token_and_spacing = iter.next();
+    }
+    // HACK: If we don't have a closing `None` delimiter for our last
+    // frame, merge the frame with the top-level frame. That is,
+    // turn `< first second` into `first second`
+    if stack.len() == 2 && stack[1].open_delim == DelimToken::NoDelim {
+        let temp_buf = stack.pop().unwrap();
+        stack.last_mut().unwrap().inner.extend(temp_buf.inner);
     }
     let mut final_buf = stack.pop().expect("Missing final buf!");
-    final_buf.inner.extend(append_unglued_token);
+    if break_last_token {
+        let (last_token, spacing) = final_buf.inner.pop().unwrap();
+        if let AttrAnnotatedTokenTree::Token(last_token) = last_token {
+            let unglued_first = last_token.kind.break_two_token_op().unwrap().0;
+
+            // A 'unglued' token is always two ASCII characters
+            let mut first_span = last_token.span.shrink_to_lo();
+            first_span = first_span.with_hi(first_span.lo() + rustc_span::BytePos(1));
+
+            final_buf.inner.push((
+                AttrAnnotatedTokenTree::Token(Token::new(unglued_first, first_span)),
+                spacing,
+            ));
+        } else {
+            panic!("Unexpected last token {:?}", last_token)
+        }
+    }
     assert!(stack.is_empty(), "Stack should be empty: final_buf={:?} stack={:?}", final_buf, stack);
-    TokenStream::new(final_buf.inner)
+    AttrAnnotatedTokenStream::new(final_buf.inner)
 }
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 02ee268b88c..e155b3fa773 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -2581,19 +2581,17 @@ impl<'a> Parser<'a> {
         attrs: AttrWrapper,
         f: impl FnOnce(&mut Self, Vec<ast::Attribute>) -> PResult<'a, P<Expr>>,
     ) -> PResult<'a, P<Expr>> {
-        // FIXME - come up with a nice way to properly forward `ForceCollect`from
-        // the nonterminal parsing code. TThis approach iscorrect, but will cause
-        // us to unnecessarily capture tokens for exprs that have only builtin
-        // attributes. Revisit this before #![feature(stmt_expr_attributes)] is stabilized
-        let force_collect = if attrs.is_empty() { ForceCollect::No } else { ForceCollect::Yes };
-        self.collect_tokens_trailing_token(attrs, force_collect, |this, attrs| {
+        self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
             let res = f(this, attrs)?;
             let trailing = if this.restrictions.contains(Restrictions::STMT_EXPR)
                 && this.token.kind == token::Semi
             {
                 TrailingToken::Semi
             } else {
-                TrailingToken::None
+                // FIXME - pass this through from the place where we know
+                // we need a comma, rather than assuming that `#[attr] expr,`
+                // always captures a trailing comma
+                TrailingToken::MaybeComma
             };
             Ok((res, trailing))
         })
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 70dbaa53d38..2b7b58459c0 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -103,20 +103,11 @@ impl<'a> Parser<'a> {
         // over when we bump the parser
         if let token::Interpolated(nt) = &self.token.kind {
             if let token::NtItem(item) = &**nt {
-                let item = item.clone();
-
-                return self.collect_tokens_trailing_token(
-                    attrs,
-                    force_collect,
-                    |this, mut attrs| {
-                        let mut item = item;
-                        mem::swap(&mut item.attrs, &mut attrs);
-                        item.attrs.extend(attrs);
-                        // Bump the parser so the we capture the token::Interpolated
-                        this.bump();
-                        Ok((Some(item.into_inner()), TrailingToken::None))
-                    },
-                );
+                let mut item = item.clone();
+                self.bump();
+
+                attrs.prepend_to_nt_inner(&mut item.attrs);
+                return Ok(Some(item.into_inner()));
             }
         };
 
@@ -530,7 +521,7 @@ impl<'a> Parser<'a> {
 
         generics.where_clause = self.parse_where_clause()?;
 
-        let impl_items = self.parse_item_list(attrs, |p| p.parse_impl_item())?;
+        let impl_items = self.parse_item_list(attrs, |p| p.parse_impl_item(ForceCollect::No))?;
 
         let item_kind = match ty_second {
             Some(ty_second) => {
@@ -718,22 +709,32 @@ impl<'a> Parser<'a> {
         } else {
             // It's a normal trait.
             tps.where_clause = self.parse_where_clause()?;
-            let items = self.parse_item_list(attrs, |p| p.parse_trait_item())?;
+            let items = self.parse_item_list(attrs, |p| p.parse_trait_item(ForceCollect::No))?;
             Ok((ident, ItemKind::Trait(box TraitKind(is_auto, unsafety, tps, bounds, items))))
         }
     }
 
-    pub fn parse_impl_item(&mut self) -> PResult<'a, Option<Option<P<AssocItem>>>> {
-        self.parse_assoc_item(|_| true)
+    pub fn parse_impl_item(
+        &mut self,
+        force_collect: ForceCollect,
+    ) -> PResult<'a, Option<Option<P<AssocItem>>>> {
+        self.parse_assoc_item(|_| true, force_collect)
     }
 
-    pub fn parse_trait_item(&mut self) -> PResult<'a, Option<Option<P<AssocItem>>>> {
-        self.parse_assoc_item(|edition| edition >= Edition::Edition2018)
+    pub fn parse_trait_item(
+        &mut self,
+        force_collect: ForceCollect,
+    ) -> PResult<'a, Option<Option<P<AssocItem>>>> {
+        self.parse_assoc_item(|edition| edition >= Edition::Edition2018, force_collect)
     }
 
     /// Parses associated items.
-    fn parse_assoc_item(&mut self, req_name: ReqName) -> PResult<'a, Option<Option<P<AssocItem>>>> {
-        Ok(self.parse_item_(req_name, ForceCollect::No)?.map(
+    fn parse_assoc_item(
+        &mut self,
+        req_name: ReqName,
+        force_collect: ForceCollect,
+    ) -> PResult<'a, Option<Option<P<AssocItem>>>> {
+        Ok(self.parse_item_(req_name, force_collect)?.map(
             |Item { attrs, id, span, vis, ident, kind, tokens }| {
                 let kind = match AssocItemKind::try_from(kind) {
                     Ok(kind) => kind,
@@ -918,14 +919,17 @@ impl<'a> Parser<'a> {
         unsafety: Unsafe,
     ) -> PResult<'a, ItemInfo> {
         let abi = self.parse_abi(); // ABI?
-        let items = self.parse_item_list(attrs, |p| p.parse_foreign_item())?;
+        let items = self.parse_item_list(attrs, |p| p.parse_foreign_item(ForceCollect::No))?;
         let module = ast::ForeignMod { unsafety, abi, items };
         Ok((Ident::invalid(), ItemKind::ForeignMod(module)))
     }
 
     /// Parses a foreign item (one in an `extern { ... }` block).
-    pub fn parse_foreign_item(&mut self) -> PResult<'a, Option<Option<P<ForeignItem>>>> {
-        Ok(self.parse_item_(|_| true, ForceCollect::No)?.map(
+    pub fn parse_foreign_item(
+        &mut self,
+        force_collect: ForceCollect,
+    ) -> PResult<'a, Option<Option<P<ForeignItem>>>> {
+        Ok(self.parse_item_(|_| true, force_collect)?.map(
             |Item { attrs, id, span, vis, ident, kind, tokens }| {
                 let kind = match ForeignItemKind::try_from(kind) {
                     Ok(kind) => kind,
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 2fa25e40c6b..4b97c8b0a81 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -19,13 +19,16 @@ pub use path::PathStyle;
 
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, DelimToken, Token, TokenKind};
+use rustc_ast::tokenstream::AttributesData;
 use rustc_ast::tokenstream::{self, DelimSpan, Spacing};
-use rustc_ast::tokenstream::{TokenStream, TokenTree, TreeAndSpacing};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_ast::AttrId;
 use rustc_ast::DUMMY_NODE_ID;
 use rustc_ast::{self as ast, AnonConst, AstLike, AttrStyle, AttrVec, Const, CrateSugar, Extern};
 use rustc_ast::{Async, Expr, ExprKind, MacArgs, MacDelimiter, Mutability, StrLit, Unsafe};
 use rustc_ast::{Visibility, VisibilityKind};
 use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::PResult;
 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, FatalError};
@@ -34,6 +37,7 @@ use rustc_span::source_map::{Span, DUMMY_SP};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use tracing::debug;
 
+use std::ops::Range;
 use std::{cmp, mem, slice};
 
 bitflags::bitflags! {
@@ -64,6 +68,7 @@ pub enum ForceCollect {
     No,
 }
 
+#[derive(Debug, Eq, PartialEq)]
 pub enum TrailingToken {
     None,
     Semi,
@@ -111,6 +116,7 @@ pub struct Parser<'a> {
     pub token_spacing: Spacing,
     /// The previous token.
     pub prev_token: Token,
+    pub capture_cfg: bool,
     restrictions: Restrictions,
     expected_tokens: Vec<TokenType>,
     // Important: This must only be advanced from `next_tok`
@@ -134,6 +140,44 @@ pub struct Parser<'a> {
     pub last_type_ascription: Option<(Span, bool /* likely path typo */)>,
     /// If present, this `Parser` is not parsing Rust code but rather a macro call.
     subparser_name: Option<&'static str>,
+    capture_state: CaptureState,
+}
+
+/// Indicates a range of tokens that should be replaced by
+/// the tokens in the provided vector. This is used in two
+/// places during token collection:
+///
+/// 1. During the parsing of an AST node that may have a `#[derive]`
+/// attribute, we parse a nested AST node that has `#[cfg]` or `#[cfg_attr]`
+/// In this case, we use a `ReplaceRange` to replace the entire inner AST node
+/// with `FlatToken::AttrTarget`, allowing us to perform eager cfg-expansion
+/// on a `AttrAnnotatedTokenStream`
+///
+/// 2. When we parse an inner attribute while collecting tokens. We
+/// remove inner attributes from the token stream entirely, and
+/// instead track them through the `attrs` field on the AST node.
+/// This allows us to easily manipulate them (for example, removing
+/// the first macro inner attribute to invoke a proc-macro).
+/// When create a `TokenStream`, the inner attributes get inserted
+/// into the proper place in the token stream.
+pub type ReplaceRange = (Range<u32>, Vec<(FlatToken, Spacing)>);
+
+/// Controls how we capture tokens. Capturing can be expensive,
+/// so we try to avoid performing capturing in cases where
+/// we will never need a `AttrAnnotatedTokenStream`
+#[derive(Copy, Clone)]
+pub enum Capturing {
+    /// We aren't performing any capturing - this is the default mode.
+    No,
+    /// We are capturing tokens
+    Yes,
+}
+
+#[derive(Clone)]
+struct CaptureState {
+    capturing: Capturing,
+    replace_ranges: Vec<ReplaceRange>,
+    inner_attr_ranges: FxHashMap<AttrId, ReplaceRange>,
 }
 
 impl<'a> Drop for Parser<'a> {
@@ -167,18 +211,11 @@ struct TokenCursor {
     // want to capture just the first 'unglued' token.
     // For example, capturing the `Vec<u8>`
     // in `Option<Vec<u8>>` requires us to unglue
-    // the trailing `>>` token. The `append_unglued_token`
+    // the trailing `>>` token. The `break_last_token`
     // field is used to track this token - it gets
     // appended to the captured stream when
     // we evaluate a `LazyTokenStream`
-    append_unglued_token: Option<TreeAndSpacing>,
-    // If `true`, skip the delimiters for `None`-delimited groups,
-    // and just yield the inner tokens. This is `true` during
-    // normal parsing, since the parser code is not currently prepared
-    // to handle `None` delimiters. When capturing a `TokenStream`,
-    // however, we want to handle `None`-delimiters, since
-    // proc-macros always see `None`-delimited groups.
-    skip_none_delims: bool,
+    break_last_token: bool,
 }
 
 #[derive(Clone)]
@@ -191,13 +228,13 @@ struct TokenCursorFrame {
 }
 
 impl TokenCursorFrame {
-    fn new(span: DelimSpan, delim: DelimToken, tts: TokenStream, skip_none_delims: bool) -> Self {
+    fn new(span: DelimSpan, delim: DelimToken, tts: TokenStream) -> Self {
         TokenCursorFrame {
             delim,
             span,
-            open_delim: delim == token::NoDelim && skip_none_delims,
+            open_delim: false,
             tree_cursor: tts.into_trees(),
-            close_delim: delim == token::NoDelim && skip_none_delims,
+            close_delim: false,
         }
     }
 }
@@ -225,7 +262,7 @@ impl TokenCursor {
                     return (token, spacing);
                 }
                 TokenTree::Delimited(sp, delim, tts) => {
-                    let frame = TokenCursorFrame::new(sp, delim, tts, self.skip_none_delims);
+                    let frame = TokenCursorFrame::new(sp, delim, tts);
                     self.stack.push(mem::replace(&mut self.frame, frame));
                 }
             }
@@ -283,7 +320,6 @@ impl TokenCursor {
                         .cloned()
                         .collect::<TokenStream>()
                 },
-                self.skip_none_delims,
             ),
         ));
 
@@ -372,26 +408,24 @@ impl<'a> Parser<'a> {
         desugar_doc_comments: bool,
         subparser_name: Option<&'static str>,
     ) -> Self {
+        let mut start_frame = TokenCursorFrame::new(DelimSpan::dummy(), token::NoDelim, tokens);
+        start_frame.open_delim = true;
+        start_frame.close_delim = true;
+
         let mut parser = Parser {
             sess,
             token: Token::dummy(),
             token_spacing: Spacing::Alone,
             prev_token: Token::dummy(),
+            capture_cfg: false,
             restrictions: Restrictions::empty(),
             expected_tokens: Vec::new(),
-            // Skip over the delimiters for `None`-delimited groups
             token_cursor: TokenCursor {
-                frame: TokenCursorFrame::new(
-                    DelimSpan::dummy(),
-                    token::NoDelim,
-                    tokens,
-                    /* skip_none_delims */ true,
-                ),
+                frame: start_frame,
                 stack: Vec::new(),
                 num_next_calls: 0,
                 desugar_doc_comments,
-                append_unglued_token: None,
-                skip_none_delims: true,
+                break_last_token: false,
             },
             desugar_doc_comments,
             unmatched_angle_bracket_count: 0,
@@ -400,6 +434,11 @@ impl<'a> Parser<'a> {
             last_unexpected_token_span: None,
             last_type_ascription: None,
             subparser_name,
+            capture_state: CaptureState {
+                capturing: Capturing::No,
+                replace_ranges: Vec::new(),
+                inner_attr_ranges: Default::default(),
+            },
         };
 
         // Make parser point to the first token.
@@ -409,21 +448,29 @@ impl<'a> Parser<'a> {
     }
 
     fn next_tok(&mut self, fallback_span: Span) -> (Token, Spacing) {
-        let (mut next, spacing) = if self.desugar_doc_comments {
-            self.token_cursor.next_desugared()
-        } else {
-            self.token_cursor.next()
-        };
-        self.token_cursor.num_next_calls += 1;
-        // We've retrieved an token from the underlying
-        // cursor, so we no longer need to worry about
-        // an unglued token. See `break_and_eat` for more details
-        self.token_cursor.append_unglued_token = None;
-        if next.span.is_dummy() {
-            // Tweak the location for better diagnostics, but keep syntactic context intact.
-            next.span = fallback_span.with_ctxt(next.span.ctxt());
+        loop {
+            let (mut next, spacing) = if self.desugar_doc_comments {
+                self.token_cursor.next_desugared()
+            } else {
+                self.token_cursor.next()
+            };
+            self.token_cursor.num_next_calls += 1;
+            // We've retrieved an token from the underlying
+            // cursor, so we no longer need to worry about
+            // an unglued token. See `break_and_eat` for more details
+            self.token_cursor.break_last_token = false;
+            if next.span.is_dummy() {
+                // Tweak the location for better diagnostics, but keep syntactic context intact.
+                next.span = fallback_span.with_ctxt(next.span.ctxt());
+            }
+            if matches!(
+                next.kind,
+                token::OpenDelim(token::NoDelim) | token::CloseDelim(token::NoDelim)
+            ) {
+                continue;
+            }
+            return (next, spacing);
         }
-        (next, spacing)
     }
 
     pub fn unexpected<T>(&mut self) -> PResult<'a, T> {
@@ -621,8 +668,7 @@ impl<'a> Parser<'a> {
                 // If we consume any additional tokens, then this token
                 // is not needed (we'll capture the entire 'glued' token),
                 // and `next_tok` will set this field to `None`
-                self.token_cursor.append_unglued_token =
-                    Some((TokenTree::Token(self.token.clone()), Spacing::Alone));
+                self.token_cursor.break_last_token = true;
                 // Use the spacing of the glued token as the spacing
                 // of the unglued second token.
                 self.bump_with((Token::new(second, second_span), self.token_spacing));
@@ -1304,3 +1350,24 @@ pub fn emit_unclosed_delims(unclosed_delims: &mut Vec<UnmatchedBrace>, sess: &Pa
         }
     }
 }
+
+/// A helper struct used when building a `AttrAnnotatedTokenStream` from
+/// a `LazyTokenStream`. Both delimiter and non-delimited tokens
+/// are stored as `FlatToken::Token`. A vector of `FlatToken`s
+/// is then 'parsed' to build up a `AttrAnnotatedTokenStream` with nested
+/// `AttrAnnotatedTokenTree::Delimited` tokens
+#[derive(Debug, Clone)]
+pub enum FlatToken {
+    /// A token - this holds both delimiter (e.g. '{' and '}')
+    /// and non-delimiter tokens
+    Token(Token),
+    /// Holds the `AttributesData` for an AST node. The
+    /// `AttributesData` is inserted directly into the
+    /// constructed `AttrAnnotatedTokenStream` as
+    /// a `AttrAnnotatedTokenTree::Attributes`
+    AttrTarget(AttributesData),
+    /// A special 'empty' token that is ignored during the conversion
+    /// to a `AttrAnnotatedTokenStream`. This is used to simplify the
+    /// handling of replace ranges.
+    Empty,
+}
diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs
index 0c49d103583..5c4a2785d6e 100644
--- a/compiler/rustc_parse/src/parser/nonterminal.rs
+++ b/compiler/rustc_parse/src/parser/nonterminal.rs
@@ -153,9 +153,7 @@ impl<'a> Parser<'a> {
             NonterminalKind::Path => token::NtPath(
                 self.collect_tokens_no_attrs(|this| this.parse_path(PathStyle::Type))?,
             ),
-            NonterminalKind::Meta => {
-                token::NtMeta(P(self.collect_tokens_no_attrs(|this| this.parse_attr_item(false))?))
-            }
+            NonterminalKind::Meta => token::NtMeta(P(self.parse_attr_item(true)?)),
             NonterminalKind::TT => token::NtTT(self.parse_token_tree()),
             NonterminalKind::Vis => token::NtVis(
                 self.collect_tokens_no_attrs(|this| this.parse_visibility(FollowedByType::Yes))?,
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index 24fb4301cc2..592f64f4a39 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -48,39 +48,26 @@ impl<'a> Parser<'a> {
         if let token::Interpolated(nt) = &self.token.kind {
             if let token::NtStmt(stmt) = &**nt {
                 let mut stmt = stmt.clone();
-                return self.collect_tokens_trailing_token(
-                    attrs,
-                    force_collect,
-                    |this, mut attrs| {
-                        stmt.visit_attrs(|stmt_attrs| {
-                            mem::swap(stmt_attrs, &mut attrs);
-                            stmt_attrs.extend(attrs);
-                        });
-                        // Make sure we capture the token::Interpolated
-                        this.bump();
-                        Ok((Some(stmt), TrailingToken::None))
-                    },
-                );
+                self.bump();
+                stmt.visit_attrs(|stmt_attrs| {
+                    attrs.prepend_to_nt_inner(stmt_attrs);
+                });
+                return Ok(Some(stmt));
             }
         }
 
         Ok(Some(if self.token.is_keyword(kw::Let) {
             self.parse_local_mk(lo, attrs, capture_semi, force_collect)?
         } else if self.is_kw_followed_by_ident(kw::Mut) {
-            self.recover_stmt_local(
-                lo,
-                attrs.take_for_recovery().into(),
-                "missing keyword",
-                "let mut",
-            )?
+            self.recover_stmt_local(lo, attrs, "missing keyword", "let mut")?
         } else if self.is_kw_followed_by_ident(kw::Auto) {
             self.bump(); // `auto`
             let msg = "write `let` instead of `auto` to introduce a new variable";
-            self.recover_stmt_local(lo, attrs.take_for_recovery().into(), msg, "let")?
+            self.recover_stmt_local(lo, attrs, msg, "let")?
         } else if self.is_kw_followed_by_ident(sym::var) {
             self.bump(); // `var`
             let msg = "write `let` instead of `var` to introduce a new variable";
-            self.recover_stmt_local(lo, attrs.take_for_recovery().into(), msg, "let")?
+            self.recover_stmt_local(lo, attrs, msg, "let")?
         } else if self.check_path() && !self.token.is_qpath_start() && !self.is_path_start_item() {
             // We have avoided contextual keywords like `union`, items with `crate` visibility,
             // or `auto trait` items. We aim to parse an arbitrary path `a::b` but not something
@@ -112,7 +99,7 @@ impl<'a> Parser<'a> {
         attrs: AttrWrapper,
         force_collect: ForceCollect,
     ) -> PResult<'a, Stmt> {
-        self.collect_tokens_trailing_token(attrs, force_collect, |this, attrs| {
+        let stmt = self.collect_tokens_trailing_token(attrs, force_collect, |this, attrs| {
             let path = this.parse_path(PathStyle::Expr)?;
 
             if this.eat(&token::Not) {
@@ -132,14 +119,22 @@ impl<'a> Parser<'a> {
             };
 
             let expr = this.with_res(Restrictions::STMT_EXPR, |this| {
-                let expr = this.parse_dot_or_call_expr_with(expr, lo, attrs)?;
+                this.parse_dot_or_call_expr_with(expr, lo, attrs)
+            })?;
+            // `DUMMY_SP` will get overwritten later in this function
+            Ok((this.mk_stmt(rustc_span::DUMMY_SP, StmtKind::Expr(expr)), TrailingToken::None))
+        })?;
+
+        if let StmtKind::Expr(expr) = stmt.kind {
+            // Perform this outside of the `collect_tokens_trailing_token` closure,
+            // since our outer attributes do not apply to this part of the expression
+            let expr = self.with_res(Restrictions::STMT_EXPR, |this| {
                 this.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(expr))
             })?;
-            Ok((
-                this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Expr(expr)),
-                TrailingToken::None,
-            ))
-        })
+            Ok(self.mk_stmt(lo.to(self.prev_token.span), StmtKind::Expr(expr)))
+        } else {
+            Ok(stmt)
+        }
     }
 
     /// Parses a statement macro `mac!(args)` provided a `path` representing `mac`.
@@ -183,7 +178,7 @@ impl<'a> Parser<'a> {
     fn recover_stmt_local(
         &mut self,
         lo: Span,
-        attrs: AttrVec,
+        attrs: AttrWrapper,
         msg: &str,
         sugg: &str,
     ) -> PResult<'a, Stmt> {
@@ -213,9 +208,15 @@ impl<'a> Parser<'a> {
         })
     }
 
-    fn recover_local_after_let(&mut self, lo: Span, attrs: AttrVec) -> PResult<'a, Stmt> {
-        let local = self.parse_local(attrs)?;
-        Ok(self.mk_stmt(lo.to(self.prev_token.span), StmtKind::Local(local)))
+    fn recover_local_after_let(&mut self, lo: Span, attrs: AttrWrapper) -> PResult<'a, Stmt> {
+        self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
+            let local = this.parse_local(attrs.into())?;
+            // FIXME - maybe capture semicolon in recovery?
+            Ok((
+                this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Local(local)),
+                TrailingToken::None,
+            ))
+        })
     }
 
     /// Parses a local variable declaration.
diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs
index f3d33309124..e9d597d1ba6 100644
--- a/compiler/rustc_session/src/utils.rs
+++ b/compiler/rustc_session/src/utils.rs
@@ -1,7 +1,13 @@
+use crate::parse::ParseSess;
 use crate::session::Session;
+use rustc_ast::token::{self, DelimToken, Nonterminal, Token};
+use rustc_ast::tokenstream::CanSynthesizeMissingTokens;
+use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
 use rustc_data_structures::profiling::VerboseTimingGuard;
 use std::path::{Path, PathBuf};
 
+pub type NtToTokenstream = fn(&Nonterminal, &ParseSess, CanSynthesizeMissingTokens) -> TokenStream;
+
 impl Session {
     pub fn timer<'a>(&'a self, what: &'static str) -> VerboseTimingGuard<'a> {
         self.prof.verbose_generic_activity(what)
@@ -53,3 +59,52 @@ impl CanonicalizedPath {
         &self.original
     }
 }
+
+// FIXME: Find a better spot for this - it needs to be accessible from `rustc_ast_lowering`,
+// and needs to access `ParseSess
+pub struct FlattenNonterminals<'a> {
+    pub parse_sess: &'a ParseSess,
+    pub synthesize_tokens: CanSynthesizeMissingTokens,
+    pub nt_to_tokenstream: NtToTokenstream,
+}
+
+impl<'a> FlattenNonterminals<'a> {
+    pub fn process_token_stream(&mut self, tokens: TokenStream) -> TokenStream {
+        fn can_skip(stream: &TokenStream) -> bool {
+            stream.trees().all(|tree| match tree {
+                TokenTree::Token(token) => !matches!(token.kind, token::Interpolated(_)),
+                TokenTree::Delimited(_, _, inner) => can_skip(&inner),
+            })
+        }
+
+        if can_skip(&tokens) {
+            return tokens;
+        }
+
+        tokens.into_trees().flat_map(|tree| self.process_token_tree(tree).into_trees()).collect()
+    }
+
+    pub fn process_token_tree(&mut self, tree: TokenTree) -> TokenStream {
+        match tree {
+            TokenTree::Token(token) => self.process_token(token),
+            TokenTree::Delimited(span, delim, tts) => {
+                TokenTree::Delimited(span, delim, self.process_token_stream(tts)).into()
+            }
+        }
+    }
+
+    pub fn process_token(&mut self, token: Token) -> TokenStream {
+        match token.kind {
+            token::Interpolated(nt) => {
+                let tts = (self.nt_to_tokenstream)(&nt, self.parse_sess, self.synthesize_tokens);
+                TokenTree::Delimited(
+                    DelimSpan::from_single(token.span),
+                    DelimToken::NoDelim,
+                    self.process_token_stream(tts),
+                )
+                .into()
+            }
+            _ => TokenTree::Token(token).into(),
+        }
+    }
+}
diff --git a/src/test/ui/proc-macro/attr-complex-fn.stdout b/src/test/ui/proc-macro/attr-complex-fn.stdout
index a395a9ac3e8..72783efe08d 100644
--- a/src/test/ui/proc-macro/attr-complex-fn.stdout
+++ b/src/test/ui/proc-macro/attr-complex-fn.stdout
@@ -80,67 +80,67 @@ PRINT-ATTR INPUT (DISPLAY): impl < T > MyTrait < T > for MyStruct < { true } > {
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "impl",
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:1: 21:5 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:5: 21:6 (#0),
     },
     Ident {
         ident: "T",
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:6: 21:7 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:7: 21:8 (#0),
     },
     Ident {
         ident: "MyTrait",
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:9: 21:16 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:16: 21:17 (#0),
     },
     Ident {
         ident: "T",
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:17: 21:18 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:18: 21:19 (#0),
     },
     Ident {
         ident: "for",
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:20: 21:23 (#0),
     },
     Ident {
         ident: "MyStruct",
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:24: 21:32 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:32: 21:33 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "true",
-                span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+                span: $DIR/attr-complex-fn.rs:21:34: 21:38 (#0),
             },
         ],
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:33: 21:39 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:39: 21:40 (#0),
     },
     Group {
         delimiter: Brace,
@@ -148,24 +148,24 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+                span: $DIR/attr-complex-fn.rs:23:5: 23:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+                span: $DIR/attr-complex-fn.rs:23:6: 23:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "rustc_dummy",
-                        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+                        span: $DIR/attr-complex-fn.rs:23:8: 23:19 (#0),
                     },
                 ],
-                span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+                span: $DIR/attr-complex-fn.rs:23:7: 23:20 (#0),
             },
         ],
-        span: $DIR/attr-complex-fn.rs:21:1: 24:2 (#0),
+        span: $DIR/attr-complex-fn.rs:21:41: 24:2 (#0),
     },
 ]
diff --git a/src/test/ui/proc-macro/attribute-after-derive.stdout b/src/test/ui/proc-macro/attribute-after-derive.stdout
index d0b66551919..4c48e41ff33 100644
--- a/src/test/ui/proc-macro/attribute-after-derive.stdout
+++ b/src/test/ui/proc-macro/attribute-after-derive.stdout
@@ -87,16 +87,16 @@ PRINT-DERIVE INPUT (DISPLAY): struct AttributeDerive { }
 PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/attribute-after-derive.rs:18:1: 21:2 (#0),
+        span: $DIR/attribute-after-derive.rs:18:1: 18:7 (#0),
     },
     Ident {
         ident: "AttributeDerive",
-        span: $DIR/attribute-after-derive.rs:18:1: 21:2 (#0),
+        span: $DIR/attribute-after-derive.rs:18:8: 18:23 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/attribute-after-derive.rs:18:1: 21:2 (#0),
+        span: $DIR/attribute-after-derive.rs:18:24: 21:2 (#0),
     },
 ]
 PRINT-DERIVE INPUT (DISPLAY): #[print_attr] struct DeriveAttribute { }
@@ -104,45 +104,45 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:24:1: 24:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "print_attr",
-                span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+                span: $DIR/attribute-after-derive.rs:24:3: 24:13 (#0),
             },
         ],
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:24:2: 24:14 (#0),
     },
     Ident {
         ident: "struct",
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:25:1: 25:7 (#0),
     },
     Ident {
         ident: "DeriveAttribute",
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:25:8: 25:23 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:25:24: 28:2 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): struct DeriveAttribute { }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:25:1: 25:7 (#0),
     },
     Ident {
         ident: "DeriveAttribute",
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:25:8: 25:23 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/attribute-after-derive.rs:25:1: 28:2 (#0),
+        span: $DIR/attribute-after-derive.rs:25:24: 28:2 (#0),
     },
 ]
diff --git a/src/test/ui/proc-macro/cfg-eval-inner.stdout b/src/test/ui/proc-macro/cfg-eval-inner.stdout
index a9301d3349f..1f2b0039589 100644
--- a/src/test/ui/proc-macro/cfg-eval-inner.stdout
+++ b/src/test/ui/proc-macro/cfg-eval-inner.stdout
@@ -2,251 +2,246 @@ PRINT-ATTR INPUT (DISPLAY): impl Foo <
 [u8 ;
  {
      # ! [rustc_dummy(cursed_inner)] # ! [allow(unused)] struct Inner
-     { field : [u8 ; { # ! [rustc_dummy(another_cursed_inner)] 1 }], } 0
+     { field : [u8 ; { # ! [rustc_dummy(another_cursed_inner)] 1 }] } 0
  }] > { # ! [rustc_dummy(evaluated_attr)] fn bar() { } }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "impl",
-        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+        span: $DIR/cfg-eval-inner.rs:18:1: 18:5 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+        span: $DIR/cfg-eval-inner.rs:18:6: 18:9 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+        span: $DIR/cfg-eval-inner.rs:18:9: 18:10 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "u8",
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:18:11: 18:13 (#0),
             },
             Punct {
                 ch: ';',
                 spacing: Alone,
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:18:13: 18:14 (#0),
             },
             Group {
                 delimiter: Brace,
                 stream: TokenStream [
                     Punct {
                         ch: '#',
-                        spacing: Joint,
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        spacing: Alone,
+                        span: $DIR/cfg-eval-inner.rs:19:5: 19:6 (#0),
                     },
                     Punct {
                         ch: '!',
                         spacing: Alone,
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:19:6: 19:7 (#0),
                     },
                     Group {
                         delimiter: Bracket,
                         stream: TokenStream [
                             Ident {
                                 ident: "rustc_dummy",
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:19:29: 19:40 (#0),
                             },
                             Group {
                                 delimiter: Parenthesis,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "cursed_inner",
-                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                        span: $DIR/cfg-eval-inner.rs:19:41: 19:53 (#0),
                                     },
                                 ],
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:19:40: 19:54 (#0),
                             },
                         ],
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:19:5: 19:6 (#0),
                     },
                     Punct {
                         ch: '#',
                         spacing: Joint,
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:20:5: 20:6 (#0),
                     },
                     Punct {
                         ch: '!',
                         spacing: Alone,
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:20:6: 20:7 (#0),
                     },
                     Group {
                         delimiter: Bracket,
                         stream: TokenStream [
                             Ident {
                                 ident: "allow",
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:20:8: 20:13 (#0),
                             },
                             Group {
                                 delimiter: Parenthesis,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "unused",
-                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                        span: $DIR/cfg-eval-inner.rs:20:14: 20:20 (#0),
                                     },
                                 ],
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:20:13: 20:21 (#0),
                             },
                         ],
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:20:7: 20:22 (#0),
                     },
                     Ident {
                         ident: "struct",
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:21:5: 21:11 (#0),
                     },
                     Ident {
                         ident: "Inner",
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:21:12: 21:17 (#0),
                     },
                     Group {
                         delimiter: Brace,
                         stream: TokenStream [
                             Ident {
                                 ident: "field",
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:22:9: 22:14 (#0),
                             },
                             Punct {
                                 ch: ':',
                                 spacing: Alone,
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:22:14: 22:15 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "u8",
-                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                        span: $DIR/cfg-eval-inner.rs:22:17: 22:19 (#0),
                                     },
                                     Punct {
                                         ch: ';',
                                         spacing: Alone,
-                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                        span: $DIR/cfg-eval-inner.rs:22:19: 22:20 (#0),
                                     },
                                     Group {
                                         delimiter: Brace,
                                         stream: TokenStream [
                                             Punct {
                                                 ch: '#',
-                                                spacing: Joint,
-                                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                spacing: Alone,
+                                                span: $DIR/cfg-eval-inner.rs:23:13: 23:14 (#0),
                                             },
                                             Punct {
                                                 ch: '!',
                                                 spacing: Alone,
-                                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                span: $DIR/cfg-eval-inner.rs:23:14: 23:15 (#0),
                                             },
                                             Group {
                                                 delimiter: Bracket,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "rustc_dummy",
-                                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                        span: $DIR/cfg-eval-inner.rs:23:37: 23:48 (#0),
                                                     },
                                                     Group {
                                                         delimiter: Parenthesis,
                                                         stream: TokenStream [
                                                             Ident {
                                                                 ident: "another_cursed_inner",
-                                                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                                span: $DIR/cfg-eval-inner.rs:23:49: 23:69 (#0),
                                                             },
                                                         ],
-                                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                        span: $DIR/cfg-eval-inner.rs:23:48: 23:70 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                span: $DIR/cfg-eval-inner.rs:23:13: 23:14 (#0),
                                             },
                                             Literal {
                                                 kind: Integer,
                                                 symbol: "1",
                                                 suffix: None,
-                                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                                span: $DIR/cfg-eval-inner.rs:24:13: 24:14 (#0),
                                             },
                                         ],
-                                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                        span: $DIR/cfg-eval-inner.rs:22:21: 25:10 (#0),
                                     },
                                 ],
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
-                            },
-                            Punct {
-                                ch: ',',
-                                spacing: Alone,
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:22:16: 25:11 (#0),
                             },
                         ],
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:21:18: 26:6 (#0),
                     },
                     Literal {
                         kind: Integer,
                         symbol: "0",
                         suffix: None,
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:28:5: 28:6 (#0),
                     },
                 ],
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:18:15: 29:2 (#0),
             },
         ],
-        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+        span: $DIR/cfg-eval-inner.rs:18:10: 29:3 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+        span: $DIR/cfg-eval-inner.rs:29:3: 29:4 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Punct {
                 ch: '#',
-                spacing: Joint,
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                spacing: Alone,
+                span: $DIR/cfg-eval-inner.rs:32:5: 32:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:32:6: 32:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "rustc_dummy",
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:32:29: 32:40 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "evaluated_attr",
-                                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                                span: $DIR/cfg-eval-inner.rs:32:41: 32:55 (#0),
                             },
                         ],
-                        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                        span: $DIR/cfg-eval-inner.rs:32:40: 32:56 (#0),
                     },
                 ],
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:32:5: 32:6 (#0),
             },
             Ident {
                 ident: "fn",
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:34:5: 34:7 (#0),
             },
             Ident {
                 ident: "bar",
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:34:8: 34:11 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [],
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:34:11: 34:13 (#0),
             },
             Group {
                 delimiter: Brace,
                 stream: TokenStream [],
-                span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+                span: $DIR/cfg-eval-inner.rs:34:14: 36:6 (#0),
             },
         ],
-        span: $DIR/cfg-eval-inner.rs:18:1: 37:2 (#0),
+        span: $DIR/cfg-eval-inner.rs:29:5: 37:2 (#0),
     },
 ]
diff --git a/src/test/ui/proc-macro/cfg-eval.stdout b/src/test/ui/proc-macro/cfg-eval.stdout
index 1f784598ff7..6732caf08dd 100644
--- a/src/test/ui/proc-macro/cfg-eval.stdout
+++ b/src/test/ui/proc-macro/cfg-eval.stdout
@@ -2,11 +2,11 @@ PRINT-ATTR INPUT (DISPLAY): struct S1 { #[cfg(all())] #[allow()] field_true : u8
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+        span: $DIR/cfg-eval.rs:17:1: 17:7 (#0),
     },
     Ident {
         ident: "S1",
-        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+        span: $DIR/cfg-eval.rs:17:8: 17:10 (#0),
     },
     Group {
         delimiter: Brace,
@@ -14,73 +14,73 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Alone,
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:20:5: 20:6 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "cfg",
-                        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                        span: $DIR/cfg-eval.rs:20:7: 20:10 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "all",
-                                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                                span: $DIR/cfg-eval.rs:20:11: 20:14 (#0),
                             },
                             Group {
                                 delimiter: Parenthesis,
                                 stream: TokenStream [],
-                                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                                span: $DIR/cfg-eval.rs:20:14: 20:24 (#0),
                             },
                         ],
-                        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                        span: $DIR/cfg-eval.rs:20:10: 20:25 (#0),
                     },
                 ],
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:20:6: 20:26 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Alone,
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:22:5: 22:6 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "allow",
-                        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                        span: $DIR/cfg-eval.rs:22:31: 22:36 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [],
-                        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                        span: $DIR/cfg-eval.rs:22:36: 22:38 (#0),
                     },
                 ],
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:22:5: 22:6 (#0),
             },
             Ident {
                 ident: "field_true",
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:23:5: 23:15 (#0),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:23:15: 23:16 (#0),
             },
             Ident {
                 ident: "u8",
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:23:17: 23:19 (#0),
             },
             Punct {
                 ch: ',',
                 spacing: Alone,
-                span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+                span: $DIR/cfg-eval.rs:23:19: 23:20 (#0),
             },
         ],
-        span: $DIR/cfg-eval.rs:17:1: 24:2 (#0),
+        span: $DIR/cfg-eval.rs:17:11: 24:2 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): #[rustc_dummy] (#[cfg(all())] 1,)
@@ -88,17 +88,17 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+        span: $DIR/cfg-eval.rs:35:39: 35:40 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "rustc_dummy",
-                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                span: $DIR/cfg-eval.rs:35:62: 35:73 (#0),
             },
         ],
-        span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+        span: $DIR/cfg-eval.rs:35:39: 35:40 (#0),
     },
     Group {
         delimiter: Parenthesis,
@@ -106,43 +106,43 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Alone,
-                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                span: $DIR/cfg-eval.rs:36:23: 36:24 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "cfg",
-                        span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                        span: $DIR/cfg-eval.rs:36:25: 36:28 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "all",
-                                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                                span: $DIR/cfg-eval.rs:36:29: 36:32 (#0),
                             },
                             Group {
                                 delimiter: Parenthesis,
                                 stream: TokenStream [],
-                                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                                span: $DIR/cfg-eval.rs:36:32: 36:42 (#0),
                             },
                         ],
-                        span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                        span: $DIR/cfg-eval.rs:36:28: 36:43 (#0),
                     },
                 ],
-                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                span: $DIR/cfg-eval.rs:36:24: 36:44 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "1",
                 suffix: None,
-                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                span: $DIR/cfg-eval.rs:36:45: 36:46 (#0),
             },
             Punct {
                 ch: ',',
                 spacing: Alone,
-                span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
+                span: $DIR/cfg-eval.rs:36:46: 36:47 (#0),
             },
         ],
         span: $DIR/cfg-eval.rs:36:5: 36:48 (#0),
diff --git a/src/test/ui/proc-macro/expand-to-derive.stdout b/src/test/ui/proc-macro/expand-to-derive.stdout
index 7eb68643342..a6437982a37 100644
--- a/src/test/ui/proc-macro/expand-to-derive.stdout
+++ b/src/test/ui/proc-macro/expand-to-derive.stdout
@@ -1,40 +1,40 @@
 PRINT-DERIVE INPUT (DISPLAY): struct Foo
 {
     field :
-    [bool ; { #[rustc_dummy] struct Inner { other_inner_field : u8, } 0 }],
+    [bool ; { #[rustc_dummy] struct Inner { other_inner_field : u8, } 0 }]
 }
 PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+        span: $DIR/expand-to-derive.rs:16:9: 16:15 (#4),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+        span: $DIR/expand-to-derive.rs:16:16: 16:19 (#4),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "field",
-                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                span: $DIR/expand-to-derive.rs:18:13: 18:18 (#4),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                span: $DIR/expand-to-derive.rs:18:18: 18:19 (#4),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "bool",
-                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                        span: $DIR/expand-to-derive.rs:18:21: 18:25 (#4),
                     },
                     Punct {
                         ch: ';',
                         spacing: Alone,
-                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                        span: $DIR/expand-to-derive.rs:18:25: 18:26 (#4),
                     },
                     Group {
                         delimiter: Brace,
@@ -42,68 +42,63 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
                             Punct {
                                 ch: '#',
                                 spacing: Alone,
-                                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                span: $DIR/expand-to-derive.rs:27:5: 27:6 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "rustc_dummy",
-                                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                        span: $DIR/expand-to-derive.rs:27:28: 27:39 (#0),
                                     },
                                 ],
-                                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                span: $DIR/expand-to-derive.rs:27:5: 27:6 (#0),
                             },
                             Ident {
                                 ident: "struct",
-                                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                span: $DIR/expand-to-derive.rs:28:5: 28:11 (#0),
                             },
                             Ident {
                                 ident: "Inner",
-                                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                span: $DIR/expand-to-derive.rs:28:12: 28:17 (#0),
                             },
                             Group {
                                 delimiter: Brace,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "other_inner_field",
-                                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                        span: $DIR/expand-to-derive.rs:30:9: 30:26 (#0),
                                     },
                                     Punct {
                                         ch: ':',
                                         spacing: Alone,
-                                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                        span: $DIR/expand-to-derive.rs:30:26: 30:27 (#0),
                                     },
                                     Ident {
                                         ident: "u8",
-                                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                        span: $DIR/expand-to-derive.rs:30:28: 30:30 (#0),
                                     },
                                     Punct {
                                         ch: ',',
                                         spacing: Alone,
-                                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                        span: $DIR/expand-to-derive.rs:30:30: 30:31 (#0),
                                     },
                                 ],
-                                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                span: $DIR/expand-to-derive.rs:28:18: 31:6 (#0),
                             },
                             Literal {
                                 kind: Integer,
                                 symbol: "0",
                                 suffix: None,
-                                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                                span: $DIR/expand-to-derive.rs:20:17: 20:18 (#4),
                             },
                         ],
-                        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                        span: $DIR/expand-to-derive.rs:18:27: 21:14 (#4),
                     },
                 ],
-                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
-            },
-            Punct {
-                ch: ',',
-                spacing: Alone,
-                span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+                span: $DIR/expand-to-derive.rs:18:20: 21:15 (#4),
             },
         ],
-        span: $DIR/expand-to-derive.rs:16:9: 22:10 (#4),
+        span: $DIR/expand-to-derive.rs:16:20: 22:10 (#4),
     },
 ]
diff --git a/src/test/ui/proc-macro/inner-attrs.rs b/src/test/ui/proc-macro/inner-attrs.rs
index 1ff97dbf3c7..14ec57ad626 100644
--- a/src/test/ui/proc-macro/inner-attrs.rs
+++ b/src/test/ui/proc-macro/inner-attrs.rs
@@ -37,7 +37,7 @@ struct MyDerivePrint {
             #![cfg_attr(not(FALSE), rustc_dummy(first))]
             #![cfg_attr(not(FALSE), rustc_dummy(second))]
             _ => {
-                #![cfg_attr(not(FALSE), rustc_dummy(second))]
+                #![cfg_attr(not(FALSE), rustc_dummy(third))]
                 true
             }
         };
diff --git a/src/test/ui/proc-macro/inner-attrs.stdout b/src/test/ui/proc-macro/inner-attrs.stdout
index b44822fb557..2f442e83002 100644
--- a/src/test/ui/proc-macro/inner-attrs.stdout
+++ b/src/test/ui/proc-macro/inner-attrs.stdout
@@ -11,40 +11,40 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:16:1: 16:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "print_target_and_args",
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:16:3: 16:24 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [
                     Ident {
                         ident: "second",
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:16:25: 16:31 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:16:24: 16:32 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:16:2: 16:33 (#0),
     },
     Ident {
         ident: "fn",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:1: 17:3 (#0),
     },
     Ident {
         ident: "foo",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:4: 17:7 (#0),
     },
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:7: 17:9 (#0),
     },
     Group {
         delimiter: Brace,
@@ -52,72 +52,72 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:18:5: 18:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:18:6: 18:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:18:8: 18:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "third",
-                                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                                span: $DIR/inner-attrs.rs:18:30: 18:35 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:18:29: 18:36 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:18:7: 18:37 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:5: 19:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:6: 19:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:19:8: 19:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "fourth",
-                                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                                span: $DIR/inner-attrs.rs:19:30: 19:36 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:19:29: 19:37 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:7: 19:38 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:10: 20:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): second
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "second",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:16:25: 16:31 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): fn foo()
@@ -125,16 +125,16 @@ PRINT-ATTR INPUT (DISPLAY): fn foo()
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "fn",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:1: 17:3 (#0),
     },
     Ident {
         ident: "foo",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:4: 17:7 (#0),
     },
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:7: 17:9 (#0),
     },
     Group {
         delimiter: Brace,
@@ -142,88 +142,88 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:18:5: 18:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:18:6: 18:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:18:8: 18:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "third",
-                                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                                span: $DIR/inner-attrs.rs:18:30: 18:35 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:18:29: 18:36 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:18:7: 18:37 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:5: 19:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:6: 19:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:19:8: 19:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "fourth",
-                                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                                span: $DIR/inner-attrs.rs:19:30: 19:36 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:19:29: 19:37 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:7: 19:38 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:10: 20:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): third
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "third",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:18:30: 18:35 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): fn foo() { # ! [print_target_and_args(fourth)] }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "fn",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:1: 17:3 (#0),
     },
     Ident {
         ident: "foo",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:4: 17:7 (#0),
     },
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:7: 17:9 (#0),
     },
     Group {
         delimiter: Brace,
@@ -231,63 +231,63 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:5: 19:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:6: 19:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:19:8: 19:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "fourth",
-                                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                                span: $DIR/inner-attrs.rs:19:30: 19:36 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                        span: $DIR/inner-attrs.rs:19:29: 19:37 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+                span: $DIR/inner-attrs.rs:19:7: 19:38 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:10: 20:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): fourth
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "fourth",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:19:30: 19:36 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): fn foo() { }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "fn",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:1: 17:3 (#0),
     },
     Ident {
         ident: "foo",
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:4: 17:7 (#0),
     },
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:7: 17:9 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:17:1: 20:2 (#0),
+        span: $DIR/inner-attrs.rs:17:10: 20:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): mod_first
@@ -306,35 +306,35 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:23:1: 23:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "print_target_and_args",
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:23:3: 23:24 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [
                     Ident {
                         ident: "mod_second",
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:23:25: 23:35 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:23:24: 23:36 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:23:2: 23:37 (#0),
     },
     Ident {
         ident: "mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:1: 24:4 (#0),
     },
     Ident {
         ident: "inline_mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:5: 24:15 (#0),
     },
     Group {
         delimiter: Brace,
@@ -342,72 +342,72 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:25:5: 25:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:25:6: 25:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:25:8: 25:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "mod_third",
-                                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                                span: $DIR/inner-attrs.rs:25:30: 25:39 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:25:29: 25:40 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:25:7: 25:41 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:5: 26:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:6: 26:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:26:8: 26:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "mod_fourth",
-                                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                                span: $DIR/inner-attrs.rs:26:30: 26:40 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:26:29: 26:41 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:7: 26:42 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:16: 27:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): mod_second
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "mod_second",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:23:25: 23:35 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): mod inline_mod
@@ -418,11 +418,11 @@ PRINT-ATTR INPUT (DISPLAY): mod inline_mod
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:1: 24:4 (#0),
     },
     Ident {
         ident: "inline_mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:5: 24:15 (#0),
     },
     Group {
         delimiter: Brace,
@@ -430,83 +430,83 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:25:5: 25:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:25:6: 25:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:25:8: 25:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "mod_third",
-                                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                                span: $DIR/inner-attrs.rs:25:30: 25:39 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:25:29: 25:40 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:25:7: 25:41 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:5: 26:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:6: 26:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:26:8: 26:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "mod_fourth",
-                                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                                span: $DIR/inner-attrs.rs:26:30: 26:40 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:26:29: 26:41 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:7: 26:42 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:16: 27:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): mod_third
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "mod_third",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:25:30: 25:39 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): mod inline_mod { # ! [print_target_and_args(mod_fourth)] }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:1: 24:4 (#0),
     },
     Ident {
         ident: "inline_mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:5: 24:15 (#0),
     },
     Group {
         delimiter: Brace,
@@ -514,58 +514,58 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:5: 26:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:6: 26:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:26:8: 26:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "mod_fourth",
-                                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                                span: $DIR/inner-attrs.rs:26:30: 26:40 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                        span: $DIR/inner-attrs.rs:26:29: 26:41 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+                span: $DIR/inner-attrs.rs:26:7: 26:42 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:16: 27:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): mod_fourth
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "mod_fourth",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:26:30: 26:40 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): mod inline_mod { }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:1: 24:4 (#0),
     },
     Ident {
         ident: "inline_mod",
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:5: 24:15 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:24:1: 27:2 (#0),
+        span: $DIR/inner-attrs.rs:24:16: 27:2 (#0),
     },
 ]
 PRINT-DERIVE INPUT (DISPLAY): struct MyDerivePrint
@@ -574,168 +574,195 @@ PRINT-DERIVE INPUT (DISPLAY): struct MyDerivePrint
     [u8 ;
      {
          match true
-         { # ! [rustc_dummy(first)] # ! [rustc_dummy(second)] _ => { true } }
-         ; 0
-     }],
+         {
+             # ! [rustc_dummy(first)] # ! [rustc_dummy(second)] _ =>
+             { # ! [rustc_dummy(third)] true }
+         } ; 0
+     }]
 }
 PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+        span: $DIR/inner-attrs.rs:34:1: 34:7 (#0),
     },
     Ident {
         ident: "MyDerivePrint",
-        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+        span: $DIR/inner-attrs.rs:34:8: 34:21 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "field",
-                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                span: $DIR/inner-attrs.rs:35:5: 35:10 (#0),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                span: $DIR/inner-attrs.rs:35:10: 35:11 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "u8",
-                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                        span: $DIR/inner-attrs.rs:35:13: 35:15 (#0),
                     },
                     Punct {
                         ch: ';',
                         spacing: Alone,
-                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                        span: $DIR/inner-attrs.rs:35:15: 35:16 (#0),
                     },
                     Group {
                         delimiter: Brace,
                         stream: TokenStream [
                             Ident {
                                 ident: "match",
-                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                span: $DIR/inner-attrs.rs:36:9: 36:14 (#0),
                             },
                             Ident {
                                 ident: "true",
-                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                span: $DIR/inner-attrs.rs:36:15: 36:19 (#0),
                             },
                             Group {
                                 delimiter: Brace,
                                 stream: TokenStream [
                                     Punct {
                                         ch: '#',
-                                        spacing: Joint,
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        spacing: Alone,
+                                        span: $DIR/inner-attrs.rs:37:13: 37:14 (#0),
                                     },
                                     Punct {
                                         ch: '!',
                                         spacing: Alone,
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:37:14: 37:15 (#0),
                                     },
                                     Group {
                                         delimiter: Bracket,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "rustc_dummy",
-                                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                span: $DIR/inner-attrs.rs:37:37: 37:48 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "first",
-                                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                        span: $DIR/inner-attrs.rs:37:49: 37:54 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                span: $DIR/inner-attrs.rs:37:48: 37:55 (#0),
                                             },
                                         ],
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:37:13: 37:14 (#0),
                                     },
                                     Punct {
                                         ch: '#',
-                                        spacing: Joint,
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        spacing: Alone,
+                                        span: $DIR/inner-attrs.rs:38:13: 38:14 (#0),
                                     },
                                     Punct {
                                         ch: '!',
                                         spacing: Alone,
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:38:14: 38:15 (#0),
                                     },
                                     Group {
                                         delimiter: Bracket,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "rustc_dummy",
-                                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                span: $DIR/inner-attrs.rs:38:37: 38:48 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "second",
-                                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                        span: $DIR/inner-attrs.rs:38:49: 38:55 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                span: $DIR/inner-attrs.rs:38:48: 38:56 (#0),
                                             },
                                         ],
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:38:13: 38:14 (#0),
                                     },
                                     Ident {
                                         ident: "_",
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:39:13: 39:14 (#0),
                                     },
                                     Punct {
                                         ch: '=',
                                         spacing: Joint,
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:39:15: 39:17 (#0),
                                     },
                                     Punct {
                                         ch: '>',
                                         spacing: Alone,
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:39:15: 39:17 (#0),
                                     },
                                     Group {
                                         delimiter: Brace,
                                         stream: TokenStream [
+                                            Punct {
+                                                ch: '#',
+                                                spacing: Alone,
+                                                span: $DIR/inner-attrs.rs:40:17: 40:18 (#0),
+                                            },
+                                            Punct {
+                                                ch: '!',
+                                                spacing: Alone,
+                                                span: $DIR/inner-attrs.rs:40:18: 40:19 (#0),
+                                            },
+                                            Group {
+                                                delimiter: Bracket,
+                                                stream: TokenStream [
+                                                    Ident {
+                                                        ident: "rustc_dummy",
+                                                        span: $DIR/inner-attrs.rs:40:41: 40:52 (#0),
+                                                    },
+                                                    Group {
+                                                        delimiter: Parenthesis,
+                                                        stream: TokenStream [
+                                                            Ident {
+                                                                ident: "third",
+                                                                span: $DIR/inner-attrs.rs:40:53: 40:58 (#0),
+                                                            },
+                                                        ],
+                                                        span: $DIR/inner-attrs.rs:40:52: 40:59 (#0),
+                                                    },
+                                                ],
+                                                span: $DIR/inner-attrs.rs:40:17: 40:18 (#0),
+                                            },
                                             Ident {
                                                 ident: "true",
-                                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                                span: $DIR/inner-attrs.rs:41:17: 41:21 (#0),
                                             },
                                         ],
-                                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                        span: $DIR/inner-attrs.rs:39:18: 42:14 (#0),
                                     },
                                 ],
-                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                span: $DIR/inner-attrs.rs:36:20: 43:10 (#0),
                             },
                             Punct {
                                 ch: ';',
                                 spacing: Alone,
-                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                span: $DIR/inner-attrs.rs:43:10: 43:11 (#0),
                             },
                             Literal {
                                 kind: Integer,
                                 symbol: "0",
                                 suffix: None,
-                                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                                span: $DIR/inner-attrs.rs:44:9: 44:10 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                        span: $DIR/inner-attrs.rs:35:17: 45:6 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
-            },
-            Punct {
-                ch: ',',
-                spacing: Alone,
-                span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+                span: $DIR/inner-attrs.rs:35:12: 45:7 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:34:1: 46:2 (#0),
+        span: $DIR/inner-attrs.rs:34:22: 46:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): tuple_attrs
@@ -745,51 +772,11 @@ PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
         span: $DIR/inner-attrs.rs:52:29: 52:40 (#0),
     },
 ]
-PRINT-ATTR INPUT (DISPLAY): (# ! [cfg_attr(FALSE, rustc_dummy)] 3, 4,
- { # ! [cfg_attr(not(FALSE), rustc_dummy(innermost))] 5 }) ;
+PRINT-ATTR INPUT (DISPLAY): (3, 4, { # ! [cfg_attr(not(FALSE), rustc_dummy(innermost))] 5 }) ;
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [
-            Punct {
-                ch: '#',
-                spacing: Joint,
-                span: $DIR/inner-attrs.rs:53:9: 53:10 (#0),
-            },
-            Punct {
-                ch: '!',
-                spacing: Alone,
-                span: $DIR/inner-attrs.rs:53:10: 53:11 (#0),
-            },
-            Group {
-                delimiter: Bracket,
-                stream: TokenStream [
-                    Ident {
-                        ident: "cfg_attr",
-                        span: $DIR/inner-attrs.rs:53:12: 53:20 (#0),
-                    },
-                    Group {
-                        delimiter: Parenthesis,
-                        stream: TokenStream [
-                            Ident {
-                                ident: "FALSE",
-                                span: $DIR/inner-attrs.rs:53:21: 53:26 (#0),
-                            },
-                            Punct {
-                                ch: ',',
-                                spacing: Alone,
-                                span: $DIR/inner-attrs.rs:53:26: 53:27 (#0),
-                            },
-                            Ident {
-                                ident: "rustc_dummy",
-                                span: $DIR/inner-attrs.rs:53:28: 53:39 (#0),
-                            },
-                        ],
-                        span: $DIR/inner-attrs.rs:53:20: 53:40 (#0),
-                    },
-                ],
-                span: $DIR/inner-attrs.rs:53:11: 53:41 (#0),
-            },
             Literal {
                 kind: Integer,
                 symbol: "3",
@@ -907,55 +894,55 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                span: $DIR/inner-attrs.rs:61:9: 61:10 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                span: $DIR/inner-attrs.rs:61:10: 61:11 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "rustc_dummy",
-                        span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                        span: $DIR/inner-attrs.rs:61:12: 61:23 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "inner",
-                                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                                span: $DIR/inner-attrs.rs:61:24: 61:29 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                        span: $DIR/inner-attrs.rs:61:23: 61:30 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                span: $DIR/inner-attrs.rs:61:11: 61:31 (#0),
             },
             Ident {
                 ident: "true",
-                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                span: $DIR/inner-attrs.rs:62:9: 62:13 (#0),
             },
             Punct {
                 ch: ';',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                span: $DIR/inner-attrs.rs:62:13: 62:14 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "0",
                 suffix: None,
-                span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+                span: $DIR/inner-attrs.rs:62:15: 62:16 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+        span: $DIR/inner-attrs.rs:60:43: 63:6 (#0),
     },
     Punct {
         ch: ';',
         spacing: Alone,
-        span: $DIR/inner-attrs.rs:60:43: 63:7 (#0),
+        span: $DIR/inner-attrs.rs:63:6: 63:7 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): tuple_attrs
@@ -965,51 +952,11 @@ PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
         span: $DIR/inner-attrs.rs:65:29: 65:40 (#0),
     },
 ]
-PRINT-ATTR INPUT (DISPLAY): (# ! [cfg_attr(FALSE, rustc_dummy)] 3, 4,
- { # ! [cfg_attr(not(FALSE), rustc_dummy(innermost))] 5 }) ;
+PRINT-ATTR INPUT (DISPLAY): (3, 4, { # ! [cfg_attr(not(FALSE), rustc_dummy(innermost))] 5 }) ;
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [
-            Punct {
-                ch: '#',
-                spacing: Joint,
-                span: $DIR/inner-attrs.rs:66:9: 66:10 (#0),
-            },
-            Punct {
-                ch: '!',
-                spacing: Alone,
-                span: $DIR/inner-attrs.rs:66:10: 66:11 (#0),
-            },
-            Group {
-                delimiter: Bracket,
-                stream: TokenStream [
-                    Ident {
-                        ident: "cfg_attr",
-                        span: $DIR/inner-attrs.rs:66:12: 66:20 (#0),
-                    },
-                    Group {
-                        delimiter: Parenthesis,
-                        stream: TokenStream [
-                            Ident {
-                                ident: "FALSE",
-                                span: $DIR/inner-attrs.rs:66:21: 66:26 (#0),
-                            },
-                            Punct {
-                                ch: ',',
-                                spacing: Alone,
-                                span: $DIR/inner-attrs.rs:66:26: 66:27 (#0),
-                            },
-                            Ident {
-                                ident: "rustc_dummy",
-                                span: $DIR/inner-attrs.rs:66:28: 66:39 (#0),
-                            },
-                        ],
-                        span: $DIR/inner-attrs.rs:66:20: 66:40 (#0),
-                    },
-                ],
-                span: $DIR/inner-attrs.rs:66:11: 66:41 (#0),
-            },
             Literal {
                 kind: Integer,
                 symbol: "3",
@@ -1127,55 +1074,55 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                span: $DIR/inner-attrs.rs:74:9: 74:10 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                span: $DIR/inner-attrs.rs:74:10: 74:11 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "rustc_dummy",
-                        span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                        span: $DIR/inner-attrs.rs:74:12: 74:23 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "inner",
-                                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                                span: $DIR/inner-attrs.rs:74:24: 74:29 (#0),
                             },
                         ],
-                        span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                        span: $DIR/inner-attrs.rs:74:23: 74:30 (#0),
                     },
                 ],
-                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                span: $DIR/inner-attrs.rs:74:11: 74:31 (#0),
             },
             Ident {
                 ident: "true",
-                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                span: $DIR/inner-attrs.rs:75:9: 75:13 (#0),
             },
             Punct {
                 ch: ';',
                 spacing: Alone,
-                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                span: $DIR/inner-attrs.rs:75:13: 75:14 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "0",
                 suffix: None,
-                span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+                span: $DIR/inner-attrs.rs:75:15: 75:16 (#0),
             },
         ],
-        span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+        span: $DIR/inner-attrs.rs:73:43: 76:6 (#0),
     },
     Punct {
         ch: ';',
         spacing: Alone,
-        span: $DIR/inner-attrs.rs:73:43: 76:7 (#0),
+        span: $DIR/inner-attrs.rs:76:6: 76:7 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): tenth
@@ -1189,20 +1136,20 @@ PRINT-ATTR INPUT (DISPLAY): fn weird_extern() { }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "fn",
-        span: $DIR/inner-attrs.rs:111:5: 113:6 (#0),
+        span: $DIR/inner-attrs.rs:111:5: 111:7 (#0),
     },
     Ident {
         ident: "weird_extern",
-        span: $DIR/inner-attrs.rs:111:5: 113:6 (#0),
+        span: $DIR/inner-attrs.rs:111:8: 111:20 (#0),
     },
     Group {
         delimiter: Parenthesis,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:111:5: 113:6 (#0),
+        span: $DIR/inner-attrs.rs:111:20: 111:22 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/inner-attrs.rs:111:5: 113:6 (#0),
+        span: $DIR/inner-attrs.rs:111:23: 113:6 (#0),
     },
 ]
diff --git a/src/test/ui/proc-macro/issue-75930-derive-cfg.stdout b/src/test/ui/proc-macro/issue-75930-derive-cfg.stdout
index 7cbc0c669a5..d7adc5331cd 100644
--- a/src/test/ui/proc-macro/issue-75930-derive-cfg.stdout
+++ b/src/test/ui/proc-macro/issue-75930-derive-cfg.stdout
@@ -1279,152 +1279,152 @@ PRINT-DERIVE INPUT (DISPLAY): #[print_helper(a)] #[allow(dead_code)] #[print_hel
     [u8 ;
      {
          #[cfg(not(FALSE))] struct Inner ; match true
-         { #[allow(warnings)] false => { } _ => { } } ; #[print_helper(c)]
+         { #[allow(warnings)] false => { }, _ => { } } ; #[print_helper(c)]
          #[cfg(not(FALSE))] fn kept_fn()
          { # ! [cfg(not(FALSE))] let my_val = true ; } enum TupleEnum
-         { Foo(#[cfg(not(FALSE))] i32, u8), } struct
+         { Foo(#[cfg(not(FALSE))] i32, u8) } struct
          TupleStruct(#[cfg(not(FALSE))] i32, u8) ; 0
-     }], #[print_helper(d)] fourth : B,
+     }], #[print_helper(d)] fourth : B
 }
 PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:19:1: 19:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "print_helper",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:19:3: 19:15 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [
                     Ident {
                         ident: "a",
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:19:16: 19:17 (#0),
                     },
                 ],
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:19:15: 19:18 (#0),
             },
         ],
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:19:2: 19:19 (#0),
     },
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:21:1: 21:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "allow",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:21:24: 21:29 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [
                     Ident {
                         ident: "dead_code",
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:21:30: 21:39 (#0),
                     },
                 ],
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:21:29: 21:40 (#0),
             },
         ],
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:21:1: 21:2 (#0),
     },
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:24:1: 24:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "print_helper",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:24:3: 24:15 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [
                     Ident {
                         ident: "b",
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:24:16: 24:17 (#0),
                     },
                 ],
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:24:15: 24:18 (#0),
             },
         ],
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:24:2: 24:19 (#0),
     },
     Ident {
         ident: "struct",
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:25:1: 25:7 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:25:8: 25:11 (#0),
     },
     Punct {
         ch: '<',
-        spacing: Alone,
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        spacing: Joint,
+        span: $DIR/issue-75930-derive-cfg.rs:25:11: 25:12 (#0),
     },
     Ident {
         ident: "B",
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:25:29: 25:30 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:25:30: 25:31 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "second",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:27:40: 27:46 (#0),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:27:46: 27:47 (#0),
             },
             Ident {
                 ident: "bool",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:27:48: 27:52 (#0),
             },
             Punct {
                 ch: ',',
                 spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:27:52: 27:53 (#0),
             },
             Ident {
                 ident: "third",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:28:5: 28:10 (#0),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:28:10: 28:11 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "u8",
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:28:13: 28:15 (#0),
                     },
                     Punct {
                         ch: ';',
                         spacing: Alone,
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:28:15: 28:16 (#0),
                     },
                     Group {
                         delimiter: Brace,
@@ -1432,58 +1432,58 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
                             Punct {
                                 ch: '#',
                                 spacing: Alone,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:30:9: 30:10 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "cfg",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:30:11: 30:14 (#0),
                                     },
                                     Group {
                                         delimiter: Parenthesis,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "not",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:30:15: 30:18 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "FALSE",
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:30:19: 30:24 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:30:18: 30:25 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:30:14: 30:26 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:30:10: 30:27 (#0),
                             },
                             Ident {
                                 ident: "struct",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:30:28: 30:34 (#0),
                             },
                             Ident {
                                 ident: "Inner",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:30:35: 30:40 (#0),
                             },
                             Punct {
                                 ch: ';',
                                 spacing: Alone,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:30:40: 30:41 (#0),
                             },
                             Ident {
                                 ident: "match",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:32:9: 32:14 (#0),
                             },
                             Ident {
                                 ident: "true",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:32:15: 32:19 (#0),
                             },
                             Group {
                                 delimiter: Brace,
@@ -1491,146 +1491,151 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
                                     Punct {
                                         ch: '#',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:13: 34:14 (#0),
                                     },
                                     Group {
                                         delimiter: Bracket,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "allow",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:34:36: 34:41 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "warnings",
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:34:42: 34:50 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:34:41: 34:51 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:13: 34:14 (#0),
                                     },
                                     Ident {
                                         ident: "false",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:54: 34:59 (#0),
                                     },
                                     Punct {
                                         ch: '=',
                                         spacing: Joint,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:60: 34:62 (#0),
                                     },
                                     Punct {
                                         ch: '>',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:60: 34:62 (#0),
                                     },
                                     Group {
                                         delimiter: Brace,
                                         stream: TokenStream [],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:63: 34:65 (#0),
+                                    },
+                                    Punct {
+                                        ch: ',',
+                                        spacing: Alone,
+                                        span: $DIR/issue-75930-derive-cfg.rs:34:65: 34:66 (#0),
                                     },
                                     Ident {
                                         ident: "_",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:35:13: 35:14 (#0),
                                     },
                                     Punct {
                                         ch: '=',
                                         spacing: Joint,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:35:15: 35:17 (#0),
                                     },
                                     Punct {
                                         ch: '>',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:35:15: 35:17 (#0),
                                     },
                                     Group {
                                         delimiter: Brace,
                                         stream: TokenStream [],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:35:18: 35:20 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:32:20: 36:10 (#0),
                             },
                             Punct {
                                 ch: ';',
                                 spacing: Alone,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:36:10: 36:11 (#0),
                             },
                             Punct {
                                 ch: '#',
                                 spacing: Alone,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:9: 43:10 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "print_helper",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:43:11: 43:23 (#0),
                                     },
                                     Group {
                                         delimiter: Parenthesis,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "c",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:43:24: 43:25 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:43:23: 43:26 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:10: 43:27 (#0),
                             },
                             Punct {
                                 ch: '#',
                                 spacing: Alone,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:28: 43:29 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "cfg",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:43:30: 43:33 (#0),
                                     },
                                     Group {
                                         delimiter: Parenthesis,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "not",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:43:34: 43:37 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "FALSE",
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:43:38: 43:43 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:43:37: 43:44 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:43:33: 43:45 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:29: 43:46 (#0),
                             },
                             Ident {
                                 ident: "fn",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:47: 43:49 (#0),
                             },
                             Ident {
                                 ident: "kept_fn",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:50: 43:57 (#0),
                             },
                             Group {
                                 delimiter: Parenthesis,
                                 stream: TokenStream [],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:57: 43:59 (#0),
                             },
                             Group {
                                 delimiter: Brace,
@@ -1638,82 +1643,82 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
                                     Punct {
                                         ch: '#',
                                         spacing: Joint,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:44:13: 44:14 (#0),
                                     },
                                     Punct {
                                         ch: '!',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:44:14: 44:15 (#0),
                                     },
                                     Group {
                                         delimiter: Bracket,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "cfg",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:44:16: 44:19 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "not",
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:44:20: 44:23 (#0),
                                                     },
                                                     Group {
                                                         delimiter: Parenthesis,
                                                         stream: TokenStream [
                                                             Ident {
                                                                 ident: "FALSE",
-                                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                                span: $DIR/issue-75930-derive-cfg.rs:44:24: 44:29 (#0),
                                                             },
                                                         ],
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:44:23: 44:30 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:44:19: 44:31 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:44:15: 44:32 (#0),
                                     },
                                     Ident {
                                         ident: "let",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:45:13: 45:16 (#0),
                                     },
                                     Ident {
                                         ident: "my_val",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:45:17: 45:23 (#0),
                                     },
                                     Punct {
                                         ch: '=',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:45:24: 45:25 (#0),
                                     },
                                     Ident {
                                         ident: "true",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:45:26: 45:30 (#0),
                                     },
                                     Punct {
                                         ch: ';',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:45:30: 45:31 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:43:60: 46:10 (#0),
                             },
                             Ident {
                                 ident: "enum",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:48:9: 48:13 (#0),
                             },
                             Ident {
                                 ident: "TupleEnum",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:48:14: 48:23 (#0),
                             },
                             Group {
                                 delimiter: Brace,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "Foo",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:49:13: 49:16 (#0),
                                     },
                                     Group {
                                         delimiter: Parenthesis,
@@ -1721,69 +1726,64 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
                                             Punct {
                                                 ch: '#',
                                                 spacing: Alone,
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:52:17: 52:18 (#0),
                                             },
                                             Group {
                                                 delimiter: Bracket,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "cfg",
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:52:19: 52:22 (#0),
                                                     },
                                                     Group {
                                                         delimiter: Parenthesis,
                                                         stream: TokenStream [
                                                             Ident {
                                                                 ident: "not",
-                                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                                span: $DIR/issue-75930-derive-cfg.rs:52:23: 52:26 (#0),
                                                             },
                                                             Group {
                                                                 delimiter: Parenthesis,
                                                                 stream: TokenStream [
                                                                     Ident {
                                                                         ident: "FALSE",
-                                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                                        span: $DIR/issue-75930-derive-cfg.rs:52:27: 52:32 (#0),
                                                                     },
                                                                 ],
-                                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                                span: $DIR/issue-75930-derive-cfg.rs:52:26: 52:33 (#0),
                                                             },
                                                         ],
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:52:22: 52:34 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:52:18: 52:35 (#0),
                                             },
                                             Ident {
                                                 ident: "i32",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:52:36: 52:39 (#0),
                                             },
                                             Punct {
                                                 ch: ',',
                                                 spacing: Alone,
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:52:39: 52:40 (#0),
                                             },
                                             Ident {
                                                 ident: "u8",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:53:39: 53:41 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
-                                    },
-                                    Punct {
-                                        ch: ',',
-                                        spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:49:16: 54:14 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:48:24: 55:10 (#0),
                             },
                             Ident {
                                 ident: "struct",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:57:9: 57:15 (#0),
                             },
                             Ident {
                                 ident: "TupleStruct",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:57:16: 57:27 (#0),
                             },
                             Group {
                                 delimiter: Parenthesis,
@@ -1791,120 +1791,115 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [
                                     Punct {
                                         ch: '#',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:59:13: 59:14 (#0),
                                     },
                                     Group {
                                         delimiter: Bracket,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "cfg",
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:59:15: 59:18 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "not",
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:59:19: 59:22 (#0),
                                                     },
                                                     Group {
                                                         delimiter: Parenthesis,
                                                         stream: TokenStream [
                                                             Ident {
                                                                 ident: "FALSE",
-                                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                                span: $DIR/issue-75930-derive-cfg.rs:59:23: 59:28 (#0),
                                                             },
                                                         ],
-                                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                        span: $DIR/issue-75930-derive-cfg.rs:59:22: 59:29 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                                span: $DIR/issue-75930-derive-cfg.rs:59:18: 59:30 (#0),
                                             },
                                         ],
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:59:14: 59:31 (#0),
                                     },
                                     Ident {
                                         ident: "i32",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:59:32: 59:35 (#0),
                                     },
                                     Punct {
                                         ch: ',',
                                         spacing: Alone,
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:59:35: 59:36 (#0),
                                     },
                                     Ident {
                                         ident: "u8",
-                                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                        span: $DIR/issue-75930-derive-cfg.rs:61:13: 61:15 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:57:27: 62:10 (#0),
                             },
                             Punct {
                                 ch: ';',
                                 spacing: Alone,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:62:10: 62:11 (#0),
                             },
                             Literal {
                                 kind: Integer,
                                 symbol: "0",
                                 suffix: None,
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:68:9: 68:10 (#0),
                             },
                         ],
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:28:17: 69:6 (#0),
                     },
                 ],
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:28:12: 69:7 (#0),
             },
             Punct {
                 ch: ',',
                 spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:69:7: 69:8 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:70:5: 70:6 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_helper",
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:70:7: 70:19 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "d",
-                                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                                span: $DIR/issue-75930-derive-cfg.rs:70:20: 70:21 (#0),
                             },
                         ],
-                        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                        span: $DIR/issue-75930-derive-cfg.rs:70:19: 70:22 (#0),
                     },
                 ],
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:70:6: 70:23 (#0),
             },
             Ident {
                 ident: "fourth",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:71:5: 71:11 (#0),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:71:11: 71:12 (#0),
             },
             Ident {
                 ident: "B",
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
-            },
-            Punct {
-                ch: ',',
-                spacing: Alone,
-                span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+                span: $DIR/issue-75930-derive-cfg.rs:71:13: 71:14 (#0),
             },
         ],
-        span: $DIR/issue-75930-derive-cfg.rs:25:1: 72:2 (#0),
+        span: $DIR/issue-75930-derive-cfg.rs:25:32: 72:2 (#0),
     },
 ]
diff --git a/src/test/ui/proc-macro/issue-78675-captured-inner-attrs.stdout b/src/test/ui/proc-macro/issue-78675-captured-inner-attrs.stdout
index 9b467a5970b..607c2954c7c 100644
--- a/src/test/ui/proc-macro/issue-78675-captured-inner-attrs.stdout
+++ b/src/test/ui/proc-macro/issue-78675-captured-inner-attrs.stdout
@@ -35,48 +35,48 @@ PRINT-BANG INPUT (DEBUG): TokenStream [
                 stream: TokenStream [
                     Ident {
                         ident: "mod",
-                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 27:8 (#0),
                     },
                     Ident {
                         ident: "bar",
-                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:9: 27:12 (#0),
                     },
                     Group {
                         delimiter: Brace,
                         stream: TokenStream [
                             Punct {
                                 ch: '#',
-                                spacing: Joint,
-                                span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                                spacing: Alone,
+                                span: $DIR/issue-78675-captured-inner-attrs.rs:28:9: 28:16 (#0),
                             },
                             Punct {
                                 ch: '!',
                                 spacing: Alone,
-                                span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                                span: $DIR/issue-78675-captured-inner-attrs.rs:28:9: 28:16 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "doc",
-                                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                                        span: $DIR/issue-78675-captured-inner-attrs.rs:28:9: 28:16 (#0),
                                     },
                                     Punct {
                                         ch: '=',
                                         spacing: Alone,
-                                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                                        span: $DIR/issue-78675-captured-inner-attrs.rs:28:9: 28:16 (#0),
                                     },
                                     Literal {
                                         kind: StrRaw(0),
                                         symbol: " Foo",
                                         suffix: None,
-                                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                                        span: $DIR/issue-78675-captured-inner-attrs.rs:28:9: 28:16 (#0),
                                     },
                                 ],
-                                span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                                span: $DIR/issue-78675-captured-inner-attrs.rs:28:9: 28:16 (#0),
                             },
                         ],
-                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:5: 29:6 (#0),
+                        span: $DIR/issue-78675-captured-inner-attrs.rs:27:13: 29:6 (#0),
                     },
                 ],
                 span: $DIR/issue-78675-captured-inner-attrs.rs:22:13: 22:18 (#4),
diff --git a/src/test/ui/proc-macro/macro-rules-derive-cfg.stdout b/src/test/ui/proc-macro/macro-rules-derive-cfg.stdout
index 5db18590bdf..a0b0cbb19e4 100644
--- a/src/test/ui/proc-macro/macro-rules-derive-cfg.stdout
+++ b/src/test/ui/proc-macro/macro-rules-derive-cfg.stdout
@@ -5,172 +5,167 @@ PRINT-DERIVE INPUT (DISPLAY): struct Foo
      {
          let a = #[rustc_dummy(first)] #[rustc_dummy(second)]
          { # ! [allow(unused)] 30 } ; 0
-     }],
+     }]
 }
 PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+        span: $DIR/macro-rules-derive-cfg.rs:17:9: 17:15 (#4),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+        span: $DIR/macro-rules-derive-cfg.rs:17:16: 17:19 (#4),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "val",
-                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                span: $DIR/macro-rules-derive-cfg.rs:18:13: 18:16 (#4),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                span: $DIR/macro-rules-derive-cfg.rs:18:16: 18:17 (#4),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "bool",
-                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                        span: $DIR/macro-rules-derive-cfg.rs:18:19: 18:23 (#4),
                     },
                     Punct {
                         ch: ';',
                         spacing: Alone,
-                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                        span: $DIR/macro-rules-derive-cfg.rs:18:23: 18:24 (#4),
                     },
                     Group {
                         delimiter: Brace,
                         stream: TokenStream [
                             Ident {
                                 ident: "let",
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:19:17: 19:20 (#4),
                             },
                             Ident {
                                 ident: "a",
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:19:21: 19:22 (#4),
                             },
                             Punct {
                                 ch: '=',
                                 spacing: Alone,
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:19:23: 19:24 (#4),
                             },
                             Punct {
                                 ch: '#',
                                 spacing: Alone,
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:19:25: 19:26 (#4),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "rustc_dummy",
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:19:48: 19:59 (#4),
                                     },
                                     Group {
                                         delimiter: Parenthesis,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "first",
-                                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                                span: $DIR/macro-rules-derive-cfg.rs:19:60: 19:65 (#4),
                                             },
                                         ],
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:19:59: 19:66 (#4),
                                     },
                                 ],
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:19:25: 19:26 (#4),
                             },
                             Punct {
                                 ch: '#',
                                 spacing: Alone,
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:26:13: 26:14 (#0),
                             },
                             Group {
                                 delimiter: Bracket,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "rustc_dummy",
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:26:36: 26:47 (#0),
                                     },
                                     Group {
                                         delimiter: Parenthesis,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "second",
-                                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                                span: $DIR/macro-rules-derive-cfg.rs:26:48: 26:54 (#0),
                                             },
                                         ],
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:26:47: 26:55 (#0),
                                     },
                                 ],
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:26:13: 26:14 (#0),
                             },
                             Group {
                                 delimiter: Brace,
                                 stream: TokenStream [
                                     Punct {
                                         ch: '#',
-                                        spacing: Joint,
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        spacing: Alone,
+                                        span: $DIR/macro-rules-derive-cfg.rs:27:5: 27:6 (#0),
                                     },
                                     Punct {
                                         ch: '!',
                                         spacing: Alone,
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:27:6: 27:7 (#0),
                                     },
                                     Group {
                                         delimiter: Bracket,
                                         stream: TokenStream [
                                             Ident {
                                                 ident: "allow",
-                                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                                span: $DIR/macro-rules-derive-cfg.rs:27:29: 27:34 (#0),
                                             },
                                             Group {
                                                 delimiter: Parenthesis,
                                                 stream: TokenStream [
                                                     Ident {
                                                         ident: "unused",
-                                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                                        span: $DIR/macro-rules-derive-cfg.rs:27:35: 27:41 (#0),
                                                     },
                                                 ],
-                                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                                span: $DIR/macro-rules-derive-cfg.rs:27:34: 27:42 (#0),
                                             },
                                         ],
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:27:5: 27:6 (#0),
                                     },
                                     Literal {
                                         kind: Integer,
                                         symbol: "30",
                                         suffix: None,
-                                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                        span: $DIR/macro-rules-derive-cfg.rs:28:5: 28:7 (#0),
                                     },
                                 ],
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:26:58: 29:2 (#0),
                             },
                             Punct {
                                 ch: ';',
                                 spacing: Alone,
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:19:74: 19:75 (#4),
                             },
                             Literal {
                                 kind: Integer,
                                 symbol: "0",
                                 suffix: None,
-                                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                                span: $DIR/macro-rules-derive-cfg.rs:20:17: 20:18 (#4),
                             },
                         ],
-                        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                        span: $DIR/macro-rules-derive-cfg.rs:18:25: 21:14 (#4),
                     },
                 ],
-                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
-            },
-            Punct {
-                ch: ',',
-                spacing: Alone,
-                span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+                span: $DIR/macro-rules-derive-cfg.rs:18:18: 21:15 (#4),
             },
         ],
-        span: $DIR/macro-rules-derive-cfg.rs:17:9: 22:10 (#4),
+        span: $DIR/macro-rules-derive-cfg.rs:17:20: 22:10 (#4),
     },
 ]
diff --git a/src/test/ui/proc-macro/nested-derive-cfg.stdout b/src/test/ui/proc-macro/nested-derive-cfg.stdout
index cf4e5d94d8a..9a562c971c8 100644
--- a/src/test/ui/proc-macro/nested-derive-cfg.stdout
+++ b/src/test/ui/proc-macro/nested-derive-cfg.stdout
@@ -1,94 +1,81 @@
 PRINT-DERIVE INPUT (DISPLAY): struct Foo
-{
-    my_array :
-    [bool ; { struct Inner { non_removed_inner_field : usize, } 0 }],
-}
+{ my_array : [bool ; { struct Inner { non_removed_inner_field : usize } 0 }] }
 PRINT-DERIVE INPUT (DEBUG): TokenStream [
     Ident {
         ident: "struct",
-        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+        span: $DIR/nested-derive-cfg.rs:12:1: 12:7 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+        span: $DIR/nested-derive-cfg.rs:12:8: 12:11 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "my_array",
-                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                span: $DIR/nested-derive-cfg.rs:14:5: 14:13 (#0),
             },
             Punct {
                 ch: ':',
                 spacing: Alone,
-                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                span: $DIR/nested-derive-cfg.rs:14:13: 14:14 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "bool",
-                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                        span: $DIR/nested-derive-cfg.rs:14:16: 14:20 (#0),
                     },
                     Punct {
                         ch: ';',
                         spacing: Alone,
-                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                        span: $DIR/nested-derive-cfg.rs:14:20: 14:21 (#0),
                     },
                     Group {
                         delimiter: Brace,
                         stream: TokenStream [
                             Ident {
                                 ident: "struct",
-                                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                span: $DIR/nested-derive-cfg.rs:15:9: 15:15 (#0),
                             },
                             Ident {
                                 ident: "Inner",
-                                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                span: $DIR/nested-derive-cfg.rs:15:16: 15:21 (#0),
                             },
                             Group {
                                 delimiter: Brace,
                                 stream: TokenStream [
                                     Ident {
                                         ident: "non_removed_inner_field",
-                                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                        span: $DIR/nested-derive-cfg.rs:17:13: 17:36 (#0),
                                     },
                                     Punct {
                                         ch: ':',
                                         spacing: Alone,
-                                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                        span: $DIR/nested-derive-cfg.rs:17:36: 17:37 (#0),
                                     },
                                     Ident {
                                         ident: "usize",
-                                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
-                                    },
-                                    Punct {
-                                        ch: ',',
-                                        spacing: Alone,
-                                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                        span: $DIR/nested-derive-cfg.rs:17:38: 17:43 (#0),
                                     },
                                 ],
-                                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                span: $DIR/nested-derive-cfg.rs:15:22: 18:10 (#0),
                             },
                             Literal {
                                 kind: Integer,
                                 symbol: "0",
                                 suffix: None,
-                                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                                span: $DIR/nested-derive-cfg.rs:19:9: 19:10 (#0),
                             },
                         ],
-                        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                        span: $DIR/nested-derive-cfg.rs:14:22: 20:6 (#0),
                     },
                 ],
-                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
-            },
-            Punct {
-                ch: ',',
-                spacing: Alone,
-                span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+                span: $DIR/nested-derive-cfg.rs:14:15: 20:7 (#0),
             },
         ],
-        span: $DIR/nested-derive-cfg.rs:12:1: 21:2 (#0),
+        span: $DIR/nested-derive-cfg.rs:12:12: 21:2 (#0),
     },
 ]
diff --git a/src/test/ui/proc-macro/simple-tuple.rs b/src/test/ui/proc-macro/simple-tuple.rs
new file mode 100644
index 00000000000..c94c5877e22
--- /dev/null
+++ b/src/test/ui/proc-macro/simple-tuple.rs
@@ -0,0 +1,19 @@
+// check-pass
+// compile-flags: -Z span-debug --error-format human
+// aux-build:test-macros.rs
+// edition:2018
+
+#![feature(proc_macro_hygiene)]
+
+#![no_std] // Don't load unnecessary hygiene information from std
+extern crate std;
+
+#[macro_use]
+extern crate test_macros;
+
+fn main() {
+    #[print_target_and_args(my_arg)] (
+        #![cfg_attr(not(FALSE), allow(unused))]
+        1, 2, 3
+    );
+}
diff --git a/src/test/ui/proc-macro/simple-tuple.stdout b/src/test/ui/proc-macro/simple-tuple.stdout
new file mode 100644
index 00000000000..1cc8579a467
--- /dev/null
+++ b/src/test/ui/proc-macro/simple-tuple.stdout
@@ -0,0 +1,79 @@
+PRINT-ATTR_ARGS INPUT (DISPLAY): my_arg
+PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
+    Ident {
+        ident: "my_arg",
+        span: $DIR/simple-tuple.rs:15:29: 15:35 (#0),
+    },
+]
+PRINT-ATTR INPUT (DISPLAY): (# ! [allow(unused)] 1, 2, 3) ;
+PRINT-ATTR INPUT (DEBUG): TokenStream [
+    Group {
+        delimiter: Parenthesis,
+        stream: TokenStream [
+            Punct {
+                ch: '#',
+                spacing: Alone,
+                span: $DIR/simple-tuple.rs:16:9: 16:10 (#0),
+            },
+            Punct {
+                ch: '!',
+                spacing: Alone,
+                span: $DIR/simple-tuple.rs:16:10: 16:11 (#0),
+            },
+            Group {
+                delimiter: Bracket,
+                stream: TokenStream [
+                    Ident {
+                        ident: "allow",
+                        span: $DIR/simple-tuple.rs:16:33: 16:38 (#0),
+                    },
+                    Group {
+                        delimiter: Parenthesis,
+                        stream: TokenStream [
+                            Ident {
+                                ident: "unused",
+                                span: $DIR/simple-tuple.rs:16:39: 16:45 (#0),
+                            },
+                        ],
+                        span: $DIR/simple-tuple.rs:16:38: 16:46 (#0),
+                    },
+                ],
+                span: $DIR/simple-tuple.rs:16:9: 16:10 (#0),
+            },
+            Literal {
+                kind: Integer,
+                symbol: "1",
+                suffix: None,
+                span: $DIR/simple-tuple.rs:17:9: 17:10 (#0),
+            },
+            Punct {
+                ch: ',',
+                spacing: Alone,
+                span: $DIR/simple-tuple.rs:17:10: 17:11 (#0),
+            },
+            Literal {
+                kind: Integer,
+                symbol: "2",
+                suffix: None,
+                span: $DIR/simple-tuple.rs:17:12: 17:13 (#0),
+            },
+            Punct {
+                ch: ',',
+                spacing: Alone,
+                span: $DIR/simple-tuple.rs:17:13: 17:14 (#0),
+            },
+            Literal {
+                kind: Integer,
+                symbol: "3",
+                suffix: None,
+                span: $DIR/simple-tuple.rs:17:15: 17:16 (#0),
+            },
+        ],
+        span: $DIR/simple-tuple.rs:15:38: 18:6 (#0),
+    },
+    Punct {
+        ch: ';',
+        spacing: Alone,
+        span: $DIR/simple-tuple.rs:18:6: 18:7 (#0),
+    },
+]
diff --git a/src/test/ui/proc-macro/weird-braces.stdout b/src/test/ui/proc-macro/weird-braces.stdout
index 25f0eaf0dd4..990829456e8 100644
--- a/src/test/ui/proc-macro/weird-braces.stdout
+++ b/src/test/ui/proc-macro/weird-braces.stdout
@@ -15,40 +15,40 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
     Punct {
         ch: '#',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:17:1: 17:2 (#0),
     },
     Group {
         delimiter: Bracket,
         stream: TokenStream [
             Ident {
                 ident: "print_target_and_args",
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:17:3: 17:24 (#0),
             },
             Group {
                 delimiter: Parenthesis,
                 stream: TokenStream [
                     Ident {
                         ident: "second_outer",
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:17:25: 17:37 (#0),
                     },
                 ],
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:17:24: 17:38 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:17:2: 17:39 (#0),
     },
     Ident {
         ident: "impl",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:1: 18:5 (#0),
     },
     Ident {
         ident: "Bar",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:6: 18:9 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:9: 18:10 (#0),
     },
     Group {
         delimiter: Brace,
@@ -57,54 +57,54 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
                 kind: Integer,
                 symbol: "1",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:11: 18:12 (#0),
             },
             Punct {
                 ch: '>',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:13: 18:14 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "0",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:15: 18:16 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:10: 18:17 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:17: 18:18 (#0),
     },
     Ident {
         ident: "for",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:19: 18:22 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:23: 18:26 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:26: 18:27 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "true",
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:28: 18:32 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:27: 18:33 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:33: 18:34 (#0),
     },
     Group {
         delimiter: Brace,
@@ -112,72 +112,72 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:19:5: 19:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:19:6: 19:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:19:8: 19:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "first_inner",
-                                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                                span: $DIR/weird-braces.rs:19:30: 19:41 (#0),
                             },
                         ],
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:19:29: 19:42 (#0),
                     },
                 ],
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:19:7: 19:43 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:5: 20:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:6: 20:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:20:8: 20:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "second_inner",
-                                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                                span: $DIR/weird-braces.rs:20:30: 20:42 (#0),
                             },
                         ],
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:20:29: 20:43 (#0),
                     },
                 ],
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:7: 20:44 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:35: 21:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): second_outer
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "second_outer",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:17:25: 17:37 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): impl Bar < { 1 > 0 } > for Foo < { true } >
@@ -188,16 +188,16 @@ PRINT-ATTR INPUT (DISPLAY): impl Bar < { 1 > 0 } > for Foo < { true } >
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "impl",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:1: 18:5 (#0),
     },
     Ident {
         ident: "Bar",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:6: 18:9 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:9: 18:10 (#0),
     },
     Group {
         delimiter: Brace,
@@ -206,54 +206,54 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
                 kind: Integer,
                 symbol: "1",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:11: 18:12 (#0),
             },
             Punct {
                 ch: '>',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:13: 18:14 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "0",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:15: 18:16 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:10: 18:17 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:17: 18:18 (#0),
     },
     Ident {
         ident: "for",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:19: 18:22 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:23: 18:26 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:26: 18:27 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "true",
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:28: 18:32 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:27: 18:33 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:33: 18:34 (#0),
     },
     Group {
         delimiter: Brace,
@@ -261,72 +261,72 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:19:5: 19:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:19:6: 19:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:19:8: 19:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "first_inner",
-                                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                                span: $DIR/weird-braces.rs:19:30: 19:41 (#0),
                             },
                         ],
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:19:29: 19:42 (#0),
                     },
                 ],
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:19:7: 19:43 (#0),
             },
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:5: 20:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:6: 20:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:20:8: 20:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "second_inner",
-                                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                                span: $DIR/weird-braces.rs:20:30: 20:42 (#0),
                             },
                         ],
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:20:29: 20:43 (#0),
                     },
                 ],
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:7: 20:44 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:35: 21:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): first_inner
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "first_inner",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:19:30: 19:41 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): impl Bar < { 1 > 0 } > for Foo < { true } >
@@ -334,16 +334,16 @@ PRINT-ATTR INPUT (DISPLAY): impl Bar < { 1 > 0 } > for Foo < { true } >
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "impl",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:1: 18:5 (#0),
     },
     Ident {
         ident: "Bar",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:6: 18:9 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:9: 18:10 (#0),
     },
     Group {
         delimiter: Brace,
@@ -352,54 +352,54 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
                 kind: Integer,
                 symbol: "1",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:11: 18:12 (#0),
             },
             Punct {
                 ch: '>',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:13: 18:14 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "0",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:15: 18:16 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:10: 18:17 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:17: 18:18 (#0),
     },
     Ident {
         ident: "for",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:19: 18:22 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:23: 18:26 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:26: 18:27 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "true",
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:28: 18:32 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:27: 18:33 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:33: 18:34 (#0),
     },
     Group {
         delimiter: Brace,
@@ -407,58 +407,58 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
             Punct {
                 ch: '#',
                 spacing: Joint,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:5: 20:6 (#0),
             },
             Punct {
                 ch: '!',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:6: 20:7 (#0),
             },
             Group {
                 delimiter: Bracket,
                 stream: TokenStream [
                     Ident {
                         ident: "print_target_and_args",
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:20:8: 20:29 (#0),
                     },
                     Group {
                         delimiter: Parenthesis,
                         stream: TokenStream [
                             Ident {
                                 ident: "second_inner",
-                                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                                span: $DIR/weird-braces.rs:20:30: 20:42 (#0),
                             },
                         ],
-                        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                        span: $DIR/weird-braces.rs:20:29: 20:43 (#0),
                     },
                 ],
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:20:7: 20:44 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:35: 21:2 (#0),
     },
 ]
 PRINT-ATTR_ARGS INPUT (DISPLAY): second_inner
 PRINT-ATTR_ARGS INPUT (DEBUG): TokenStream [
     Ident {
         ident: "second_inner",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:20:30: 20:42 (#0),
     },
 ]
 PRINT-ATTR INPUT (DISPLAY): impl Bar < { 1 > 0 } > for Foo < { true } > { }
 PRINT-ATTR INPUT (DEBUG): TokenStream [
     Ident {
         ident: "impl",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:1: 18:5 (#0),
     },
     Ident {
         ident: "Bar",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:6: 18:9 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:9: 18:10 (#0),
     },
     Group {
         delimiter: Brace,
@@ -467,58 +467,58 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
                 kind: Integer,
                 symbol: "1",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:11: 18:12 (#0),
             },
             Punct {
                 ch: '>',
                 spacing: Alone,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:13: 18:14 (#0),
             },
             Literal {
                 kind: Integer,
                 symbol: "0",
                 suffix: None,
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:15: 18:16 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:10: 18:17 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:17: 18:18 (#0),
     },
     Ident {
         ident: "for",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:19: 18:22 (#0),
     },
     Ident {
         ident: "Foo",
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:23: 18:26 (#0),
     },
     Punct {
         ch: '<',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:26: 18:27 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [
             Ident {
                 ident: "true",
-                span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+                span: $DIR/weird-braces.rs:18:28: 18:32 (#0),
             },
         ],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:27: 18:33 (#0),
     },
     Punct {
         ch: '>',
         spacing: Alone,
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:33: 18:34 (#0),
     },
     Group {
         delimiter: Brace,
         stream: TokenStream [],
-        span: $DIR/weird-braces.rs:18:1: 21:2 (#0),
+        span: $DIR/weird-braces.rs:18:35: 21:2 (#0),
     },
 ]