about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast/src/expand/mod.rs17
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs9
-rw-r--r--compiler/rustc_attr_data_structures/src/attributes.rs27
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg.rs464
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg_old.rs247
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs1
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs97
-rw-r--r--compiler/rustc_attr_parsing/src/lib.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs8
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs2
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0536.md16
-rw-r--r--compiler/rustc_expand/src/base.rs10
-rw-r--r--compiler/rustc_expand/src/config.rs52
-rw-r--r--compiler/rustc_expand/src/expand.rs38
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/src/arena.rs2
-rw-r--r--compiler/rustc_middle/src/query/mod.rs2
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs3
-rw-r--r--compiler/rustc_middle/src/ty/parameterized.rs2
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs14
-rw-r--r--compiler/rustc_resolve/src/def_collector.rs1
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs21
-rw-r--r--compiler/rustc_resolve/src/lib.rs2
-rw-r--r--compiler/rustc_resolve/src/macros.rs17
-rw-r--r--tests/ui/attributes/malformed-attrs.rs2
-rw-r--r--tests/ui/attributes/malformed-attrs.stderr7
-rw-r--r--tests/ui/cfg/cfg-target-compact-errors.rs12
-rw-r--r--tests/ui/cfg/cfg-target-compact-errors.stderr47
-rw-r--r--tests/ui/cfg/cfg-version/syntax.stderr4
-rw-r--r--tests/ui/cfg/diagnostics-cross-crate.stderr12
-rw-r--r--tests/ui/cfg/diagnostics-reexport-2.stderr20
-rw-r--r--tests/ui/cfg/diagnostics-reexport.stderr16
-rw-r--r--tests/ui/cfg/diagnostics-same-crate.stderr16
-rw-r--r--tests/ui/check-cfg/compact-names.rs4
-rw-r--r--tests/ui/check-cfg/compact-names.stderr11
-rw-r--r--tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs25
-rw-r--r--tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr63
-rw-r--r--tests/ui/macros/macro-outer-attributes.stderr14
-rw-r--r--tests/ui/span/E0536.rs7
-rw-r--r--tests/ui/span/E0536.stderr6
41 files changed, 897 insertions, 428 deletions
diff --git a/compiler/rustc_ast/src/expand/mod.rs b/compiler/rustc_ast/src/expand/mod.rs
index 323a8fab6d5..069bff67b97 100644
--- a/compiler/rustc_ast/src/expand/mod.rs
+++ b/compiler/rustc_ast/src/expand/mod.rs
@@ -1,24 +1,7 @@
 //! Definitions shared by macros / syntax extensions and e.g. `rustc_middle`.
 
 use rustc_macros::{Decodable, Encodable, HashStable_Generic};
-use rustc_span::Ident;
-use rustc_span::def_id::DefId;
-
-use crate::MetaItem;
 
 pub mod allocator;
 pub mod autodiff_attrs;
 pub mod typetree;
-
-#[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)]
-pub struct StrippedCfgItem<ModId = DefId> {
-    pub parent_module: ModId,
-    pub ident: Ident,
-    pub cfg: MetaItem,
-}
-
-impl<ModId> StrippedCfgItem<ModId> {
-    pub fn map_mod_id<New>(self, f: impl FnOnce(ModId) -> New) -> StrippedCfgItem<New> {
-        StrippedCfgItem { parent_module: f(self.parent_module), ident: self.ident, cfg: self.cfg }
-    }
-}
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 348fe2ee40a..1c96a375035 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -42,7 +42,7 @@ use std::sync::Arc;
 
 use rustc_ast::node_id::NodeMap;
 use rustc_ast::{self as ast, *};
-use rustc_attr_parsing::{AttributeParser, OmitDoc};
+use rustc_attr_parsing::{AttributeParser, Late, OmitDoc};
 use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_data_structures::sorted_map::SortedMap;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
@@ -192,7 +192,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
             // interact with `gen`/`async gen` blocks
             allow_async_iterator: [sym::gen_future, sym::async_iterator].into(),
 
-            attribute_parser: AttributeParser::new(tcx.sess, tcx.features(), registered_tools),
+            attribute_parser: AttributeParser::new(
+                tcx.sess,
+                tcx.features(),
+                registered_tools,
+                Late,
+            ),
             delayed_lints: Vec::new(),
         }
     }
diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs
index 9b14ed88978..995ac514d8d 100644
--- a/compiler/rustc_attr_data_structures/src/attributes.rs
+++ b/compiler/rustc_attr_data_structures/src/attributes.rs
@@ -3,7 +3,7 @@ use rustc_ast::token::CommentKind;
 use rustc_ast::{self as ast, AttrStyle};
 use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
 use rustc_span::hygiene::Transparency;
-use rustc_span::{Span, Symbol};
+use rustc_span::{Ident, Span, Symbol};
 use thin_vec::ThinVec;
 
 use crate::{DefaultBodyStability, PartialConstStability, PrintAttribute, RustcVersion, Stability};
@@ -69,6 +69,7 @@ pub enum ReprAttr {
     ReprAlign(Align),
 }
 pub use ReprAttr::*;
+use rustc_span::def_id::DefId;
 
 pub enum TransparencyError {
     UnknownTransparency(Symbol, Span),
@@ -140,6 +141,30 @@ pub enum UsedBy {
     Linker,
 }
 
+#[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)]
+pub struct StrippedCfgItem<ModId = DefId> {
+    pub parent_module: ModId,
+    pub ident: Ident,
+    pub cfg: (CfgEntry, Span),
+}
+
+impl<ModId> StrippedCfgItem<ModId> {
+    pub fn map_mod_id<New>(self, f: impl FnOnce(ModId) -> New) -> StrippedCfgItem<New> {
+        StrippedCfgItem { parent_module: f(self.parent_module), ident: self.ident, cfg: self.cfg }
+    }
+}
+
+#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(HashStable_Generic, PrintAttribute)]
+pub enum CfgEntry {
+    All(ThinVec<CfgEntry>, Span),
+    Any(ThinVec<CfgEntry>, Span),
+    Not(Box<CfgEntry>, Span),
+    Bool(bool, Span),
+    NameValue { name: Symbol, name_span: Span, value: Option<(Symbol, Span)>, span: Span },
+    Version(Option<RustcVersion>, Span),
+}
+
 /// Represents parsed *built-in* inert attributes.
 ///
 /// ## Overview
diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
index a8d9229cbc3..a56855b3bd3 100644
--- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
@@ -1,247 +1,291 @@
-use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NodeId};
-use rustc_ast_pretty::pprust;
-use rustc_attr_data_structures::RustcVersion;
-use rustc_feature::{Features, GatedCfg, find_gated_cfg};
+use rustc_ast::{LitKind, NodeId};
+use rustc_attr_data_structures::{CfgEntry, RustcVersion};
+use rustc_feature::{AttributeTemplate, Features, template};
 use rustc_session::Session;
 use rustc_session::config::ExpectedValues;
+use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::lint::builtin::UNEXPECTED_CFGS;
-use rustc_session::lint::{BuiltinLintDiag, Lint};
 use rustc_session::parse::feature_err;
 use rustc_span::{Span, Symbol, sym};
+use thin_vec::ThinVec;
 
-use crate::session_diagnostics::{self, UnsupportedLiteralReason};
-use crate::{fluent_generated, parse_version};
+use crate::context::{AcceptContext, Stage};
+use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
+use crate::{
+    CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
+};
 
-/// Emitter of a builtin lint from `cfg_matches`.
-///
-/// Used to support emitting a lint (currently on check-cfg), either:
-///  - as an early buffered lint (in `rustc`)
-///  - or has a "normal" lint from HIR (in `rustdoc`)
-pub trait CfgMatchesLintEmitter {
-    fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag);
-}
+pub const CFG_TEMPLATE: AttributeTemplate = template!(List: "predicate");
 
-impl CfgMatchesLintEmitter for NodeId {
-    fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag) {
-        sess.psess.buffer_lint(lint, sp, *self, diag);
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct Condition {
-    pub name: Symbol,
-    pub name_span: Span,
-    pub value: Option<Symbol>,
-    pub value_span: Option<Span>,
-    pub span: Span,
+pub fn parse_cfg_attr<'c, S: Stage>(
+    cx: &'c mut AcceptContext<'_, '_, S>,
+    args: &'c ArgParser<'_>,
+) -> Option<CfgEntry> {
+    let ArgParser::List(list) = args else {
+        cx.expected_list(cx.attr_span);
+        return None;
+    };
+    let Some(single) = list.single() else {
+        cx.expected_single_argument(list.span);
+        return None;
+    };
+    parse_cfg_entry(cx, single)
 }
 
-/// Tests if a cfg-pattern matches the cfg set
-pub fn cfg_matches(
-    cfg: &MetaItemInner,
-    sess: &Session,
-    lint_emitter: impl CfgMatchesLintEmitter,
-    features: Option<&Features>,
-) -> bool {
-    eval_condition(cfg, sess, features, &mut |cfg| {
-        try_gate_cfg(cfg.name, cfg.span, sess, features);
-        match sess.psess.check_config.expecteds.get(&cfg.name) {
-            Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
-                lint_emitter.emit_span_lint(
-                    sess,
-                    UNEXPECTED_CFGS,
-                    cfg.span,
-                    BuiltinLintDiag::UnexpectedCfgValue(
-                        (cfg.name, cfg.name_span),
-                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
-                    ),
-                );
+fn parse_cfg_entry<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    item: &MetaItemOrLitParser<'_>,
+) -> Option<CfgEntry> {
+    Some(match item {
+        MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
+            ArgParser::List(list) => match meta.path().word_sym() {
+                Some(sym::not) => {
+                    let Some(single) = list.single() else {
+                        cx.expected_single_argument(list.span);
+                        return None;
+                    };
+                    CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
+                }
+                Some(sym::any) => CfgEntry::Any(
+                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
+                    list.span,
+                ),
+                Some(sym::all) => CfgEntry::All(
+                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
+                    list.span,
+                ),
+                Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
+                Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
+                _ => {
+                    cx.emit_err(session_diagnostics::InvalidPredicate {
+                        span: meta.span(),
+                        predicate: meta.path().to_string(),
+                    });
+                    return None;
+                }
+            },
+            a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
+                let Some(name) = meta.path().word_sym() else {
+                    cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
+                        span: meta.path().span(),
+                    });
+                    return None;
+                };
+                parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
             }
-            None if sess.psess.check_config.exhaustive_names => {
-                lint_emitter.emit_span_lint(
-                    sess,
-                    UNEXPECTED_CFGS,
-                    cfg.span,
-                    BuiltinLintDiag::UnexpectedCfgName(
-                        (cfg.name, cfg.name_span),
-                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
-                    ),
-                );
+        },
+        MetaItemOrLitParser::Lit(lit) => match lit.kind {
+            LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
+            _ => {
+                cx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: lit.span });
+                return None;
             }
-            _ => { /* not unexpected */ }
-        }
-        sess.psess.config.contains(&(cfg.name, cfg.value))
+        },
+        MetaItemOrLitParser::Err(_, _) => return None,
     })
 }
 
-fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
-    let gate = find_gated_cfg(|sym| sym == name);
-    if let (Some(feats), Some(gated_cfg)) = (features, gate) {
-        gate_cfg(gated_cfg, span, sess, feats);
-    }
+fn parse_cfg_entry_version<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    list: &MetaItemListParser<'_>,
+    meta_span: Span,
+) -> Option<CfgEntry> {
+    try_gate_cfg(sym::version, meta_span, cx.sess(), Some(cx.features()));
+    let Some(version) = list.single() else {
+        cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span });
+        return None;
+    };
+    let Some(version_lit) = version.lit() else {
+        cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() });
+        return None;
+    };
+    let Some(version_str) = version_lit.value_str() else {
+        cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span });
+        return None;
+    };
+
+    let min_version = parse_version(version_str).or_else(|| {
+        cx.sess()
+            .dcx()
+            .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
+        None
+    });
+
+    Some(CfgEntry::Version(min_version, list.span))
 }
 
-#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
-fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
-    let (cfg, feature, has_feature) = gated_cfg;
-    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
-        let explain = format!("`cfg({cfg})` is experimental and subject to change");
-        feature_err(sess, *feature, cfg_span, explain).emit();
+fn parse_cfg_entry_target<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    list: &MetaItemListParser<'_>,
+    meta_span: Span,
+) -> Option<CfgEntry> {
+    if !cx.features().cfg_target_compact() {
+        feature_err(
+            cx.sess(),
+            sym::cfg_target_compact,
+            meta_span,
+            fluent_generated::attr_parsing_unstable_cfg_target_compact,
+        )
+        .emit();
     }
-}
 
-/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
-/// evaluate individual items.
-pub fn eval_condition(
-    cfg: &MetaItemInner,
-    sess: &Session,
-    features: Option<&Features>,
-    eval: &mut impl FnMut(Condition) -> bool,
-) -> bool {
-    let dcx = sess.dcx();
-
-    let cfg = match cfg {
-        MetaItemInner::MetaItem(meta_item) => meta_item,
-        MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
-            return *b;
-        }
-        _ => {
-            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
-                span: cfg.span(),
-                reason: UnsupportedLiteralReason::CfgBoolean,
-                is_bytestr: false,
-                start_point_span: sess.source_map().start_point(cfg.span()),
+    let mut result = ThinVec::new();
+    for sub_item in list.mixed() {
+        // First, validate that this is a NameValue item
+        let Some(sub_item) = sub_item.meta_item() else {
+            cx.expected_name_value(sub_item.span(), None);
+            continue;
+        };
+        let Some(nv) = sub_item.args().name_value() else {
+            cx.expected_name_value(sub_item.span(), None);
+            continue;
+        };
+
+        // Then, parse it as a name-value item
+        let Some(name) = sub_item.path().word_sym() else {
+            cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
+                span: sub_item.path().span(),
             });
-            return false;
+            return None;
+        };
+        let name = Symbol::intern(&format!("target_{name}"));
+        if let Some(cfg) =
+            parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
+        {
+            result.push(cfg);
+        }
+    }
+    Some(CfgEntry::All(result, list.span))
+}
+
+fn parse_name_value<S: Stage>(
+    name: Symbol,
+    name_span: Span,
+    value: Option<&NameValueParser>,
+    span: Span,
+    cx: &mut AcceptContext<'_, '_, S>,
+) -> Option<CfgEntry> {
+    try_gate_cfg(name, span, cx.sess(), cx.features_option());
+
+    let value = match value {
+        None => None,
+        Some(value) => {
+            let Some(value_str) = value.value_as_str() else {
+                cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
+                return None;
+            };
+            Some((value_str, value.value_span))
         }
     };
 
-    match &cfg.kind {
-        MetaItemKind::List(mis) if cfg.has_name(sym::version) => {
-            try_gate_cfg(sym::version, cfg.span, sess, features);
-            let (min_version, span) = match &mis[..] {
-                [MetaItemInner::Lit(MetaItemLit { kind: LitKind::Str(sym, ..), span, .. })] => {
-                    (sym, span)
-                }
-                [
-                    MetaItemInner::Lit(MetaItemLit { span, .. })
-                    | MetaItemInner::MetaItem(MetaItem { span, .. }),
-                ] => {
-                    dcx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: *span });
-                    return false;
+    Some(CfgEntry::NameValue { name, name_span, value, span })
+}
+
+pub fn eval_config_entry(
+    sess: &Session,
+    cfg_entry: &CfgEntry,
+    id: NodeId,
+    features: Option<&Features>,
+) -> EvalConfigResult {
+    match cfg_entry {
+        CfgEntry::All(subs, ..) => {
+            let mut all = None;
+            for sub in subs {
+                let res = eval_config_entry(sess, sub, id, features);
+                // We cannot short-circuit because `eval_config_entry` emits some lints
+                if !res.as_bool() {
+                    all.get_or_insert(res);
                 }
-                [..] => {
-                    dcx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral {
-                        span: cfg.span,
-                    });
-                    return false;
+            }
+            all.unwrap_or_else(|| EvalConfigResult::True)
+        }
+        CfgEntry::Any(subs, span) => {
+            let mut any = None;
+            for sub in subs {
+                let res = eval_config_entry(sess, sub, id, features);
+                // We cannot short-circuit because `eval_config_entry` emits some lints
+                if res.as_bool() {
+                    any.get_or_insert(res);
                 }
-            };
-            let Some(min_version) = parse_version(*min_version) else {
-                dcx.emit_warn(session_diagnostics::UnknownVersionLiteral { span: *span });
-                return false;
-            };
-
-            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
-            if sess.psess.assume_incomplete_release {
-                RustcVersion::current_overridable() > min_version
+            }
+            any.unwrap_or_else(|| EvalConfigResult::False {
+                reason: cfg_entry.clone(),
+                reason_span: *span,
+            })
+        }
+        CfgEntry::Not(sub, span) => {
+            if eval_config_entry(sess, sub, id, features).as_bool() {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
             } else {
-                RustcVersion::current_overridable() >= min_version
+                EvalConfigResult::True
             }
         }
-        MetaItemKind::List(mis) => {
-            for mi in mis.iter() {
-                if mi.meta_item_or_bool().is_none() {
-                    dcx.emit_err(session_diagnostics::UnsupportedLiteral {
-                        span: mi.span(),
-                        reason: UnsupportedLiteralReason::Generic,
-                        is_bytestr: false,
-                        start_point_span: sess.source_map().start_point(mi.span()),
-                    });
-                    return false;
-                }
+        CfgEntry::Bool(b, span) => {
+            if *b {
+                EvalConfigResult::True
+            } else {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
             }
-
-            // The unwraps below may look dangerous, but we've already asserted
-            // that they won't fail with the loop above.
-            match cfg.name() {
-                Some(sym::any) => mis
-                    .iter()
-                    // We don't use any() here, because we want to evaluate all cfg condition
-                    // as eval_condition can (and does) extra checks
-                    .fold(false, |res, mi| res | eval_condition(mi, sess, features, eval)),
-                Some(sym::all) => mis
-                    .iter()
-                    // We don't use all() here, because we want to evaluate all cfg condition
-                    // as eval_condition can (and does) extra checks
-                    .fold(true, |res, mi| res & eval_condition(mi, sess, features, eval)),
-                Some(sym::not) => {
-                    let [mi] = mis.as_slice() else {
-                        dcx.emit_err(session_diagnostics::ExpectedOneCfgPattern { span: cfg.span });
-                        return false;
-                    };
-
-                    !eval_condition(mi, sess, features, eval)
-                }
-                Some(sym::target) => {
-                    if let Some(features) = features
-                        && !features.cfg_target_compact()
-                    {
-                        feature_err(
-                            sess,
-                            sym::cfg_target_compact,
-                            cfg.span,
-                            fluent_generated::attr_parsing_unstable_cfg_target_compact,
-                        )
-                        .emit();
-                    }
-
-                    mis.iter().fold(true, |res, mi| {
-                        let Some(mut mi) = mi.meta_item().cloned() else {
-                            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier {
-                                span: mi.span(),
-                            });
-                            return false;
-                        };
-
-                        if let [seg, ..] = &mut mi.path.segments[..] {
-                            seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name));
-                        }
-
-                        res & eval_condition(&MetaItemInner::MetaItem(mi), sess, features, eval)
-                    })
+        }
+        CfgEntry::NameValue { name, name_span, value, span } => {
+            match sess.psess.check_config.expecteds.get(name) {
+                Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => {
+                    id.emit_span_lint(
+                        sess,
+                        UNEXPECTED_CFGS,
+                        *span,
+                        BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
+                    );
                 }
-                _ => {
-                    dcx.emit_err(session_diagnostics::InvalidPredicate {
-                        span: cfg.span,
-                        predicate: pprust::path_to_string(&cfg.path),
-                    });
-                    false
+                None if sess.psess.check_config.exhaustive_names => {
+                    id.emit_span_lint(
+                        sess,
+                        UNEXPECTED_CFGS,
+                        *span,
+                        BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
+                    );
                 }
+                _ => { /* not unexpected */ }
+            }
+
+            if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {
+                EvalConfigResult::True
+            } else {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
             }
         }
-        MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => {
-            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: cfg.path.span });
-            true
-        }
-        MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
-            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
-                span: lit.span,
-                reason: UnsupportedLiteralReason::CfgString,
-                is_bytestr: lit.kind.is_bytestr(),
-                start_point_span: sess.source_map().start_point(lit.span),
-            });
-            true
+        CfgEntry::Version(min_version, version_span) => {
+            let Some(min_version) = min_version else {
+                return EvalConfigResult::False {
+                    reason: cfg_entry.clone(),
+                    reason_span: *version_span,
+                };
+            };
+            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
+            let min_version_ok = if sess.psess.assume_incomplete_release {
+                RustcVersion::current_overridable() > *min_version
+            } else {
+                RustcVersion::current_overridable() >= *min_version
+            };
+            if min_version_ok {
+                EvalConfigResult::True
+            } else {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
+            }
         }
-        MetaItemKind::Word | MetaItemKind::NameValue(..) => {
-            let ident = cfg.ident().expect("multi-segment cfg predicate");
-            eval(Condition {
-                name: ident.name,
-                name_span: ident.span,
-                value: cfg.value_str(),
-                value_span: cfg.name_value_literal_span(),
-                span: cfg.span,
-            })
+    }
+}
+
+pub enum EvalConfigResult {
+    True,
+    False { reason: CfgEntry, reason_span: Span },
+}
+
+impl EvalConfigResult {
+    pub fn as_bool(&self) -> bool {
+        match self {
+            EvalConfigResult::True => true,
+            EvalConfigResult::False { .. } => false,
         }
     }
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs
new file mode 100644
index 00000000000..c5025a8b6ea
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs
@@ -0,0 +1,247 @@
+use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NodeId};
+use rustc_ast_pretty::pprust;
+use rustc_attr_data_structures::RustcVersion;
+use rustc_feature::{Features, GatedCfg, find_gated_cfg};
+use rustc_session::Session;
+use rustc_session::config::ExpectedValues;
+use rustc_session::lint::builtin::UNEXPECTED_CFGS;
+use rustc_session::lint::{BuiltinLintDiag, Lint};
+use rustc_session::parse::feature_err;
+use rustc_span::{Span, Symbol, sym};
+
+use crate::session_diagnostics::{self, UnsupportedLiteralReason};
+use crate::{fluent_generated, parse_version};
+
+/// Emitter of a builtin lint from `cfg_matches`.
+///
+/// Used to support emitting a lint (currently on check-cfg), either:
+///  - as an early buffered lint (in `rustc`)
+///  - or has a "normal" lint from HIR (in `rustdoc`)
+pub trait CfgMatchesLintEmitter {
+    fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag);
+}
+
+impl CfgMatchesLintEmitter for NodeId {
+    fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag) {
+        sess.psess.buffer_lint(lint, sp, *self, diag);
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Condition {
+    pub name: Symbol,
+    pub name_span: Span,
+    pub value: Option<Symbol>,
+    pub value_span: Option<Span>,
+    pub span: Span,
+}
+
+/// Tests if a cfg-pattern matches the cfg set
+pub fn cfg_matches(
+    cfg: &MetaItemInner,
+    sess: &Session,
+    lint_emitter: impl CfgMatchesLintEmitter,
+    features: Option<&Features>,
+) -> bool {
+    eval_condition(cfg, sess, features, &mut |cfg| {
+        try_gate_cfg(cfg.name, cfg.span, sess, features);
+        match sess.psess.check_config.expecteds.get(&cfg.name) {
+            Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
+                lint_emitter.emit_span_lint(
+                    sess,
+                    UNEXPECTED_CFGS,
+                    cfg.span,
+                    BuiltinLintDiag::UnexpectedCfgValue(
+                        (cfg.name, cfg.name_span),
+                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
+                    ),
+                );
+            }
+            None if sess.psess.check_config.exhaustive_names => {
+                lint_emitter.emit_span_lint(
+                    sess,
+                    UNEXPECTED_CFGS,
+                    cfg.span,
+                    BuiltinLintDiag::UnexpectedCfgName(
+                        (cfg.name, cfg.name_span),
+                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
+                    ),
+                );
+            }
+            _ => { /* not unexpected */ }
+        }
+        sess.psess.config.contains(&(cfg.name, cfg.value))
+    })
+}
+
+pub fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
+    let gate = find_gated_cfg(|sym| sym == name);
+    if let (Some(feats), Some(gated_cfg)) = (features, gate) {
+        gate_cfg(gated_cfg, span, sess, feats);
+    }
+}
+
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
+    let (cfg, feature, has_feature) = gated_cfg;
+    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
+        let explain = format!("`cfg({cfg})` is experimental and subject to change");
+        feature_err(sess, *feature, cfg_span, explain).emit();
+    }
+}
+
+/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
+/// evaluate individual items.
+pub fn eval_condition(
+    cfg: &MetaItemInner,
+    sess: &Session,
+    features: Option<&Features>,
+    eval: &mut impl FnMut(Condition) -> bool,
+) -> bool {
+    let dcx = sess.dcx();
+
+    let cfg = match cfg {
+        MetaItemInner::MetaItem(meta_item) => meta_item,
+        MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
+            return *b;
+        }
+        _ => {
+            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
+                span: cfg.span(),
+                reason: UnsupportedLiteralReason::CfgBoolean,
+                is_bytestr: false,
+                start_point_span: sess.source_map().start_point(cfg.span()),
+            });
+            return false;
+        }
+    };
+
+    match &cfg.kind {
+        MetaItemKind::List(mis) if cfg.has_name(sym::version) => {
+            try_gate_cfg(sym::version, cfg.span, sess, features);
+            let (min_version, span) = match &mis[..] {
+                [MetaItemInner::Lit(MetaItemLit { kind: LitKind::Str(sym, ..), span, .. })] => {
+                    (sym, span)
+                }
+                [
+                    MetaItemInner::Lit(MetaItemLit { span, .. })
+                    | MetaItemInner::MetaItem(MetaItem { span, .. }),
+                ] => {
+                    dcx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: *span });
+                    return false;
+                }
+                [..] => {
+                    dcx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral {
+                        span: cfg.span,
+                    });
+                    return false;
+                }
+            };
+            let Some(min_version) = parse_version(*min_version) else {
+                dcx.emit_warn(session_diagnostics::UnknownVersionLiteral { span: *span });
+                return false;
+            };
+
+            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
+            if sess.psess.assume_incomplete_release {
+                RustcVersion::current_overridable() > min_version
+            } else {
+                RustcVersion::current_overridable() >= min_version
+            }
+        }
+        MetaItemKind::List(mis) => {
+            for mi in mis.iter() {
+                if mi.meta_item_or_bool().is_none() {
+                    dcx.emit_err(session_diagnostics::UnsupportedLiteral {
+                        span: mi.span(),
+                        reason: UnsupportedLiteralReason::Generic,
+                        is_bytestr: false,
+                        start_point_span: sess.source_map().start_point(mi.span()),
+                    });
+                    return false;
+                }
+            }
+
+            // The unwraps below may look dangerous, but we've already asserted
+            // that they won't fail with the loop above.
+            match cfg.name() {
+                Some(sym::any) => mis
+                    .iter()
+                    // We don't use any() here, because we want to evaluate all cfg condition
+                    // as eval_condition can (and does) extra checks
+                    .fold(false, |res, mi| res | eval_condition(mi, sess, features, eval)),
+                Some(sym::all) => mis
+                    .iter()
+                    // We don't use all() here, because we want to evaluate all cfg condition
+                    // as eval_condition can (and does) extra checks
+                    .fold(true, |res, mi| res & eval_condition(mi, sess, features, eval)),
+                Some(sym::not) => {
+                    let [mi] = mis.as_slice() else {
+                        dcx.emit_err(session_diagnostics::ExpectedOneCfgPattern { span: cfg.span });
+                        return false;
+                    };
+
+                    !eval_condition(mi, sess, features, eval)
+                }
+                Some(sym::target) => {
+                    if let Some(features) = features
+                        && !features.cfg_target_compact()
+                    {
+                        feature_err(
+                            sess,
+                            sym::cfg_target_compact,
+                            cfg.span,
+                            fluent_generated::attr_parsing_unstable_cfg_target_compact,
+                        )
+                        .emit();
+                    }
+
+                    mis.iter().fold(true, |res, mi| {
+                        let Some(mut mi) = mi.meta_item().cloned() else {
+                            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier {
+                                span: mi.span(),
+                            });
+                            return false;
+                        };
+
+                        if let [seg, ..] = &mut mi.path.segments[..] {
+                            seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name));
+                        }
+
+                        res & eval_condition(&MetaItemInner::MetaItem(mi), sess, features, eval)
+                    })
+                }
+                _ => {
+                    dcx.emit_err(session_diagnostics::InvalidPredicate {
+                        span: cfg.span,
+                        predicate: pprust::path_to_string(&cfg.path),
+                    });
+                    false
+                }
+            }
+        }
+        MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => {
+            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: cfg.path.span });
+            true
+        }
+        MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
+            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
+                span: lit.span,
+                reason: UnsupportedLiteralReason::CfgString,
+                is_bytestr: lit.kind.is_bytestr(),
+                start_point_span: sess.source_map().start_point(lit.span),
+            });
+            true
+        }
+        MetaItemKind::Word | MetaItemKind::NameValue(..) => {
+            let ident = cfg.ident().expect("multi-segment cfg predicate");
+            eval(Condition {
+                name: ident.name,
+                name_span: ident.span,
+                value: cfg.value_str(),
+                value_span: cfg.name_value_literal_span(),
+                span: cfg.span,
+            })
+        }
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index 68c716d1a99..200f1381960 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -27,6 +27,7 @@ use crate::session_diagnostics::UnusedMultiple;
 
 pub(crate) mod allow_unstable;
 pub(crate) mod cfg;
+pub(crate) mod cfg_old;
 pub(crate) mod codegen_attrs;
 pub(crate) mod confusables;
 pub(crate) mod deprecation;
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 6c70f2ee4ee..1f6675b4c5a 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -1,6 +1,5 @@
 use std::cell::RefCell;
 use std::collections::BTreeMap;
-use std::marker::PhantomData;
 use std::ops::{Deref, DerefMut};
 use std::sync::LazyLock;
 
@@ -202,7 +201,11 @@ pub trait Stage: Sized + 'static + Sealed {
 
     fn parsers() -> &'static group_type!(Self);
 
-    fn emit_err<'sess>(sess: &'sess Session, diag: impl for<'x> Diagnostic<'x>) -> ErrorGuaranteed;
+    fn emit_err<'sess>(
+        &self,
+        sess: &'sess Session,
+        diag: impl for<'x> Diagnostic<'x>,
+    ) -> ErrorGuaranteed;
 }
 
 // allow because it's a sealed trait
@@ -214,8 +217,16 @@ impl Stage for Early {
     fn parsers() -> &'static group_type!(Self) {
         &early::ATTRIBUTE_PARSERS
     }
-    fn emit_err<'sess>(sess: &'sess Session, diag: impl for<'x> Diagnostic<'x>) -> ErrorGuaranteed {
-        sess.dcx().create_err(diag).delay_as_bug()
+    fn emit_err<'sess>(
+        &self,
+        sess: &'sess Session,
+        diag: impl for<'x> Diagnostic<'x>,
+    ) -> ErrorGuaranteed {
+        if self.emit_errors {
+            sess.dcx().emit_err(diag)
+        } else {
+            sess.dcx().create_err(diag).delay_as_bug()
+        }
     }
 }
 
@@ -228,20 +239,29 @@ impl Stage for Late {
     fn parsers() -> &'static group_type!(Self) {
         &late::ATTRIBUTE_PARSERS
     }
-    fn emit_err<'sess>(tcx: &'sess Session, diag: impl for<'x> Diagnostic<'x>) -> ErrorGuaranteed {
+    fn emit_err<'sess>(
+        &self,
+        tcx: &'sess Session,
+        diag: impl for<'x> Diagnostic<'x>,
+    ) -> ErrorGuaranteed {
         tcx.dcx().emit_err(diag)
     }
 }
 
 /// used when parsing attributes for miscellaneous things *before* ast lowering
-pub struct Early;
+pub struct Early {
+    /// Whether to emit errors or delay them as a bug
+    /// For most attributes, the attribute will be parsed again in the `Late` stage and in this case the errors should be delayed
+    /// But for some, such as `cfg`, the attribute will be removed before the `Late` stage so errors must be emitted
+    pub emit_errors: bool,
+}
 /// used when parsing attributes during ast lowering
 pub struct Late;
 
 /// Context given to every attribute parser when accepting
 ///
 /// Gives [`AttributeParser`]s enough information to create errors, for example.
-pub(crate) struct AcceptContext<'f, 'sess, S: Stage> {
+pub struct AcceptContext<'f, 'sess, S: Stage> {
     pub(crate) shared: SharedContext<'f, 'sess, S>,
     /// The span of the attribute currently being parsed
     pub(crate) attr_span: Span,
@@ -257,7 +277,7 @@ pub(crate) struct AcceptContext<'f, 'sess, S: Stage> {
 
 impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> {
     pub(crate) fn emit_err(&self, diag: impl for<'x> Diagnostic<'x>) -> ErrorGuaranteed {
-        S::emit_err(&self.sess, diag)
+        self.stage.emit_err(&self.sess, diag)
     }
 
     /// Emit a lint. This method is somewhat special, since lints emitted during attribute parsing
@@ -472,7 +492,7 @@ impl<'f, 'sess, S: Stage> DerefMut for AcceptContext<'f, 'sess, S> {
 ///
 /// Gives [`AttributeParser`](crate::attributes::AttributeParser)s enough information to create
 /// errors, for example.
-pub(crate) struct SharedContext<'p, 'sess, S: Stage> {
+pub struct SharedContext<'p, 'sess, S: Stage> {
     /// The parse context, gives access to the session and the
     /// diagnostics context.
     pub(crate) cx: &'p mut AttributeParser<'sess, S>,
@@ -540,7 +560,7 @@ pub struct AttributeParser<'sess, S: Stage = Late> {
     pub(crate) tools: Vec<Symbol>,
     features: Option<&'sess Features>,
     sess: &'sess Session,
-    stage: PhantomData<S>,
+    stage: S,
 
     /// *Only* parse attributes with this symbol.
     ///
@@ -569,13 +589,14 @@ impl<'sess> AttributeParser<'sess, Early> {
         sym: Symbol,
         target_span: Span,
         target_node_id: NodeId,
+        features: Option<&'sess Features>,
     ) -> Option<Attribute> {
         let mut p = Self {
-            features: None,
+            features,
             tools: Vec::new(),
             parse_only: Some(sym),
             sess,
-            stage: PhantomData,
+            stage: Early { emit_errors: false },
         };
         let mut parsed = p.parse_attribute_list(
             attrs,
@@ -591,11 +612,55 @@ impl<'sess> AttributeParser<'sess, Early> {
 
         parsed.pop()
     }
+
+    pub fn parse_single<T>(
+        sess: &'sess Session,
+        attr: &ast::Attribute,
+        target_span: Span,
+        target_node_id: NodeId,
+        features: Option<&'sess Features>,
+        emit_errors: bool,
+        parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> T,
+        template: &AttributeTemplate,
+    ) -> T {
+        let mut parser = Self {
+            features,
+            tools: Vec::new(),
+            parse_only: None,
+            sess,
+            stage: Early { emit_errors },
+        };
+        let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
+            panic!("parse_single called on a doc attr")
+        };
+        let meta_parser = MetaItemParser::from_attr(normal_attr, parser.dcx());
+        let path = meta_parser.path();
+        let args = meta_parser.args();
+        let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext {
+            shared: SharedContext {
+                cx: &mut parser,
+                target_span,
+                target_id: target_node_id,
+                emit_lint: &mut |_lint| {
+                    panic!("can't emit lints here for now (nothing uses this atm)");
+                },
+            },
+            attr_span: attr.span,
+            template,
+            attr_path: path.get_attribute_path(),
+        };
+        parse_fn(&mut cx, args)
+    }
 }
 
 impl<'sess, S: Stage> AttributeParser<'sess, S> {
-    pub fn new(sess: &'sess Session, features: &'sess Features, tools: Vec<Symbol>) -> Self {
-        Self { features: Some(features), tools, parse_only: None, sess, stage: PhantomData }
+    pub fn new(
+        sess: &'sess Session,
+        features: &'sess Features,
+        tools: Vec<Symbol>,
+        stage: S,
+    ) -> Self {
+        Self { features: Some(features), tools, parse_only: None, sess, stage }
     }
 
     pub(crate) fn sess(&self) -> &'sess Session {
@@ -606,6 +671,10 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
         self.features.expect("features not available at this point in the compiler")
     }
 
+    pub(crate) fn features_option(&self) -> Option<&'sess Features> {
+        self.features
+    }
+
     pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
         self.sess().dcx()
     }
diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs
index 47eeb63bad3..2102a26108b 100644
--- a/compiler/rustc_attr_parsing/src/lib.rs
+++ b/compiler/rustc_attr_parsing/src/lib.rs
@@ -90,7 +90,8 @@ mod lints;
 pub mod parser;
 mod session_diagnostics;
 
-pub use attributes::cfg::*;
+pub use attributes::cfg::{CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr};
+pub use attributes::cfg_old::*;
 pub use attributes::util::{
     find_crate_name, is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version,
 };
diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
index 8a240639d75..97bf3d1c549 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -593,7 +593,13 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                 diag.code(E0565);
             }
             AttributeParseErrorReason::ExpectedNameValue(None) => {
-                // The suggestion we add below this match already contains enough information
+                // If the span is the entire attribute, the suggestion we add below this match already contains enough information
+                if self.span != self.attr_span {
+                    diag.span_label(
+                        self.span,
+                        format!("expected this to be of the form `... = \"...\"`"),
+                    );
+                }
             }
             AttributeParseErrorReason::ExpectedNameValue(Some(name)) => {
                 diag.span_label(
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
index 8c3093acea4..c55a9e73e38 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -484,7 +484,7 @@ impl<'a> TraitDef<'a> {
         match item {
             Annotatable::Item(item) => {
                 let is_packed = matches!(
-                    AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, item.span, item.id),
+                    AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, item.span, item.id, None),
                     Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..)))
                 );
 
diff --git a/compiler/rustc_error_codes/src/error_codes/E0536.md b/compiler/rustc_error_codes/src/error_codes/E0536.md
index c081a3d9cfa..f00d1773944 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0536.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0536.md
@@ -3,22 +3,20 @@ The `not` cfg-predicate was malformed.
 Erroneous code example:
 
 ```compile_fail,E0536
-#[cfg(not())] // error: expected 1 cfg-pattern
-pub fn something() {}
-
-pub fn main() {}
+pub fn main() {
+    if cfg!(not()) { }
+}
 ```
 
 The `not` predicate expects one cfg-pattern. Example:
 
 ```
-#[cfg(not(target_os = "linux"))] // ok!
-pub fn something() {}
-
-pub fn main() {}
+pub fn main() {
+    if cfg!(not(target_os = "linux")) { } // ok!
+}
 ```
 
-For more information about the `cfg` attribute, read the section on
+For more information about the `cfg` macro, read the section on
 [Conditional Compilation][conditional-compilation] in the Reference.
 
 [conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 757a7e1c8e7..ce3006c2604 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -11,7 +11,7 @@ use rustc_ast::token::MetaVarKind;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::visit::{AssocCtxt, Visitor};
 use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind};
-use rustc_attr_data_structures::{AttributeKind, Deprecation, Stability, find_attr};
+use rustc_attr_data_structures::{AttributeKind, CfgEntry, Deprecation, Stability, find_attr};
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_data_structures::sync;
 use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, PResult};
@@ -1128,7 +1128,13 @@ pub trait ResolverExpand {
     /// HIR proc macros items back to their harness items.
     fn declare_proc_macro(&mut self, id: NodeId);
 
-    fn append_stripped_cfg_item(&mut self, parent_node: NodeId, ident: Ident, cfg: ast::MetaItem);
+    fn append_stripped_cfg_item(
+        &mut self,
+        parent_node: NodeId,
+        ident: Ident,
+        cfg: CfgEntry,
+        cfg_span: Span,
+    );
 
     /// Tools registered with `#![register_tool]` and used by tool attributes and lints.
     fn registered_tools(&self) -> &RegisteredTools;
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 170ac39d1ec..6922ddfd6bd 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -11,6 +11,9 @@ use rustc_ast::{
     NodeId, NormalAttr,
 };
 use rustc_attr_parsing as attr;
+use rustc_attr_parsing::{
+    AttributeParser, CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr,
+};
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
 use rustc_feature::{
     ACCEPTED_LANG_FEATURES, AttributeSafety, EnabledLangFeature, EnabledLibFeature, Features,
@@ -18,6 +21,7 @@ use rustc_feature::{
 };
 use rustc_lint_defs::BuiltinLintDiag;
 use rustc_parse::validate_attr;
+use rustc_parse::validate_attr::deny_builtin_meta_unsafety;
 use rustc_session::Session;
 use rustc_session::parse::feature_err;
 use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym};
@@ -161,7 +165,10 @@ pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec
     attrs
         .iter()
         .flat_map(|attr| strip_unconfigured.process_cfg_attr(attr))
-        .take_while(|attr| !is_cfg(attr) || strip_unconfigured.cfg_true(attr).0)
+        .take_while(|attr| {
+            !is_cfg(attr)
+                || strip_unconfigured.cfg_true(attr, strip_unconfigured.lint_node_id).as_bool()
+        })
         .collect()
 }
 
@@ -394,26 +401,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| !is_cfg(attr) || self.cfg_true(attr).0)
+        attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr, self.lint_node_id).as_bool())
     }
 
-    pub(crate) fn cfg_true(&self, attr: &Attribute) -> (bool, Option<MetaItem>) {
-        let meta_item = match validate_attr::parse_meta(&self.sess.psess, attr) {
-            Ok(meta_item) => meta_item,
+    pub(crate) fn cfg_true(&self, attr: &Attribute, node: NodeId) -> EvalConfigResult {
+        // We need to run this to do basic validation of the attribute, such as that lits are valid, etc
+        // FIXME(jdonszelmann) this should not be necessary in the future
+        match validate_attr::parse_meta(&self.sess.psess, attr) {
+            Ok(_) => {}
             Err(err) => {
                 err.emit();
-                return (true, None);
+                return EvalConfigResult::True;
             }
-        };
+        }
 
-        validate_attr::deny_builtin_meta_unsafety(&self.sess.psess, &meta_item);
+        // Unsafety check needs to be done explicitly here because this attribute will be removed before the normal check
+        deny_builtin_meta_unsafety(
+            self.sess.dcx(),
+            attr.get_normal_item().unsafety,
+            &rustc_ast::Path::from_ident(attr.ident().unwrap()),
+        );
+
+        let Some(cfg) = AttributeParser::parse_single(
+            self.sess,
+            attr,
+            attr.span,
+            node,
+            self.features,
+            true,
+            parse_cfg_attr,
+            &CFG_TEMPLATE,
+        ) else {
+            // Cfg attribute was not parsable, give up
+            return EvalConfigResult::True;
+        };
 
-        (
-            parse_cfg(&meta_item, self.sess).is_none_or(|meta_item| {
-                attr::cfg_matches(meta_item, &self.sess, self.lint_node_id, self.features)
-            }),
-            Some(meta_item),
-        )
+        eval_config_entry(self.sess, &cfg, self.lint_node_id, self.features)
     }
 
     /// If attributes are not allowed on expressions, emit an error for `attr`
@@ -465,6 +488,7 @@ impl<'a> StripUnconfigured<'a> {
     }
 }
 
+/// FIXME: Still used by Rustdoc, should be removed after
 pub fn parse_cfg<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItemInner> {
     let span = meta_item.span;
     match meta_item.meta_item_list() {
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 2de09aa1a28..f99060e9a21 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -13,6 +13,7 @@ use rustc_ast::{
     MetaItemKind, ModKind, NodeId, PatKind, StmtKind, TyKind, token,
 };
 use rustc_ast_pretty::pprust;
+use rustc_attr_parsing::EvalConfigResult;
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
 use rustc_errors::PResult;
 use rustc_feature::Features;
@@ -2166,19 +2167,19 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
 
     fn expand_cfg_true(
         &mut self,
-        node: &mut impl HasAttrs,
+        node: &mut (impl HasAttrs + HasNodeId),
         attr: ast::Attribute,
         pos: usize,
-    ) -> (bool, Option<ast::MetaItem>) {
-        let (res, meta_item) = self.cfg().cfg_true(&attr);
-        if res {
+    ) -> EvalConfigResult {
+        let res = self.cfg().cfg_true(&attr, node.node_id());
+        if res.as_bool() {
             // A trace attribute left in AST in place of the original `cfg` attribute.
             // It can later be used by lints or other diagnostics.
             let trace_attr = attr_into_trace(attr, sym::cfg_trace);
             node.visit_attrs(|attrs| attrs.insert(pos, trace_attr));
         }
 
-        (res, meta_item)
+        res
     }
 
     fn expand_cfg_attr(&self, node: &mut impl HasAttrs, attr: &ast::Attribute, pos: usize) {
@@ -2199,20 +2200,21 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
             return match self.take_first_attr(&mut node) {
                 Some((attr, pos, derives)) => match attr.name() {
                     Some(sym::cfg) => {
-                        let (res, meta_item) = self.expand_cfg_true(&mut node, attr, pos);
-                        if res {
-                            continue;
-                        }
-
-                        if let Some(meta_item) = meta_item {
-                            for ident in node.declared_idents() {
-                                self.cx.resolver.append_stripped_cfg_item(
-                                    self.cx.current_expansion.lint_node_id,
-                                    ident,
-                                    meta_item.clone(),
-                                )
+                        let res = self.expand_cfg_true(&mut node, attr, pos);
+                        match res {
+                            EvalConfigResult::True => continue,
+                            EvalConfigResult::False { reason, reason_span } => {
+                                for ident in node.declared_idents() {
+                                    self.cx.resolver.append_stripped_cfg_item(
+                                        self.cx.current_expansion.lint_node_id,
+                                        ident,
+                                        reason.clone(),
+                                        reason_span,
+                                    )
+                                }
                             }
                         }
+
                         Default::default()
                     }
                     Some(sym::cfg_attr) => {
@@ -2291,7 +2293,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                 Some((attr, pos, derives)) => match attr.name() {
                     Some(sym::cfg) => {
                         let span = attr.span;
-                        if self.expand_cfg_true(node, attr, pos).0 {
+                        if self.expand_cfg_true(node, attr, pos).as_bool() {
                             continue;
                         }
 
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
index bc9badbb232..db89396d1dc 100644
--- a/compiler/rustc_lint/src/nonstandard_style.rs
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -167,7 +167,7 @@ impl NonCamelCaseTypes {
 impl EarlyLintPass for NonCamelCaseTypes {
     fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
         let has_repr_c = matches!(
-            AttributeParser::parse_limited(cx.sess(), &it.attrs, sym::repr, it.span, it.id),
+            AttributeParser::parse_limited(cx.sess(), &it.attrs, sym::repr, it.span, it.id, None),
             Some(Attribute::Parsed(AttributeKind::Repr { reprs, ..})) if reprs.iter().any(|(r, _)| r == &ReprAttr::ReprC)
         );
 
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 8347626080e..915c9731688 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -7,7 +7,7 @@ use def_path_hash_map::DefPathHashMapRef;
 use encoder::EncodeContext;
 pub use encoder::{EncodedMetadata, encode_metadata, rendered_const};
 use rustc_abi::{FieldIdx, ReprOptions, VariantIdx};
-use rustc_ast::expand::StrippedCfgItem;
+use rustc_attr_data_structures::StrippedCfgItem;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::svh::Svh;
 use rustc_hir::PreciseCapturingArgKind;
diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs
index f140ab4755b..43a7af9ce38 100644
--- a/compiler/rustc_middle/src/arena.rs
+++ b/compiler/rustc_middle/src/arena.rs
@@ -110,7 +110,7 @@ macro_rules! arena_types {
             [] external_constraints: rustc_middle::traits::solve::ExternalConstraintsData<rustc_middle::ty::TyCtxt<'tcx>>,
             [] predefined_opaques_in_body: rustc_middle::traits::solve::PredefinedOpaquesData<rustc_middle::ty::TyCtxt<'tcx>>,
             [decode] doc_link_resolutions: rustc_hir::def::DocLinkResMap,
-            [] stripped_cfg_items: rustc_ast::expand::StrippedCfgItem,
+            [] stripped_cfg_items: rustc_attr_data_structures::StrippedCfgItem,
             [] mod_child: rustc_middle::metadata::ModChild,
             [] features: rustc_feature::Features,
             [decode] specialization_graph: rustc_middle::traits::specialization_graph::Graph,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 842d118449a..935cc889574 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -69,8 +69,8 @@ use std::sync::Arc;
 
 use rustc_abi::Align;
 use rustc_arena::TypedArena;
-use rustc_ast::expand::StrippedCfgItem;
 use rustc_ast::expand::allocator::AllocatorKind;
+use rustc_attr_data_structures::StrippedCfgItem;
 use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::sorted_map::SortedMap;
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index a352ab3fd08..6e8f1e8fdd5 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -25,10 +25,9 @@ pub use generic_args::{GenericArgKind, TermKind, *};
 pub use generics::*;
 pub use intrinsic::IntrinsicDef;
 use rustc_abi::{Align, FieldIdx, Integer, IntegerType, ReprFlags, ReprOptions, VariantIdx};
-use rustc_ast::expand::StrippedCfgItem;
 use rustc_ast::node_id::NodeMap;
 pub use rustc_ast_ir::{Movability, Mutability, try_visit};
-use rustc_attr_data_structures::{AttributeKind, find_attr};
+use rustc_attr_data_structures::{AttributeKind, StrippedCfgItem, find_attr};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_data_structures::intern::Interned;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index 3d601fc0533..dbacbe21edb 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -87,7 +87,7 @@ trivially_parameterized_over_tcx! {
     ty::IntrinsicDef,
     rustc_ast::Attribute,
     rustc_ast::DelimArgs,
-    rustc_ast::expand::StrippedCfgItem<rustc_hir::def_id::DefIndex>,
+    rustc_attr_data_structures::StrippedCfgItem<rustc_hir::def_id::DefIndex>,
     rustc_attr_data_structures::ConstStability,
     rustc_attr_data_structures::DefaultBodyStability,
     rustc_attr_data_structures::Deprecation,
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index 5c748e956a0..e000d61083d 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -4,9 +4,9 @@ use rustc_ast::token::Delimiter;
 use rustc_ast::tokenstream::DelimSpan;
 use rustc_ast::{
     self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, NodeId,
-    Safety,
+    Path, Safety,
 };
-use rustc_errors::{Applicability, FatalError, PResult};
+use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult};
 use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
 use rustc_session::errors::report_lit_error;
 use rustc_session::lint::BuiltinLintDiag;
@@ -247,14 +247,12 @@ pub fn check_attribute_safety(
 
 // Called by `check_builtin_meta_item` and code that manually denies
 // `unsafe(...)` in `cfg`
-pub fn deny_builtin_meta_unsafety(psess: &ParseSess, meta: &MetaItem) {
+pub fn deny_builtin_meta_unsafety(diag: DiagCtxtHandle<'_>, unsafety: Safety, name: &Path) {
     // This only supports denying unsafety right now - making builtin attributes
     // support unsafety will requite us to thread the actual `Attribute` through
     // for the nice diagnostics.
-    if let Safety::Unsafe(unsafe_span) = meta.unsafety {
-        psess
-            .dcx()
-            .emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: meta.path.clone() });
+    if let Safety::Unsafe(unsafe_span) = unsafety {
+        diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() });
     }
 }
 
@@ -326,7 +324,7 @@ pub fn check_builtin_meta_item(
     }
 
     if deny_unsafety {
-        deny_builtin_meta_unsafety(psess, meta);
+        deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
     }
 }
 
diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs
index e2caf632dd2..781e2ce9704 100644
--- a/compiler/rustc_resolve/src/def_collector.rs
+++ b/compiler/rustc_resolve/src/def_collector.rs
@@ -132,6 +132,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
                     &self.resolver.tcx.sess,
                     self.resolver.tcx.features(),
                     Vec::new(),
+                    Early { emit_errors: false },
                 );
                 let attrs = parser.parse_attribute_list(
                     &i.attrs,
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index b2f16fb1dfb..93d848787ef 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -1,11 +1,10 @@
-use rustc_ast::expand::StrippedCfgItem;
 use rustc_ast::ptr::P;
 use rustc_ast::visit::{self, Visitor};
-use rustc_ast::{
-    self as ast, CRATE_NODE_ID, Crate, ItemKind, MetaItemInner, MetaItemKind, ModKind, NodeId, Path,
-};
+use rustc_ast::{self as ast, CRATE_NODE_ID, Crate, ItemKind, ModKind, NodeId, Path};
 use rustc_ast_pretty::pprust;
-use rustc_attr_data_structures::{self as attr, AttributeKind, Stability, find_attr};
+use rustc_attr_data_structures::{
+    self as attr, AttributeKind, CfgEntry, Stability, StrippedCfgItem, find_attr,
+};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_errors::codes::*;
@@ -2860,17 +2859,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             let note = errors::FoundItemConfigureOut { span: ident.span };
             err.subdiagnostic(note);
 
-            if let MetaItemKind::List(nested) = &cfg.kind
-                && let MetaItemInner::MetaItem(meta_item) = &nested[0]
-                && let MetaItemKind::NameValue(feature_name) = &meta_item.kind
-            {
-                let note = errors::ItemWasBehindFeature {
-                    feature: feature_name.symbol,
-                    span: meta_item.span,
-                };
+            if let CfgEntry::NameValue { value: Some((feature, _)), .. } = cfg.0 {
+                let note = errors::ItemWasBehindFeature { feature, span: cfg.1 };
                 err.subdiagnostic(note);
             } else {
-                let note = errors::ItemWasCfgOut { span: cfg.span };
+                let note = errors::ItemWasCfgOut { span: cfg.1 };
                 err.subdiagnostic(note);
             }
         }
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index f8d1b6783e3..c2b3451d4a1 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -36,12 +36,12 @@ use late::{
 };
 use macros::{MacroRulesBinding, MacroRulesScope, MacroRulesScopeRef};
 use rustc_arena::{DroplessArena, TypedArena};
-use rustc_ast::expand::StrippedCfgItem;
 use rustc_ast::node_id::NodeMap;
 use rustc_ast::{
     self as ast, AngleBracketedArg, CRATE_NODE_ID, Crate, Expr, ExprKind, GenericArg, GenericArgs,
     LitKind, NodeId, Path, attr,
 };
+use rustc_attr_data_structures::StrippedCfgItem;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_data_structures::intern::Interned;
 use rustc_data_structures::steal::Steal;
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index 5864b035f73..cbb24289f51 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -5,10 +5,9 @@ use std::cell::Cell;
 use std::mem;
 use std::sync::Arc;
 
-use rustc_ast::expand::StrippedCfgItem;
 use rustc_ast::{self as ast, Crate, NodeId, attr};
 use rustc_ast_pretty::pprust;
-use rustc_attr_data_structures::StabilityLevel;
+use rustc_attr_data_structures::{CfgEntry, StabilityLevel, StrippedCfgItem};
 use rustc_errors::{Applicability, DiagCtxtHandle, StashKey};
 use rustc_expand::base::{
     Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension,
@@ -485,8 +484,18 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
         self.proc_macros.push(self.local_def_id(id))
     }
 
-    fn append_stripped_cfg_item(&mut self, parent_node: NodeId, ident: Ident, cfg: ast::MetaItem) {
-        self.stripped_cfg_items.push(StrippedCfgItem { parent_module: parent_node, ident, cfg });
+    fn append_stripped_cfg_item(
+        &mut self,
+        parent_node: NodeId,
+        ident: Ident,
+        cfg: CfgEntry,
+        cfg_span: Span,
+    ) {
+        self.stripped_cfg_items.push(StrippedCfgItem {
+            parent_module: parent_node,
+            ident,
+            cfg: (cfg, cfg_span),
+        });
     }
 
     fn registered_tools(&self) -> &RegisteredTools {
diff --git a/tests/ui/attributes/malformed-attrs.rs b/tests/ui/attributes/malformed-attrs.rs
index aa52de63a60..5026687b97b 100644
--- a/tests/ui/attributes/malformed-attrs.rs
+++ b/tests/ui/attributes/malformed-attrs.rs
@@ -100,7 +100,7 @@
 //~^ ERROR malformed
 //~| ERROR the `#[proc_macro]` attribute is only usable with crates of the `proc-macro` crate type
 #[cfg]
-//~^ ERROR is not followed by parentheses
+//~^ ERROR malformed
 #[cfg_attr]
 //~^ ERROR malformed
 #[instruction_set]
diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr
index 3dc3c32c936..56f2be353e7 100644
--- a/tests/ui/attributes/malformed-attrs.stderr
+++ b/tests/ui/attributes/malformed-attrs.stderr
@@ -1,8 +1,11 @@
-error: `cfg` is not followed by parentheses
+error[E0539]: malformed `cfg` attribute input
   --> $DIR/malformed-attrs.rs:102:1
    |
 LL | #[cfg]
-   | ^^^^^^ help: expected syntax is: `cfg(/* predicate */)`
+   | ^^^^^^
+   | |
+   | expected this to be a list
+   | help: must be of the form: `#[cfg(predicate)]`
 
 error: malformed `cfg_attr` attribute input
   --> $DIR/malformed-attrs.rs:104:1
diff --git a/tests/ui/cfg/cfg-target-compact-errors.rs b/tests/ui/cfg/cfg-target-compact-errors.rs
index e00d42fe4d9..cfb19c58a19 100644
--- a/tests/ui/cfg/cfg-target-compact-errors.rs
+++ b/tests/ui/cfg/cfg-target-compact-errors.rs
@@ -3,19 +3,23 @@
 #![feature(cfg_target_compact)]
 
 #[cfg(target(o::o))]
-//~^ ERROR `cfg` predicate key must be an identifier
+//~^ ERROR malformed `cfg` attribute input
 fn one() {}
 
 #[cfg(target(os = 8))]
-//~^ ERROR literal in `cfg` predicate value must be a string
+//~^ ERROR malformed `cfg` attribute input
 fn two() {}
 
 #[cfg(target(os = "linux", pointer(width = "64")))]
-//~^ ERROR invalid predicate `target_pointer`
+//~^ ERROR malformed `cfg` attribute input
 fn three() {}
 
 #[cfg(target(true))]
-//~^ ERROR `cfg` predicate key must be an identifier
+//~^ ERROR malformed `cfg` attribute input
 fn four() {}
 
+#[cfg(target(clippy::os = "linux"))]
+//~^ ERROR `cfg` predicate key must be an identifier
+fn five() {}
+
 fn main() {}
diff --git a/tests/ui/cfg/cfg-target-compact-errors.stderr b/tests/ui/cfg/cfg-target-compact-errors.stderr
index 219d9732c32..7df6729e881 100644
--- a/tests/ui/cfg/cfg-target-compact-errors.stderr
+++ b/tests/ui/cfg/cfg-target-compact-errors.stderr
@@ -1,28 +1,45 @@
-error: `cfg` predicate key must be an identifier
-  --> $DIR/cfg-target-compact-errors.rs:5:14
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-target-compact-errors.rs:5:1
    |
 LL | #[cfg(target(o::o))]
-   |              ^^^^
+   | ^^^^^^^^^^^^^----^^^
+   | |            |
+   | |            expected this to be of the form `... = "..."`
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error[E0565]: literal in `cfg` predicate value must be a string
-  --> $DIR/cfg-target-compact-errors.rs:9:19
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-target-compact-errors.rs:9:1
    |
 LL | #[cfg(target(os = 8))]
-   |                   ^
+   | ^^^^^^^^^^^^^^^^^^-^^^
+   | |                 |
+   | |                 expected a string literal here
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error[E0537]: invalid predicate `target_pointer`
-  --> $DIR/cfg-target-compact-errors.rs:13:28
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-target-compact-errors.rs:13:1
    |
 LL | #[cfg(target(os = "linux", pointer(width = "64")))]
-   |                            ^^^^^^^^^^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------------^^^
+   | |                          |
+   | |                          expected this to be of the form `... = "..."`
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error: `cfg` predicate key must be an identifier
-  --> $DIR/cfg-target-compact-errors.rs:17:14
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-target-compact-errors.rs:17:1
    |
 LL | #[cfg(target(true))]
-   |              ^^^^
+   | ^^^^^^^^^^^^^----^^^
+   | |            |
+   | |            expected this to be of the form `... = "..."`
+   | help: must be of the form: `#[cfg(predicate)]`
+
+error: `cfg` predicate key must be an identifier
+  --> $DIR/cfg-target-compact-errors.rs:21:14
+   |
+LL | #[cfg(target(clippy::os = "linux"))]
+   |              ^^^^^^^^^^
 
-error: aborting due to 4 previous errors
+error: aborting due to 5 previous errors
 
-Some errors have detailed explanations: E0537, E0565.
-For more information about an error, try `rustc --explain E0537`.
+For more information about this error, try `rustc --explain E0539`.
diff --git a/tests/ui/cfg/cfg-version/syntax.stderr b/tests/ui/cfg/cfg-version/syntax.stderr
index 2facd960763..188f6d7aa5d 100644
--- a/tests/ui/cfg/cfg-version/syntax.stderr
+++ b/tests/ui/cfg/cfg-version/syntax.stderr
@@ -17,10 +17,10 @@ LL | #[cfg(version(false))]
    |               ^^^^^
 
 error: expected single version literal
-  --> $DIR/syntax.rs:23:7
+  --> $DIR/syntax.rs:23:14
    |
 LL | #[cfg(version("1.43", "1.44", "1.45"))]
-   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: unknown version literal format, assuming it refers to a future version
   --> $DIR/syntax.rs:51:15
diff --git a/tests/ui/cfg/diagnostics-cross-crate.stderr b/tests/ui/cfg/diagnostics-cross-crate.stderr
index 3e32a856e95..155b3db2f42 100644
--- a/tests/ui/cfg/diagnostics-cross-crate.stderr
+++ b/tests/ui/cfg/diagnostics-cross-crate.stderr
@@ -10,10 +10,10 @@ note: found an item that was configured out
 LL |     pub mod doesnt_exist {
    |             ^^^^^^^^^^^^
 note: the item is gated here
-  --> $DIR/auxiliary/cfged_out.rs:5:5
+  --> $DIR/auxiliary/cfged_out.rs:5:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0425]: cannot find function `uwu` in crate `cfged_out`
   --> $DIR/diagnostics-cross-crate.rs:7:16
@@ -33,10 +33,10 @@ note: found an item that was configured out
 LL |     pub fn uwu() {}
    |            ^^^
 note: the item is gated here
-  --> $DIR/auxiliary/cfged_out.rs:2:5
+  --> $DIR/auxiliary/cfged_out.rs:2:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0425]: cannot find function `meow` in module `cfged_out::inner::right`
   --> $DIR/diagnostics-cross-crate.rs:24:30
@@ -67,10 +67,10 @@ note: found an item that was configured out
 LL | pub fn vanished() {}
    |        ^^^^^^^^
 note: the item is gated here
-  --> $DIR/auxiliary/cfged_out.rs:21:1
+  --> $DIR/auxiliary/cfged_out.rs:21:7
    |
 LL | #[cfg(i_dont_exist_and_you_can_do_nothing_about_it)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 5 previous errors
 
diff --git a/tests/ui/cfg/diagnostics-reexport-2.stderr b/tests/ui/cfg/diagnostics-reexport-2.stderr
index 95ac5a19b0b..e1f91fcc5d1 100644
--- a/tests/ui/cfg/diagnostics-reexport-2.stderr
+++ b/tests/ui/cfg/diagnostics-reexport-2.stderr
@@ -10,10 +10,10 @@ note: found an item that was configured out
 LL |     pub mod gated {
    |             ^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport-2.rs:4:5
+  --> $DIR/diagnostics-reexport-2.rs:4:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0433]: failed to resolve: could not find `gated` in `reexport2`
   --> $DIR/diagnostics-reexport-2.rs:46:16
@@ -27,10 +27,10 @@ note: found an item that was configured out
 LL |     pub mod gated {
    |             ^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport-2.rs:4:5
+  --> $DIR/diagnostics-reexport-2.rs:4:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0433]: failed to resolve: could not find `gated` in `reexport30`
   --> $DIR/diagnostics-reexport-2.rs:50:17
@@ -44,10 +44,10 @@ note: found an item that was configured out
 LL |     pub mod gated {
    |             ^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport-2.rs:4:5
+  --> $DIR/diagnostics-reexport-2.rs:4:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0433]: failed to resolve: could not find `gated` in `reexport31`
   --> $DIR/diagnostics-reexport-2.rs:54:17
@@ -61,10 +61,10 @@ note: found an item that was configured out
 LL |     pub mod gated {
    |             ^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport-2.rs:4:5
+  --> $DIR/diagnostics-reexport-2.rs:4:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0433]: failed to resolve: could not find `gated` in `reexport32`
   --> $DIR/diagnostics-reexport-2.rs:58:17
@@ -78,10 +78,10 @@ note: found an item that was configured out
 LL |     pub mod gated {
    |             ^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport-2.rs:4:5
+  --> $DIR/diagnostics-reexport-2.rs:4:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error: aborting due to 5 previous errors
 
diff --git a/tests/ui/cfg/diagnostics-reexport.stderr b/tests/ui/cfg/diagnostics-reexport.stderr
index 95dc4fac945..bf1bbcba7e4 100644
--- a/tests/ui/cfg/diagnostics-reexport.stderr
+++ b/tests/ui/cfg/diagnostics-reexport.stderr
@@ -10,10 +10,10 @@ note: found an item that was configured out
 LL |     pub fn x() {}
    |            ^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport.rs:17:5
+  --> $DIR/diagnostics-reexport.rs:17:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0432]: unresolved imports `b::x`, `b::y`
   --> $DIR/diagnostics-reexport.rs:22:13
@@ -29,20 +29,20 @@ note: found an item that was configured out
 LL |     pub fn x() {}
    |            ^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport.rs:28:5
+  --> $DIR/diagnostics-reexport.rs:28:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 note: found an item that was configured out
   --> $DIR/diagnostics-reexport.rs:32:12
    |
 LL |     pub fn y() {}
    |            ^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport.rs:31:5
+  --> $DIR/diagnostics-reexport.rs:31:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0425]: cannot find function `uwu` in module `inner`
   --> $DIR/diagnostics-reexport.rs:38:12
@@ -56,10 +56,10 @@ note: found an item that was configured out
 LL |     pub use super::uwu;
    |                    ^^^
 note: the item is gated here
-  --> $DIR/diagnostics-reexport.rs:7:5
+  --> $DIR/diagnostics-reexport.rs:7:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error: aborting due to 3 previous errors
 
diff --git a/tests/ui/cfg/diagnostics-same-crate.stderr b/tests/ui/cfg/diagnostics-same-crate.stderr
index 75a1bc39a01..121f25ca090 100644
--- a/tests/ui/cfg/diagnostics-same-crate.stderr
+++ b/tests/ui/cfg/diagnostics-same-crate.stderr
@@ -10,10 +10,10 @@ note: found an item that was configured out
 LL |     pub mod doesnt_exist {
    |             ^^^^^^^^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-same-crate.rs:8:5
+  --> $DIR/diagnostics-same-crate.rs:8:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0432]: unresolved import `super::inner::doesnt_exist`
   --> $DIR/diagnostics-same-crate.rs:35:23
@@ -27,10 +27,10 @@ note: found an item that was configured out
 LL |     pub mod doesnt_exist {
    |             ^^^^^^^^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-same-crate.rs:8:5
+  --> $DIR/diagnostics-same-crate.rs:8:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0433]: failed to resolve: could not find `doesnt_exist` in `inner`
   --> $DIR/diagnostics-same-crate.rs:54:12
@@ -44,10 +44,10 @@ note: found an item that was configured out
 LL |     pub mod doesnt_exist {
    |             ^^^^^^^^^^^^
 note: the item is gated here
-  --> $DIR/diagnostics-same-crate.rs:8:5
+  --> $DIR/diagnostics-same-crate.rs:8:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0425]: cannot find function `uwu` in module `inner`
   --> $DIR/diagnostics-same-crate.rs:49:12
@@ -61,10 +61,10 @@ note: found an item that was configured out
 LL |     pub fn uwu() {}
    |            ^^^
 note: the item is gated here
-  --> $DIR/diagnostics-same-crate.rs:4:5
+  --> $DIR/diagnostics-same-crate.rs:4:11
    |
 LL |     #[cfg(false)]
-   |     ^^^^^^^^^^^^^
+   |           ^^^^^
 
 error[E0425]: cannot find function `meow` in module `inner::right`
   --> $DIR/diagnostics-same-crate.rs:58:19
diff --git a/tests/ui/check-cfg/compact-names.rs b/tests/ui/check-cfg/compact-names.rs
index 931afdf986f..fbf8f9d2002 100644
--- a/tests/ui/check-cfg/compact-names.rs
+++ b/tests/ui/check-cfg/compact-names.rs
@@ -13,4 +13,8 @@ pub fn expected() {}
 //~^ WARNING unexpected `cfg` condition name
 pub fn unexpected() {}
 
+#[cfg(target(os = "windows", architecture = "arm"))]
+//~^ WARNING unexpected `cfg` condition name
+pub fn unexpected2() {}
+
 fn main() {}
diff --git a/tests/ui/check-cfg/compact-names.stderr b/tests/ui/check-cfg/compact-names.stderr
index 74ed0337e3b..3221b4e07ec 100644
--- a/tests/ui/check-cfg/compact-names.stderr
+++ b/tests/ui/check-cfg/compact-names.stderr
@@ -8,5 +8,14 @@ LL | #[cfg(target(os = "linux", architecture = "arm"))]
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: 1 warning emitted
+warning: unexpected `cfg` condition name: `target_architecture`
+  --> $DIR/compact-names.rs:16:30
+   |
+LL | #[cfg(target(os = "windows", architecture = "arm"))]
+   |                              ^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: to expect this configuration use `--check-cfg=cfg(target_architecture, values("arm"))`
+   = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+
+warning: 2 warnings emitted
 
diff --git a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs
index 47418b4e091..df87a3d846e 100644
--- a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs
+++ b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs
@@ -1,16 +1,24 @@
-#[cfg] //~ ERROR `cfg` is not followed by parentheses
+#[cfg]
+//~^ ERROR malformed `cfg` attribute
+//~| NOTE expected this to be a list
 struct S1;
 
-#[cfg = 10] //~ ERROR `cfg` is not followed by parentheses
+#[cfg = 10]
+//~^ ERROR malformed `cfg` attribute
+//~| NOTE expected this to be a list
 struct S2;
 
-#[cfg()] //~ ERROR `cfg` predicate is not specified
+#[cfg()]
+//~^ ERROR malformed `cfg` attribute
+//~| NOTE expected a single argument here
 struct S3;
 
-#[cfg(a, b)] //~ ERROR multiple `cfg` predicates are specified
+#[cfg(a, b)]
+//~^ ERROR malformed `cfg` attribute
+//~| NOTE expected a single argument here
 struct S4;
 
-#[cfg("str")] //~ ERROR `cfg` predicate key cannot be a literal
+#[cfg("str")] //~ ERROR `cfg` predicate key must be an identifier
 struct S5;
 
 #[cfg(a::b)] //~ ERROR `cfg` predicate key must be an identifier
@@ -19,10 +27,12 @@ struct S6;
 #[cfg(a())] //~ ERROR invalid predicate `a`
 struct S7;
 
-#[cfg(a = 10)] //~ ERROR literal in `cfg` predicate value must be a string
+#[cfg(a = 10)] //~ ERROR malformed `cfg` attribute input
+//~^ NOTE expected a string literal here
 struct S8;
 
-#[cfg(a = b"hi")]  //~ ERROR literal in `cfg` predicate value must be a string
+#[cfg(a = b"hi")]  //~ ERROR malformed `cfg` attribute input
+//~^ NOTE expected a normal string literal, not a byte string literal
 struct S9;
 
 macro_rules! generate_s10 {
@@ -34,5 +44,6 @@ macro_rules! generate_s10 {
 }
 
 generate_s10!(concat!("nonexistent"));
+//~^ NOTE in this expansion of generate_s10!
 
 fn main() {}
diff --git a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr
index 66ce2ee9858..75e9b9209c0 100644
--- a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr
+++ b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr
@@ -1,61 +1,78 @@
-error: `cfg` is not followed by parentheses
+error[E0539]: malformed `cfg` attribute input
   --> $DIR/cfg-attr-syntax-validation.rs:1:1
    |
 LL | #[cfg]
-   | ^^^^^^ help: expected syntax is: `cfg(/* predicate */)`
+   | ^^^^^^
+   | |
+   | expected this to be a list
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error: `cfg` is not followed by parentheses
-  --> $DIR/cfg-attr-syntax-validation.rs:4:1
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-attr-syntax-validation.rs:6:1
    |
 LL | #[cfg = 10]
-   | ^^^^^^^^^^^ help: expected syntax is: `cfg(/* predicate */)`
+   | ^^^^^^^^^^^
+   | |
+   | expected this to be a list
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error: `cfg` predicate is not specified
-  --> $DIR/cfg-attr-syntax-validation.rs:7:1
+error[E0805]: malformed `cfg` attribute input
+  --> $DIR/cfg-attr-syntax-validation.rs:11:1
    |
 LL | #[cfg()]
-   | ^^^^^^^^ help: expected syntax is: `cfg(/* predicate */)`
+   | ^^^^^--^
+   | |    |
+   | |    expected a single argument here
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error: multiple `cfg` predicates are specified
-  --> $DIR/cfg-attr-syntax-validation.rs:10:10
+error[E0805]: malformed `cfg` attribute input
+  --> $DIR/cfg-attr-syntax-validation.rs:16:1
    |
 LL | #[cfg(a, b)]
-   |          ^
+   | ^^^^^------^
+   | |    |
+   | |    expected a single argument here
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error: `cfg` predicate key cannot be a literal
-  --> $DIR/cfg-attr-syntax-validation.rs:13:7
+error: `cfg` predicate key must be an identifier
+  --> $DIR/cfg-attr-syntax-validation.rs:21:7
    |
 LL | #[cfg("str")]
    |       ^^^^^
 
 error: `cfg` predicate key must be an identifier
-  --> $DIR/cfg-attr-syntax-validation.rs:16:7
+  --> $DIR/cfg-attr-syntax-validation.rs:24:7
    |
 LL | #[cfg(a::b)]
    |       ^^^^
 
 error[E0537]: invalid predicate `a`
-  --> $DIR/cfg-attr-syntax-validation.rs:19:7
+  --> $DIR/cfg-attr-syntax-validation.rs:27:7
    |
 LL | #[cfg(a())]
    |       ^^^
 
-error[E0565]: literal in `cfg` predicate value must be a string
-  --> $DIR/cfg-attr-syntax-validation.rs:22:11
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-attr-syntax-validation.rs:30:1
    |
 LL | #[cfg(a = 10)]
-   |           ^^
+   | ^^^^^^^^^^--^^
+   | |         |
+   | |         expected a string literal here
+   | help: must be of the form: `#[cfg(predicate)]`
 
-error[E0565]: literal in `cfg` predicate value must be a string
-  --> $DIR/cfg-attr-syntax-validation.rs:25:11
+error[E0539]: malformed `cfg` attribute input
+  --> $DIR/cfg-attr-syntax-validation.rs:34:1
    |
 LL | #[cfg(a = b"hi")]
-   |           -^^^^
+   | ^^^^^^^^^^-^^^^^^
    |           |
    |           help: consider removing the prefix
+   |
+   = note: expected a normal string literal, not a byte string literal
 
 error: expected unsuffixed literal, found `expr` metavariable
-  --> $DIR/cfg-attr-syntax-validation.rs:30:25
+  --> $DIR/cfg-attr-syntax-validation.rs:40:25
    |
 LL |         #[cfg(feature = $expr)]
    |                         ^^^^^
@@ -67,5 +84,5 @@ LL | generate_s10!(concat!("nonexistent"));
 
 error: aborting due to 10 previous errors
 
-Some errors have detailed explanations: E0537, E0565.
+Some errors have detailed explanations: E0537, E0539, E0805.
 For more information about an error, try `rustc --explain E0537`.
diff --git a/tests/ui/macros/macro-outer-attributes.stderr b/tests/ui/macros/macro-outer-attributes.stderr
index a894c90f4c3..9215754d4bb 100644
--- a/tests/ui/macros/macro-outer-attributes.stderr
+++ b/tests/ui/macros/macro-outer-attributes.stderr
@@ -10,16 +10,10 @@ note: found an item that was configured out
 LL |       pub fn bar() { });
    |              ^^^
 note: the item is gated here
-  --> $DIR/macro-outer-attributes.rs:5:45
-   |
-LL |                        $i:item) => (mod $nm { #[$a] $i }); }
-   |                                               ^^^^^
-LL |
-LL | / test!(a,
-LL | |       #[cfg(false)],
-LL | |       pub fn bar() { });
-   | |_______________________- in this macro invocation
-   = note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)
+  --> $DIR/macro-outer-attributes.rs:8:13
+   |
+LL |       #[cfg(false)],
+   |             ^^^^^
 help: consider importing this function
    |
 LL + use b::bar;
diff --git a/tests/ui/span/E0536.rs b/tests/ui/span/E0536.rs
index 72de7b6f453..ae073363354 100644
--- a/tests/ui/span/E0536.rs
+++ b/tests/ui/span/E0536.rs
@@ -1,4 +1,3 @@
-#[cfg(not())] //~ ERROR E0536
-pub fn something() {}
-
-pub fn main() {}
+pub fn main() {
+    if cfg!(not()) { } //~ ERROR E0536
+}
diff --git a/tests/ui/span/E0536.stderr b/tests/ui/span/E0536.stderr
index b0f652208c4..6c25f9140a1 100644
--- a/tests/ui/span/E0536.stderr
+++ b/tests/ui/span/E0536.stderr
@@ -1,8 +1,8 @@
 error[E0536]: expected 1 cfg-pattern
-  --> $DIR/E0536.rs:1:7
+  --> $DIR/E0536.rs:2:13
    |
-LL | #[cfg(not())]
-   |       ^^^^^
+LL |     if cfg!(not()) { }
+   |             ^^^^^
 
 error: aborting due to 1 previous error