From 7bfa11d931a39f0419ff0f693544c3a54462a243 Mon Sep 17 00:00:00 2001
From: Vadim Petrochenkov <vadim.petrochenkov@gmail.com>
Date: Sun, 21 Mar 2021 03:58:32 +0300
Subject: [PATCH] [WIP] Expand attributes in left-to-right order

---
 compiler/rustc_attr/src/builtin.rs            |  12 +
 compiler/rustc_builtin_macros/src/cfg_eval.rs |  46 +-
 compiler/rustc_builtin_macros/src/test.rs     |   7 +
 compiler/rustc_expand/src/base.rs             |  60 +-
 compiler/rustc_expand/src/config.rs           | 127 ++-
 compiler/rustc_expand/src/expand.rs           | 897 +++++++++++-------
 src/bootstrap/test.rs                         |   4 +-
 .../cfg-attr-empty-is-unused.rs               |   5 +-
 .../cfg-attr-empty-is-unused.stderr           |  21 -
 .../cfg-attr-syntax-validation.rs             |   1 -
 .../cfg-attr-syntax-validation.stderr         |  13 +-
 src/test/ui/macros/macro-attributes.rs        |   6 +-
 src/test/ui/proc-macro/cfg-eval-fail.rs       |   1 -
 src/test/ui/proc-macro/cfg-eval-fail.stderr   |   8 +-
 src/test/ui/proc-macro/cfg-eval.rs            |   4 +
 ...-54400-unused-extern-crate-attr-span.fixed |   3 +-
 ...sue-54400-unused-extern-crate-attr-span.rs |   4 +-
 ...54400-unused-extern-crate-attr-span.stderr |   7 +-
 18 files changed, 782 insertions(+), 444 deletions(-)
 delete mode 100644 src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.stderr

diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index 20971ebb95748..3ce4e39e7331c 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -15,6 +15,18 @@ pub fn is_builtin_attr(attr: &Attribute) -> bool {
     attr.is_doc_comment() || attr.ident().filter(|ident| is_builtin_attr_name(ident.name)).is_some()
 }
 
+pub fn is_inert_builtin_attr(attr: &Attribute) -> bool {
+    attr.is_doc_comment()
+        || attr
+            .ident()
+            .filter(|ident| {
+                ident.name != sym::cfg
+                    && ident.name != sym::cfg_attr
+                    && is_builtin_attr_name(ident.name)
+            })
+            .is_some()
+}
+
 enum AttrError {
     MultipleItem(String),
     UnknownMetaItem(String, &'static [&'static str]),
diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs
index 79dc857074d59..95e19b7c38fe7 100644
--- a/compiler/rustc_builtin_macros/src/cfg_eval.rs
+++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs
@@ -35,50 +35,48 @@ crate fn cfg_eval(ecx: &ExtCtxt<'_>, annotatable: Annotatable) -> Vec<Annotatabl
             config_tokens: true,
         },
     };
-    let annotatable = visitor.configure_annotatable(annotatable);
-    vec![annotatable]
+    visitor.configure_annotatable(annotatable).map_or(Vec::new(), |annotatable| vec![annotatable])
 }
 
 struct CfgEval<'a, 'b> {
     cfg: &'a mut StripUnconfigured<'b>,
 }
 
-fn flat_map_annotatable(vis: &mut impl MutVisitor, annotatable: Annotatable) -> Annotatable {
+fn flat_map_annotatable(
+    vis: &mut impl MutVisitor,
+    annotatable: Annotatable,
+) -> Option<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::Item(item) => vis.flat_map_item(item).pop().map(Annotatable::Item),
         Annotatable::TraitItem(item) => {
-            Annotatable::TraitItem(vis.flat_map_trait_item(item).pop().unwrap())
+            vis.flat_map_trait_item(item).pop().map(Annotatable::TraitItem)
         }
         Annotatable::ImplItem(item) => {
-            Annotatable::ImplItem(vis.flat_map_impl_item(item).pop().unwrap())
+            vis.flat_map_impl_item(item).pop().map(Annotatable::ImplItem)
         }
         Annotatable::ForeignItem(item) => {
-            Annotatable::ForeignItem(vis.flat_map_foreign_item(item).pop().unwrap())
+            vis.flat_map_foreign_item(item).pop().map(Annotatable::ForeignItem)
         }
         Annotatable::Stmt(stmt) => {
-            Annotatable::Stmt(stmt.map(|stmt| vis.flat_map_stmt(stmt).pop().unwrap()))
+            vis.flat_map_stmt(stmt.into_inner()).pop().map(P).map(Annotatable::Stmt)
         }
-        Annotatable::Expr(mut expr) => Annotatable::Expr({
+        Annotatable::Expr(mut 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())
+            Some(Annotatable::Expr(expr))
         }
-        Annotatable::PatField(fp) => {
-            Annotatable::PatField(vis.flat_map_pat_field(fp).pop().unwrap())
+        Annotatable::Arm(arm) => vis.flat_map_arm(arm).pop().map(Annotatable::Arm),
+        Annotatable::ExprField(field) => {
+            vis.flat_map_expr_field(field).pop().map(Annotatable::ExprField)
         }
+        Annotatable::PatField(fp) => vis.flat_map_pat_field(fp).pop().map(Annotatable::PatField),
         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())
+            vis.flat_map_generic_param(param).pop().map(Annotatable::GenericParam)
         }
-        Annotatable::Variant(v) => Annotatable::Variant(vis.flat_map_variant(v).pop().unwrap()),
+        Annotatable::Param(param) => vis.flat_map_param(param).pop().map(Annotatable::Param),
+        Annotatable::FieldDef(sf) => vis.flat_map_field_def(sf).pop().map(Annotatable::FieldDef),
+        Annotatable::Variant(v) => vis.flat_map_variant(v).pop().map(Annotatable::Variant),
     }
 }
 
@@ -123,11 +121,11 @@ impl CfgEval<'_, '_> {
         self.cfg.configure(node)
     }
 
-    pub fn configure_annotatable(&mut self, mut annotatable: Annotatable) -> Annotatable {
+    pub fn configure_annotatable(&mut self, mut annotatable: Annotatable) -> Option<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;
+            return Some(annotatable);
         }
 
         // The majority of parsed attribute targets will never need to have early cfg-expansion
diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs
index e845f9ec55ad5..6aace9a67391e 100644
--- a/compiler/rustc_builtin_macros/src/test.rs
+++ b/compiler/rustc_builtin_macros/src/test.rs
@@ -7,6 +7,7 @@ use rustc_ast::attr;
 use rustc_ast::ptr::P;
 use rustc_ast_pretty::pprust;
 use rustc_expand::base::*;
+use rustc_expand::config::StripUnconfigured;
 use rustc_session::Session;
 use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
@@ -55,6 +56,12 @@ pub fn expand_test(
     item: Annotatable,
 ) -> Vec<Annotatable> {
     check_builtin_macro_attribute(cx, meta_item, sym::test);
+    let mut cfg =
+        StripUnconfigured { sess: cx.sess, features: cx.ecfg.features, config_tokens: true };
+    let item = match cfg.configure(item) {
+        Some(item) => item,
+        None => return Vec::new(),
+    };
     expand_test_or_bench(cx, attr_sp, item, false)
 }
 
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 5950584281649..be9ce5929ccd8 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, TokenStream};
+use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, LazyTokenStream, 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,6 +46,64 @@ pub enum Annotatable {
     Variant(ast::Variant),
 }
 
+impl AstLike for Annotatable {
+    const SUPPORTS_CUSTOM_INNER_ATTRS: bool = true;
+
+    fn attrs(&self) -> &[Attribute] {
+        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(),
+        }
+    }
+
+    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),
+            Annotatable::ImplItem(impl_item) => impl_item.visit_attrs(f),
+            Annotatable::ForeignItem(foreign_item) => foreign_item.visit_attrs(f),
+            Annotatable::Stmt(stmt) => stmt.visit_attrs(f),
+            Annotatable::Expr(expr) => expr.visit_attrs(f),
+            Annotatable::Arm(arm) => arm.visit_attrs(f),
+            Annotatable::ExprField(field) => field.visit_attrs(f),
+            Annotatable::PatField(fp) => fp.visit_attrs(f),
+            Annotatable::GenericParam(gp) => gp.visit_attrs(f),
+            Annotatable::Param(p) => p.visit_attrs(f),
+            Annotatable::FieldDef(sf) => sf.visit_attrs(f),
+            Annotatable::Variant(v) => v.visit_attrs(f),
+        }
+    }
+
+    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 {
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 03c83f9c07b5d..2d3be0169dbe4 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -415,6 +415,66 @@ impl<'a> StripUnconfigured<'a> {
             .collect()
     }
 
+    crate fn expand_cfg_attr(&mut self, attr: Attribute) -> Vec<Attribute> {
+        let (cfg_predicate, expanded_attrs) = match self.parse_cfg_attr(&attr) {
+            None => return vec![],
+            Some(r) => r,
+        };
+
+        if !attr::cfg_matches(&cfg_predicate, &self.sess.parse_sess, self.features) {
+            return vec![];
+        }
+
+        // We call `process_cfg_attr` recursively in case there's a
+        // `cfg_attr` inside of another `cfg_attr`. E.g.
+        //  `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
+        expanded_attrs
+            .into_iter()
+            .map(|(item, span)| {
+                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
+                // have captured tokens for `attr` itself, but we need to
+                // synthesize tokens for the wrapper `#` and `[]`, which
+                // we do below.
+
+                // Use the `#` in `#[cfg_attr(pred, attr)]` as the `#` token
+                // for `attr` when we expand it to `#[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 = AttrAnnotatedTokenTree::Delimited(
+                    DelimSpan::from_single(pound_span),
+                    DelimToken::Bracket,
+                    item.tokens
+                        .as_ref()
+                        .unwrap_or_else(|| panic!("Missing tokens for {:?}", item))
+                        .create_token_stream(),
+                );
+                trees.push((bracket_group, Spacing::Alone));
+                let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees)));
+
+                attr::mk_attr_from_item(item, tokens, attr.style, span)
+            })
+            .collect()
+    }
+
     fn parse_cfg_attr(&self, attr: &Attribute) -> Option<(MetaItem, Vec<(AttrItem, Span)>)> {
         match attr.get_normal_item().args {
             ast::MacArgs::Delimited(dspan, delim, ref tts) if !tts.is_empty() => {
@@ -453,43 +513,42 @@ impl<'a> StripUnconfigured<'a> {
 
     /// Determines if a node with the given attributes should be included in this configuration.
     fn in_cfg(&self, attrs: &[Attribute]) -> bool {
-        attrs.iter().all(|attr| {
-            if !is_cfg(self.sess, attr) {
+        attrs.iter().all(|attr| !is_cfg(self.sess, attr) || self.cfg_true(attr))
+    }
+
+    crate fn cfg_true(&self, attr: &Attribute) -> bool {
+        let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) {
+            Ok(meta_item) => meta_item,
+            Err(mut err) => {
+                err.emit();
                 return true;
             }
-            let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) {
-                Ok(meta_item) => meta_item,
-                Err(mut err) => {
-                    err.emit();
-                    return true;
-                }
-            };
-            let error = |span, msg, suggestion: &str| {
-                let mut err = self.sess.parse_sess.span_diagnostic.struct_span_err(span, msg);
-                if !suggestion.is_empty() {
-                    err.span_suggestion(
-                        span,
-                        "expected syntax is",
-                        suggestion.into(),
-                        Applicability::MaybeIncorrect,
-                    );
-                }
-                err.emit();
-                true
-            };
-            let span = meta_item.span;
-            match meta_item.meta_item_list() {
-                None => error(span, "`cfg` is not followed by parentheses", "cfg(/* predicate */)"),
-                Some([]) => error(span, "`cfg` predicate is not specified", ""),
-                Some([_, .., l]) => error(l.span(), "multiple `cfg` predicates are specified", ""),
-                Some([single]) => match single.meta_item() {
-                    Some(meta_item) => {
-                        attr::cfg_matches(meta_item, &self.sess.parse_sess, self.features)
-                    }
-                    None => error(single.span(), "`cfg` predicate key cannot be a literal", ""),
-                },
+        };
+        let error = |span, msg, suggestion: &str| {
+            let mut err = self.sess.parse_sess.span_diagnostic.struct_span_err(span, msg);
+            if !suggestion.is_empty() {
+                err.span_suggestion(
+                    span,
+                    "expected syntax is",
+                    suggestion.into(),
+                    Applicability::MaybeIncorrect,
+                );
             }
-        })
+            err.emit();
+            true
+        };
+        let span = meta_item.span;
+        match meta_item.meta_item_list() {
+            None => error(span, "`cfg` is not followed by parentheses", "cfg(/* predicate */)"),
+            Some([]) => error(span, "`cfg` predicate is not specified", ""),
+            Some([_, .., l]) => error(l.span(), "multiple `cfg` predicates are specified", ""),
+            Some([single]) => match single.meta_item() {
+                Some(meta_item) => {
+                    attr::cfg_matches(meta_item, &self.sess.parse_sess, self.features)
+                }
+                None => error(single.span(), "`cfg` predicate key cannot be a literal", ""),
+            },
+        }
     }
 
     /// If attributes are not allowed on expressions, emit an error for `attr`
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 3347c93948ccc..4a063e971beb4 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1,6 +1,5 @@
 use crate::base::*;
 use crate::config::StripUnconfigured;
-use crate::configure;
 use crate::hygiene::SyntaxContext;
 use crate::mbe::macro_rules::annotate_err_with_kind;
 use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
@@ -16,7 +15,7 @@ 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;
-use rustc_attr::{self as attr, is_builtin_attr};
+use rustc_attr::{self as attr, is_inert_builtin_attr};
 use rustc_data_structures::map_in_place::MapInPlace;
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_data_structures::sync::Lrc;
@@ -33,7 +32,6 @@ use rustc_span::{ExpnId, FileName, Span, DUMMY_SP};
 
 use smallvec::{smallvec, SmallVec};
 use std::io::ErrorKind;
-use std::ops::DerefMut;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::{iter, mem, slice};
@@ -506,10 +504,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                             let item = match &fragment {
                                 AstFragment::Items(items) => match &items[..] {
                                     [item] => AnnotatableRef::Item(item),
+                                    [] => return Vec::new(),
                                     _ => unreachable!(),
                                 },
                                 AstFragment::Stmts(stmts) => match &stmts[..] {
                                     [stmt] => AnnotatableRef::Stmt(stmt),
+                                    [] => return Vec::new(),
                                     _ => unreachable!(),
                                 },
                                 _ => unreachable!(),
@@ -1061,7 +1061,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
         item.visit_attrs(|attrs| {
             attr = attrs
                 .iter()
-                .position(|a| !self.cx.sess.is_attr_known(a) && !is_builtin_attr(a))
+                .position(|a| !self.cx.sess.is_attr_known(a) && !is_inert_builtin_attr(a))
                 .map(|attr_pos| {
                     let attr = attrs.remove(attr_pos);
                     let following_derives = attrs[attr_pos..]
@@ -1085,10 +1085,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
         attr
     }
 
-    fn configure<T: AstLike>(&mut self, node: T) -> Option<T> {
-        self.cfg.configure(node)
-    }
-
     // Detect use of feature-gated or invalid attributes on macro invocations
     // since they will not be detected after macro expansion.
     fn check_attributes(&mut self, attrs: &[ast::Attribute]) {
@@ -1121,177 +1117,349 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
 
 impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
     fn visit_expr(&mut self, expr: &mut P<ast::Expr>) {
-        self.cfg.configure_expr(expr);
-        visit_clobber(expr.deref_mut(), |mut expr| {
-            if let Some(attr) = self.take_first_attr(&mut expr) {
-                // Collect the invoc regardless of whether or not attributes are permitted here
-                // expansion will eat the attribute so it won't error later.
-                self.cfg.maybe_emit_expr_attr_err(&attr.0);
-
-                // AstFragmentKind::Expr requires the macro to emit an expression.
-                return self
-                    .collect_attr(attr, Annotatable::Expr(P(expr)), AstFragmentKind::Expr)
-                    .make_expr()
-                    .into_inner();
-            }
+        // FIXME: Feature gating is performed inconsistently between `Expr` and `OptExpr`.
+        if let Some(attr) = expr.attrs.first() {
+            self.cfg.maybe_emit_expr_attr_err(attr);
+        }
 
-            if let ast::ExprKind::MacCall(mac) = expr.kind {
-                self.check_attributes(&expr.attrs);
-                self.collect_bang(mac, expr.span, AstFragmentKind::Expr).make_expr().into_inner()
-            } else {
-                ensure_sufficient_stack(|| noop_visit_expr(&mut expr, self));
-                expr
-            }
-        });
+        loop {
+            return match self.take_first_attr(expr) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        let msg = "removing an expression is not supported in this position";
+                        self.cx.span_err(attr.span, msg);
+                        continue;
+                    }
+                    sym::cfg_attr => {
+                        expr.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => visit_clobber(expr, |expr| {
+                        self.collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Expr(expr),
+                            AstFragmentKind::Expr,
+                        )
+                        .make_expr()
+                    }),
+                },
+                None => match expr.kind {
+                    ast::ExprKind::MacCall(..) => {
+                        self.check_attributes(&expr.attrs);
+                        visit_clobber(expr, |mut expr| {
+                            match mem::replace(&mut expr.kind, ast::ExprKind::Err) {
+                                ast::ExprKind::MacCall(mac) => self
+                                    .collect_bang(mac, expr.span, AstFragmentKind::Expr)
+                                    .make_expr(),
+                                _ => unreachable!(),
+                            }
+                        })
+                    }
+                    _ => ensure_sufficient_stack(|| noop_visit_expr(expr, self)),
+                },
+            };
+        }
     }
 
-    fn flat_map_arm(&mut self, arm: ast::Arm) -> SmallVec<[ast::Arm; 1]> {
-        let mut arm = configure!(self, arm);
-
-        if let Some(attr) = self.take_first_attr(&mut arm) {
-            return self
-                .collect_attr(attr, Annotatable::Arm(arm), AstFragmentKind::Arms)
-                .make_arms();
+    fn flat_map_arm(&mut self, mut arm: ast::Arm) -> SmallVec<[ast::Arm; 1]> {
+        loop {
+            return match self.take_first_attr(&mut arm) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        arm.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Arm(arm),
+                            AstFragmentKind::Arms,
+                        )
+                        .make_arms(),
+                },
+                None => noop_flat_map_arm(arm, self),
+            };
         }
-
-        noop_flat_map_arm(arm, self)
     }
 
-    fn flat_map_expr_field(&mut self, field: ast::ExprField) -> SmallVec<[ast::ExprField; 1]> {
-        let mut field = configure!(self, field);
-
-        if let Some(attr) = self.take_first_attr(&mut field) {
-            return self
-                .collect_attr(attr, Annotatable::ExprField(field), AstFragmentKind::Fields)
-                .make_expr_fields();
+    fn flat_map_expr_field(&mut self, mut field: ast::ExprField) -> SmallVec<[ast::ExprField; 1]> {
+        loop {
+            return match self.take_first_attr(&mut field) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        field.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::ExprField(field),
+                            AstFragmentKind::Fields,
+                        )
+                        .make_expr_fields(),
+                },
+                None => noop_flat_map_expr_field(field, self),
+            };
         }
-
-        noop_flat_map_expr_field(field, self)
     }
 
-    fn flat_map_pat_field(&mut self, fp: ast::PatField) -> SmallVec<[ast::PatField; 1]> {
-        let mut fp = configure!(self, fp);
-
-        if let Some(attr) = self.take_first_attr(&mut fp) {
-            return self
-                .collect_attr(attr, Annotatable::PatField(fp), AstFragmentKind::FieldPats)
-                .make_pat_fields();
+    fn flat_map_pat_field(&mut self, mut fp: ast::PatField) -> SmallVec<[ast::PatField; 1]> {
+        loop {
+            return match self.take_first_attr(&mut fp) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        fp.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::PatField(fp),
+                            AstFragmentKind::FieldPats,
+                        )
+                        .make_pat_fields(),
+                },
+                None => noop_flat_map_pat_field(fp, self),
+            };
         }
-
-        noop_flat_map_pat_field(fp, self)
     }
 
-    fn flat_map_param(&mut self, p: ast::Param) -> SmallVec<[ast::Param; 1]> {
-        let mut p = configure!(self, p);
-
-        if let Some(attr) = self.take_first_attr(&mut p) {
-            return self
-                .collect_attr(attr, Annotatable::Param(p), AstFragmentKind::Params)
-                .make_params();
+    fn flat_map_param(&mut self, mut p: ast::Param) -> SmallVec<[ast::Param; 1]> {
+        loop {
+            return match self.take_first_attr(&mut p) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        p.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Param(p),
+                            AstFragmentKind::Params,
+                        )
+                        .make_params(),
+                },
+                None => noop_flat_map_param(p, self),
+            };
         }
-
-        noop_flat_map_param(p, self)
     }
 
-    fn flat_map_field_def(&mut self, sf: ast::FieldDef) -> SmallVec<[ast::FieldDef; 1]> {
-        let mut sf = configure!(self, sf);
-
-        if let Some(attr) = self.take_first_attr(&mut sf) {
-            return self
-                .collect_attr(attr, Annotatable::FieldDef(sf), AstFragmentKind::StructFields)
-                .make_field_defs();
+    fn flat_map_field_def(&mut self, mut sf: ast::FieldDef) -> SmallVec<[ast::FieldDef; 1]> {
+        loop {
+            return match self.take_first_attr(&mut sf) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        sf.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::FieldDef(sf),
+                            AstFragmentKind::StructFields,
+                        )
+                        .make_field_defs(),
+                },
+                None => noop_flat_map_field_def(sf, self),
+            };
         }
-
-        noop_flat_map_field_def(sf, self)
     }
 
-    fn flat_map_variant(&mut self, variant: ast::Variant) -> SmallVec<[ast::Variant; 1]> {
-        let mut variant = configure!(self, variant);
-
-        if let Some(attr) = self.take_first_attr(&mut variant) {
-            return self
-                .collect_attr(attr, Annotatable::Variant(variant), AstFragmentKind::Variants)
-                .make_variants();
+    fn flat_map_variant(&mut self, mut variant: ast::Variant) -> SmallVec<[ast::Variant; 1]> {
+        loop {
+            return match self.take_first_attr(&mut variant) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        variant.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Variant(variant),
+                            AstFragmentKind::Variants,
+                        )
+                        .make_variants(),
+                },
+                None => noop_flat_map_variant(variant, self),
+            };
         }
-
-        noop_flat_map_variant(variant, self)
     }
 
-    fn filter_map_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
-        let expr = configure!(self, expr);
-        expr.filter_map(|mut expr| {
-            if let Some(attr) = self.take_first_attr(&mut expr) {
-                self.cfg.maybe_emit_expr_attr_err(&attr.0);
-
-                return self
-                    .collect_attr(attr, Annotatable::Expr(P(expr)), AstFragmentKind::OptExpr)
-                    .make_opt_expr()
-                    .map(|expr| expr.into_inner());
-            }
-
-            if let ast::ExprKind::MacCall(mac) = expr.kind {
-                self.check_attributes(&expr.attrs);
-                self.collect_bang(mac, expr.span, AstFragmentKind::OptExpr)
-                    .make_opt_expr()
-                    .map(|expr| expr.into_inner())
-            } else {
-                Some({
-                    noop_visit_expr(&mut expr, self);
-                    expr
-                })
-            }
-        })
+    fn filter_map_expr(&mut self, mut expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
+        loop {
+            return match self.take_first_attr(&mut expr) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        expr.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => {
+                        // FIXME: Feature gating is performed inconsistently
+                        // between `Expr` and `OptExpr`.
+                        self.cfg.maybe_emit_expr_attr_err(&attr);
+                        self.collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Expr(expr),
+                            AstFragmentKind::OptExpr,
+                        )
+                        .make_opt_expr()
+                    }
+                },
+                None => match expr.kind {
+                    ast::ExprKind::MacCall(..) => {
+                        let expr = expr.into_inner();
+                        let mac = match expr.kind {
+                            ast::ExprKind::MacCall(mac) => mac,
+                            _ => unreachable!(),
+                        };
+                        self.check_attributes(&expr.attrs);
+                        self.collect_bang(mac, expr.span, AstFragmentKind::OptExpr).make_opt_expr()
+                    }
+                    _ => Some({
+                        noop_visit_expr(&mut expr, self);
+                        expr
+                    }),
+                },
+            };
+        }
     }
 
     fn visit_pat(&mut self, pat: &mut P<ast::Pat>) {
         match pat.kind {
-            PatKind::MacCall(_) => {}
-            _ => return noop_visit_pat(pat, self),
-        }
-
-        visit_clobber(pat, |mut pat| match mem::replace(&mut pat.kind, PatKind::Wild) {
-            PatKind::MacCall(mac) => {
-                self.collect_bang(mac, pat.span, AstFragmentKind::Pat).make_pat()
+            PatKind::MacCall(_) => {
+                visit_clobber(pat, |mut pat| match mem::replace(&mut pat.kind, PatKind::Wild) {
+                    PatKind::MacCall(mac) => {
+                        self.collect_bang(mac, pat.span, AstFragmentKind::Pat).make_pat()
+                    }
+                    _ => unreachable!(),
+                })
             }
-            _ => unreachable!(),
-        });
+            _ => noop_visit_pat(pat, self),
+        }
     }
 
-    fn flat_map_stmt(&mut self, stmt: ast::Stmt) -> SmallVec<[ast::Stmt; 1]> {
-        let mut stmt = configure!(self, stmt);
-
-        // we'll expand attributes on expressions separately
-        if !stmt.is_expr() {
-            if let Some(attr) = self.take_first_attr(&mut stmt) {
-                return self
-                    .collect_attr(attr, Annotatable::Stmt(P(stmt)), AstFragmentKind::Stmts)
-                    .make_stmts();
-            }
+    fn flat_map_stmt(&mut self, mut stmt: ast::Stmt) -> SmallVec<[ast::Stmt; 1]> {
+        // FIXME: Expression statements are currently expanded as expressions,
+        // expand them as statements instead (#61733).
+        if stmt.is_expr() {
+            // The placeholder expander gives ids to statements, so we avoid folding the id here.
+            // FIXME: Avoid special treatment of node ids in statements.
+            let ast::Stmt { id, kind, span } = stmt;
+            return noop_flat_map_stmt_kind(kind, self)
+                .into_iter()
+                .map(|kind| ast::Stmt { id, kind, span })
+                .collect();
         }
 
-        if let StmtKind::MacCall(mac) = stmt.kind {
-            let MacCallStmt { mac, style, attrs, tokens: _ } = mac.into_inner();
-            self.check_attributes(&attrs);
-            let mut placeholder =
-                self.collect_bang(mac, stmt.span, AstFragmentKind::Stmts).make_stmts();
-
-            // If this is a macro invocation with a semicolon, then apply that
-            // semicolon to the final statement produced by expansion.
-            if style == MacStmtStyle::Semicolon {
-                if let Some(stmt) = placeholder.pop() {
-                    placeholder.push(stmt.add_trailing_semicolon());
-                }
-            }
+        loop {
+            return match self.take_first_attr(&mut stmt) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        stmt.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Stmt(P(stmt)),
+                            AstFragmentKind::Stmts,
+                        )
+                        .make_stmts(),
+                },
+                None => match stmt.kind {
+                    StmtKind::MacCall(mac) => {
+                        let MacCallStmt { mac, style, attrs, tokens: _ } = mac.into_inner();
+                        self.check_attributes(&attrs);
+                        let mut placeholder =
+                            self.collect_bang(mac, stmt.span, AstFragmentKind::Stmts).make_stmts();
+
+                        // If this is a macro invocation with a semicolon, then apply that
+                        // semicolon to the final statement produced by expansion.
+                        if style == MacStmtStyle::Semicolon {
+                            if let Some(stmt) = placeholder.pop() {
+                                placeholder.push(stmt.add_trailing_semicolon());
+                            }
+                        }
 
-            return placeholder;
+                        placeholder
+                    }
+                    _ => {
+                        // The placeholder expander gives ids to statements, so we avoid folding the id here.
+                        // FIXME: Avoid special treatment of node ids in statements.
+                        let ast::Stmt { id, kind, span } = stmt;
+                        noop_flat_map_stmt_kind(kind, self)
+                            .into_iter()
+                            .map(|kind| ast::Stmt { id, kind, span })
+                            .collect()
+                    }
+                },
+            };
         }
-
-        // The placeholder expander gives ids to statements, so we avoid folding the id here.
-        let ast::Stmt { id, kind, span } = stmt;
-        noop_flat_map_stmt_kind(kind, self)
-            .into_iter()
-            .map(|kind| ast::Stmt { id, kind, span })
-            .collect()
     }
 
     fn visit_block(&mut self, block: &mut P<Block>) {
@@ -1303,223 +1471,296 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
         self.cx.current_expansion.dir_ownership = orig_dir_ownership;
     }
 
-    fn flat_map_item(&mut self, item: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
-        let mut item = configure!(self, item);
-
-        if let Some(attr) = self.take_first_attr(&mut item) {
-            return self
-                .collect_attr(attr, Annotatable::Item(item), AstFragmentKind::Items)
-                .make_items();
-        }
-
-        let mut attrs = mem::take(&mut item.attrs); // We do this to please borrowck.
-        let ident = item.ident;
-        let span = item.span;
-
-        match item.kind {
-            ast::ItemKind::MacCall(..) => {
-                item.attrs = attrs;
-                self.check_attributes(&item.attrs);
-                item.and_then(|item| match item.kind {
-                    ItemKind::MacCall(mac) => {
-                        self.collect_bang(mac, span, AstFragmentKind::Items).make_items()
+    fn flat_map_item(&mut self, mut item: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+        loop {
+            return match self.take_first_attr(&mut item) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
                     }
-                    _ => unreachable!(),
-                })
-            }
-            ast::ItemKind::Mod(_, ref mut mod_kind) if ident != Ident::invalid() => {
-                let (file_path, dir_path, dir_ownership) = match mod_kind {
-                    ModKind::Loaded(_, inline, _) => {
-                        // Inline `mod foo { ... }`, but we still need to push directories.
-                        let (dir_path, dir_ownership) = mod_dir_path(
-                            &self.cx.sess,
-                            ident,
-                            &attrs,
-                            &self.cx.current_expansion.module,
-                            self.cx.current_expansion.dir_ownership,
-                            *inline,
-                        );
-                        item.attrs = attrs;
-                        (None, dir_path, dir_ownership)
+                    sym::cfg_attr => {
+                        item.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
                     }
-                    ModKind::Unloaded => {
-                        // We have an outline `mod foo;` so we need to parse the file.
-                        let old_attrs_len = attrs.len();
-                        let ParsedExternalMod {
-                            mut items,
-                            inner_span,
-                            file_path,
-                            dir_path,
-                            dir_ownership,
-                        } = parse_external_mod(
-                            &self.cx.sess,
-                            ident,
-                            span,
-                            &self.cx.current_expansion.module,
-                            self.cx.current_expansion.dir_ownership,
-                            &mut attrs,
-                        );
-
-                        if let Some(extern_mod_loaded) = self.cx.extern_mod_loaded {
-                            (attrs, items) = extern_mod_loaded(ident, attrs, items, inner_span);
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::Item(item),
+                            AstFragmentKind::Items,
+                        )
+                        .make_items(),
+                },
+                None => {
+                    let mut attrs = mem::take(&mut item.attrs); // We do this to please borrowck.
+                    let ident = item.ident;
+                    let span = item.span;
+
+                    match item.kind {
+                        ast::ItemKind::MacCall(..) => {
+                            item.attrs = attrs;
+                            self.check_attributes(&item.attrs);
+                            item.and_then(|item| match item.kind {
+                                ItemKind::MacCall(mac) => self
+                                    .collect_bang(mac, span, AstFragmentKind::Items)
+                                    .make_items(),
+                                _ => unreachable!(),
+                            })
                         }
+                        ast::ItemKind::Mod(_, ref mut mod_kind) if ident != Ident::invalid() => {
+                            let (file_path, dir_path, dir_ownership) = match mod_kind {
+                                ModKind::Loaded(_, inline, _) => {
+                                    // Inline `mod foo { ... }`, but we still need to push directories.
+                                    let (dir_path, dir_ownership) = mod_dir_path(
+                                        &self.cx.sess,
+                                        ident,
+                                        &attrs,
+                                        &self.cx.current_expansion.module,
+                                        self.cx.current_expansion.dir_ownership,
+                                        *inline,
+                                    );
+                                    item.attrs = attrs;
+                                    (None, dir_path, dir_ownership)
+                                }
+                                ModKind::Unloaded => {
+                                    // We have an outline `mod foo;` so we need to parse the file.
+                                    let old_attrs_len = attrs.len();
+                                    let ParsedExternalMod {
+                                        mut items,
+                                        inner_span,
+                                        file_path,
+                                        dir_path,
+                                        dir_ownership,
+                                    } = parse_external_mod(
+                                        &self.cx.sess,
+                                        ident,
+                                        span,
+                                        &self.cx.current_expansion.module,
+                                        self.cx.current_expansion.dir_ownership,
+                                        &mut attrs,
+                                    );
+
+                                    if let Some(extern_mod_loaded) = self.cx.extern_mod_loaded {
+                                        (attrs, items) =
+                                            extern_mod_loaded(ident, attrs, items, inner_span);
+                                    }
+
+                                    *mod_kind = ModKind::Loaded(items, Inline::No, inner_span);
+                                    item.attrs = attrs;
+                                    if item.attrs.len() > old_attrs_len {
+                                        // If we loaded an out-of-line module and added some inner attributes,
+                                        // then we need to re-configure it and re-collect attributes for
+                                        // resolution and expansion.
+                                        continue;
+                                    }
+                                    (Some(file_path), dir_path, dir_ownership)
+                                }
+                            };
 
-                        *mod_kind = ModKind::Loaded(items, Inline::No, inner_span);
-                        item.attrs = attrs;
-                        if item.attrs.len() > old_attrs_len {
-                            // If we loaded an out-of-line module and added some inner attributes,
-                            // then we need to re-configure it and re-collect attributes for
-                            // resolution and expansion.
-                            item = configure!(self, item);
-
-                            if let Some(attr) = self.take_first_attr(&mut item) {
-                                return self
-                                    .collect_attr(
-                                        attr,
-                                        Annotatable::Item(item),
-                                        AstFragmentKind::Items,
-                                    )
-                                    .make_items();
+                            // Set the module info before we flat map.
+                            let mut module =
+                                self.cx.current_expansion.module.with_dir_path(dir_path);
+                            module.mod_path.push(ident);
+                            if let Some(file_path) = file_path {
+                                module.file_path_stack.push(file_path);
                             }
-                        }
-                        (Some(file_path), dir_path, dir_ownership)
-                    }
-                };
-
-                // Set the module info before we flat map.
-                let mut module = self.cx.current_expansion.module.with_dir_path(dir_path);
-                module.mod_path.push(ident);
-                if let Some(file_path) = file_path {
-                    module.file_path_stack.push(file_path);
-                }
 
-                let orig_module =
-                    mem::replace(&mut self.cx.current_expansion.module, Rc::new(module));
-                let orig_dir_ownership =
-                    mem::replace(&mut self.cx.current_expansion.dir_ownership, dir_ownership);
+                            let orig_module = mem::replace(
+                                &mut self.cx.current_expansion.module,
+                                Rc::new(module),
+                            );
+                            let orig_dir_ownership = mem::replace(
+                                &mut self.cx.current_expansion.dir_ownership,
+                                dir_ownership,
+                            );
 
-                let result = noop_flat_map_item(item, self);
+                            let result = noop_flat_map_item(item, self);
 
-                // Restore the module info.
-                self.cx.current_expansion.dir_ownership = orig_dir_ownership;
-                self.cx.current_expansion.module = orig_module;
+                            // Restore the module info.
+                            self.cx.current_expansion.dir_ownership = orig_dir_ownership;
+                            self.cx.current_expansion.module = orig_module;
 
-                result
-            }
-            _ => {
-                item.attrs = attrs;
-                noop_flat_map_item(item, self)
-            }
+                            result
+                        }
+                        _ => {
+                            item.attrs = attrs;
+                            noop_flat_map_item(item, self)
+                        }
+                    }
+                }
+            };
         }
     }
 
-    fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
-        let mut item = configure!(self, item);
-
-        if let Some(attr) = self.take_first_attr(&mut item) {
-            return self
-                .collect_attr(attr, Annotatable::TraitItem(item), AstFragmentKind::TraitItems)
-                .make_trait_items();
-        }
-
-        match item.kind {
-            ast::AssocItemKind::MacCall(..) => {
-                self.check_attributes(&item.attrs);
-                item.and_then(|item| match item.kind {
-                    ast::AssocItemKind::MacCall(mac) => self
-                        .collect_bang(mac, item.span, AstFragmentKind::TraitItems)
+    fn flat_map_trait_item(
+        &mut self,
+        mut item: P<ast::AssocItem>,
+    ) -> SmallVec<[P<ast::AssocItem>; 1]> {
+        loop {
+            return match self.take_first_attr(&mut item) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        item.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::TraitItem(item),
+                            AstFragmentKind::TraitItems,
+                        )
                         .make_trait_items(),
-                    _ => unreachable!(),
-                })
-            }
-            _ => noop_flat_map_assoc_item(item, self),
+                },
+                None => match item.kind {
+                    ast::AssocItemKind::MacCall(..) => {
+                        self.check_attributes(&item.attrs);
+                        item.and_then(|item| match item.kind {
+                            ast::AssocItemKind::MacCall(mac) => self
+                                .collect_bang(mac, item.span, AstFragmentKind::TraitItems)
+                                .make_trait_items(),
+                            _ => unreachable!(),
+                        })
+                    }
+                    _ => noop_flat_map_assoc_item(item, self),
+                },
+            };
         }
     }
 
-    fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
-        let mut item = configure!(self, item);
-
-        if let Some(attr) = self.take_first_attr(&mut item) {
-            return self
-                .collect_attr(attr, Annotatable::ImplItem(item), AstFragmentKind::ImplItems)
-                .make_impl_items();
-        }
-
-        match item.kind {
-            ast::AssocItemKind::MacCall(..) => {
-                self.check_attributes(&item.attrs);
-                item.and_then(|item| match item.kind {
-                    ast::AssocItemKind::MacCall(mac) => self
-                        .collect_bang(mac, item.span, AstFragmentKind::ImplItems)
+    fn flat_map_impl_item(
+        &mut self,
+        mut item: P<ast::AssocItem>,
+    ) -> SmallVec<[P<ast::AssocItem>; 1]> {
+        loop {
+            return match self.take_first_attr(&mut item) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        item.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::ImplItem(item),
+                            AstFragmentKind::ImplItems,
+                        )
                         .make_impl_items(),
-                    _ => unreachable!(),
-                })
-            }
-            _ => noop_flat_map_assoc_item(item, self),
+                },
+                None => match item.kind {
+                    ast::AssocItemKind::MacCall(..) => {
+                        self.check_attributes(&item.attrs);
+                        item.and_then(|item| match item.kind {
+                            ast::AssocItemKind::MacCall(mac) => self
+                                .collect_bang(mac, item.span, AstFragmentKind::ImplItems)
+                                .make_impl_items(),
+                            _ => unreachable!(),
+                        })
+                    }
+                    _ => noop_flat_map_assoc_item(item, self),
+                },
+            };
         }
     }
 
     fn visit_ty(&mut self, ty: &mut P<ast::Ty>) {
         match ty.kind {
-            ast::TyKind::MacCall(_) => {}
-            _ => return noop_visit_ty(ty, self),
-        };
-
-        visit_clobber(ty, |mut ty| match mem::replace(&mut ty.kind, ast::TyKind::Err) {
-            ast::TyKind::MacCall(mac) => {
-                self.collect_bang(mac, ty.span, AstFragmentKind::Ty).make_ty()
+            ast::TyKind::MacCall(_) => {
+                visit_clobber(ty, |mut ty| match mem::replace(&mut ty.kind, ast::TyKind::Err) {
+                    ast::TyKind::MacCall(mac) => {
+                        self.collect_bang(mac, ty.span, AstFragmentKind::Ty).make_ty()
+                    }
+                    _ => unreachable!(),
+                })
             }
-            _ => unreachable!(),
-        });
+            _ => noop_visit_ty(ty, self),
+        }
     }
 
     fn flat_map_foreign_item(
         &mut self,
-        foreign_item: P<ast::ForeignItem>,
+        mut foreign_item: P<ast::ForeignItem>,
     ) -> SmallVec<[P<ast::ForeignItem>; 1]> {
-        let mut foreign_item = configure!(self, foreign_item);
-
-        if let Some(attr) = self.take_first_attr(&mut foreign_item) {
-            return self
-                .collect_attr(
-                    attr,
-                    Annotatable::ForeignItem(foreign_item),
-                    AstFragmentKind::ForeignItems,
-                )
-                .make_foreign_items();
-        }
-
-        match foreign_item.kind {
-            ast::ForeignItemKind::MacCall(..) => {
-                self.check_attributes(&foreign_item.attrs);
-                foreign_item.and_then(|item| match item.kind {
-                    ast::ForeignItemKind::MacCall(mac) => self
-                        .collect_bang(mac, item.span, AstFragmentKind::ForeignItems)
+        loop {
+            return match self.take_first_attr(&mut foreign_item) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        foreign_item.attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::ForeignItem(foreign_item),
+                            AstFragmentKind::ForeignItems,
+                        )
                         .make_foreign_items(),
-                    _ => unreachable!(),
-                })
-            }
-            _ => noop_flat_map_foreign_item(foreign_item, self),
+                },
+                None => match foreign_item.kind {
+                    ast::ForeignItemKind::MacCall(..) => {
+                        self.check_attributes(&foreign_item.attrs);
+                        foreign_item.and_then(|item| match item.kind {
+                            ast::ForeignItemKind::MacCall(mac) => self
+                                .collect_bang(mac, item.span, AstFragmentKind::ForeignItems)
+                                .make_foreign_items(),
+                            _ => unreachable!(),
+                        })
+                    }
+                    _ => noop_flat_map_foreign_item(foreign_item, self),
+                },
+            };
         }
     }
 
     fn flat_map_generic_param(
         &mut self,
-        param: ast::GenericParam,
+        mut param: ast::GenericParam,
     ) -> SmallVec<[ast::GenericParam; 1]> {
-        let mut param = configure!(self, param);
-
-        if let Some(attr) = self.take_first_attr(&mut param) {
-            return self
-                .collect_attr(
-                    attr,
-                    Annotatable::GenericParam(param),
-                    AstFragmentKind::GenericParams,
-                )
-                .make_generic_params();
+        loop {
+            return match self.take_first_attr(&mut param) {
+                Some((attr, pos, derives)) => match attr.name_or_empty() {
+                    sym::cfg => {
+                        if self.cfg.cfg_true(&attr) {
+                            continue;
+                        }
+                        Default::default()
+                    }
+                    sym::cfg_attr => {
+                        param.visit_attrs(|attrs| {
+                            attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr));
+                        });
+                        continue;
+                    }
+                    _ => self
+                        .collect_attr(
+                            (attr, pos, derives),
+                            Annotatable::GenericParam(param),
+                            AstFragmentKind::GenericParams,
+                        )
+                        .make_generic_params(),
+                },
+                None => noop_flat_map_generic_param(param, self),
+            };
         }
-
-        noop_flat_map_generic_param(param, self)
     }
 
     fn visit_attribute(&mut self, at: &mut ast::Attribute) {
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index de9c1882c7d48..f452ea53b8a1e 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -1252,7 +1252,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the
         hostflags.push(format!("-Lnative={}", builder.test_helpers_out(compiler.host).display()));
         if builder.is_fuse_ld_lld(compiler.host) {
             hostflags.push("-Clink-args=-fuse-ld=lld".to_string());
-            hostflags.push("-Clink-arg=-Wl,--threads=1".to_string());
+            // hostflags.push("-Clink-arg=-Wl,--threads=1".to_string());
         }
         cmd.arg("--host-rustcflags").arg(hostflags.join(" "));
 
@@ -1260,7 +1260,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the
         targetflags.push(format!("-Lnative={}", builder.test_helpers_out(target).display()));
         if builder.is_fuse_ld_lld(target) {
             targetflags.push("-Clink-args=-fuse-ld=lld".to_string());
-            targetflags.push("-Clink-arg=-Wl,--threads=1".to_string());
+            // targetflags.push("-Clink-arg=-Wl,--threads=1".to_string());
         }
         cmd.arg("--target-rustcflags").arg(targetflags.join(" "));
 
diff --git a/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.rs b/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.rs
index 4c96d6e7ca17d..d909dd6c19e9c 100644
--- a/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.rs
+++ b/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.rs
@@ -1,13 +1,14 @@
 // Check that `#[cfg_attr($PREDICATE,)]` triggers the `unused_attribute` lint.
 
+// check-pass
 // compile-flags: --cfg TRUE
 
 #![deny(unused)]
 
-#[cfg_attr(FALSE,)] //~ ERROR unused attribute
+#[cfg_attr(FALSE,)]
 fn _f() {}
 
-#[cfg_attr(TRUE,)] //~ ERROR unused attribute
+#[cfg_attr(TRUE,)]
 fn _g() {}
 
 fn main() {}
diff --git a/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.stderr b/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.stderr
deleted file mode 100644
index 67cb6530e3831..0000000000000
--- a/src/test/ui/conditional-compilation/cfg-attr-empty-is-unused.stderr
+++ /dev/null
@@ -1,21 +0,0 @@
-error: unused attribute
-  --> $DIR/cfg-attr-empty-is-unused.rs:7:1
-   |
-LL | #[cfg_attr(FALSE,)]
-   | ^^^^^^^^^^^^^^^^^^^
-   |
-note: the lint level is defined here
-  --> $DIR/cfg-attr-empty-is-unused.rs:5:9
-   |
-LL | #![deny(unused)]
-   |         ^^^^^^
-   = note: `#[deny(unused_attributes)]` implied by `#[deny(unused)]`
-
-error: unused attribute
-  --> $DIR/cfg-attr-empty-is-unused.rs:10:1
-   |
-LL | #[cfg_attr(TRUE,)]
-   | ^^^^^^^^^^^^^^^^^^
-
-error: aborting due to 2 previous errors
-
diff --git a/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.rs b/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.rs
index 408eaffccf7d9..7f0648b381dbd 100644
--- a/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.rs
+++ b/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.rs
@@ -29,7 +29,6 @@ macro_rules! generate_s10 {
     ($expr: expr) => {
         #[cfg(feature = $expr)]
         //~^ ERROR expected unsuffixed literal or identifier, found `concat!("nonexistent")`
-        //~| ERROR expected unsuffixed literal or identifier, found `concat!("nonexistent")`
         struct S10;
     }
 }
diff --git a/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.stderr b/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.stderr
index 8ae2ff16a2b4f..44063dd1d65d7 100644
--- a/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.stderr
+++ b/src/test/ui/conditional-compilation/cfg-attr-syntax-validation.stderr
@@ -63,18 +63,7 @@ LL | generate_s10!(concat!("nonexistent"));
    |
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: expected unsuffixed literal or identifier, found `concat!("nonexistent")`
-  --> $DIR/cfg-attr-syntax-validation.rs:30:25
-   |
-LL |         #[cfg(feature = $expr)]
-   |                         ^^^^^
-...
-LL | generate_s10!(concat!("nonexistent"));
-   | -------------------------------------- in this macro invocation
-   |
-   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error: aborting due to 11 previous errors
+error: aborting due to 10 previous errors
 
 Some errors have detailed explanations: E0537, E0565.
 For more information about an error, try `rustc --explain E0537`.
diff --git a/src/test/ui/macros/macro-attributes.rs b/src/test/ui/macros/macro-attributes.rs
index d382e8b719713..267d2118732dd 100644
--- a/src/test/ui/macros/macro-attributes.rs
+++ b/src/test/ui/macros/macro-attributes.rs
@@ -3,7 +3,7 @@
 macro_rules! compiles_fine {
     (#[$at:meta]) => {
         // test that the different types of attributes work
-        #[attribute]
+        #[rustfmt::attribute]
         /// Documentation!
         #[$at]
 
@@ -15,9 +15,9 @@ macro_rules! compiles_fine {
 }
 
 // item
-compiles_fine!(#[foo]);
+compiles_fine!(#[doc = "foo"]);
 
 pub fn main() {
     // statement
-    compiles_fine!(#[bar]);
+    compiles_fine!(#[doc = "bar"]);
 }
diff --git a/src/test/ui/proc-macro/cfg-eval-fail.rs b/src/test/ui/proc-macro/cfg-eval-fail.rs
index 379491f3126b0..ca5034cbeca3e 100644
--- a/src/test/ui/proc-macro/cfg-eval-fail.rs
+++ b/src/test/ui/proc-macro/cfg-eval-fail.rs
@@ -5,5 +5,4 @@ fn main() {
     let _ = #[cfg_eval] #[cfg(FALSE)] 0;
     //~^ ERROR removing an expression is not supported in this position
     //~| ERROR removing an expression is not supported in this position
-    //~| ERROR removing an expression is not supported in this position
 }
diff --git a/src/test/ui/proc-macro/cfg-eval-fail.stderr b/src/test/ui/proc-macro/cfg-eval-fail.stderr
index 010ac006b0bee..69e92114ba518 100644
--- a/src/test/ui/proc-macro/cfg-eval-fail.stderr
+++ b/src/test/ui/proc-macro/cfg-eval-fail.stderr
@@ -10,11 +10,5 @@ error: removing an expression is not supported in this position
 LL |     let _ = #[cfg_eval] #[cfg(FALSE)] 0;
    |                         ^^^^^^^^^^^^^
 
-error: removing an expression is not supported in this position
-  --> $DIR/cfg-eval-fail.rs:5:25
-   |
-LL |     let _ = #[cfg_eval] #[cfg(FALSE)] 0;
-   |                         ^^^^^^^^^^^^^
-
-error: aborting due to 3 previous errors
+error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/proc-macro/cfg-eval.rs b/src/test/ui/proc-macro/cfg-eval.rs
index fa6d015e48eb8..2a1c4b43c7113 100644
--- a/src/test/ui/proc-macro/cfg-eval.rs
+++ b/src/test/ui/proc-macro/cfg-eval.rs
@@ -35,3 +35,7 @@ fn main() {
     let _ = #[cfg_eval] #[print_attr] #[cfg_attr(not(FALSE), rustc_dummy)]
     (#[cfg(FALSE)] 0, #[cfg(all(/*true*/))] 1,);
 }
+
+#[derive(Clone)]
+#[cfg(FALSE)]
+struct S3 {}
diff --git a/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed b/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed
index d592438009a48..ebb1abadeb90c 100644
--- a/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed
+++ b/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed
@@ -8,7 +8,8 @@
 
 // The suggestion span should include the attribute.
 
-
+#[cfg(blandiloquence)]
+ //~ HELP remove it
 //~^ ERROR unused extern crate
 
 fn main() {}
diff --git a/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs b/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs
index a948baee53c4c..d0dcccba0a6ac 100644
--- a/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs
+++ b/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs
@@ -8,8 +8,8 @@
 
 // The suggestion span should include the attribute.
 
-#[cfg(blandiloquence)] //~ HELP remove it
-extern crate edition_lint_paths;
+#[cfg(blandiloquence)]
+extern crate edition_lint_paths; //~ HELP remove it
 //~^ ERROR unused extern crate
 
 fn main() {}
diff --git a/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr b/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr
index 2ef97e7f20e9f..f6a85b7fdbe70 100644
--- a/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr
+++ b/src/test/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr
@@ -1,11 +1,8 @@
 error: unused extern crate
   --> $DIR/issue-54400-unused-extern-crate-attr-span.rs:12:1
    |
-LL | / #[cfg(blandiloquence)]
-LL | | extern crate edition_lint_paths;
-   | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
-   | |________________________________|
-   |                                  help: remove it
+LL | extern crate edition_lint_paths;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
    |
 note: the lint level is defined here
   --> $DIR/issue-54400-unused-extern-crate-attr-span.rs:6:9