about summary refs log tree commit diff
path: root/compiler/rustc_attr_parsing/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_attr_parsing/src')
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs20
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg.rs467
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg_old.rs247
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs12
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/deprecation.rs2
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/dummy.rs19
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/inline.rs4
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/link_attrs.rs77
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs7
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs47
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/must_use.rs2
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/path.rs2
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs40
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs10
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/test_attrs.rs2
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/traits.rs109
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/transparency.rs2
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs199
-rw-r--r--compiler/rustc_attr_parsing/src/lib.rs5
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs25
20 files changed, 992 insertions, 306 deletions
diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
index 1c51c3eee4e..a6bd2306ec5 100644
--- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
@@ -27,6 +27,26 @@ impl<S: Stage> CombineAttributeParser<S> for AllowInternalUnstableParser {
     }
 }
 
+pub(crate) struct UnstableFeatureBoundParser;
+impl<S: Stage> CombineAttributeParser<S> for UnstableFeatureBoundParser {
+    const PATH: &'static [rustc_span::Symbol] = &[sym::unstable_feature_bound];
+    type Item = (Symbol, Span);
+    const CONVERT: ConvertFn<Self::Item> = |items, _| AttributeKind::UnstableFeatureBound(items);
+    const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ...");
+
+    fn extend<'c>(
+        cx: &'c mut AcceptContext<'_, '_, S>,
+        args: &'c ArgParser<'_>,
+    ) -> impl IntoIterator<Item = Self::Item> {
+        if !cx.features().staged_api() {
+            cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span });
+        }
+        parse_unstable(cx, args, <Self as CombineAttributeParser<S>>::PATH[0])
+            .into_iter()
+            .zip(iter::repeat(cx.attr_span))
+    }
+}
+
 pub(crate) struct AllowConstFnUnstableParser;
 impl<S: Stage> CombineAttributeParser<S> for AllowConstFnUnstableParser {
     const PATH: &[Symbol] = &[sym::rustc_allow_const_fn_unstable];
diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
index a8d9229cbc3..6373cf6e08a 100644
--- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
@@ -1,247 +1,298 @@
-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, ShouldEmit, 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);
-}
-
-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);
-    }
-}
+pub const CFG_TEMPLATE: AttributeTemplate = template!(List: "predicate");
 
-#[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(), cx.features_option());
+    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 let Some(features) = cx.features_option()
+        && !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>,
+    emit_lints: ShouldEmit,
+) -> EvalConfigResult {
+    match cfg_entry {
+        CfgEntry::All(subs, ..) => {
+            let mut all = None;
+            for sub in subs {
+                let res = eval_config_entry(sess, sub, id, features, emit_lints);
+                // 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, emit_lints);
+                // 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, emit_lints).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()
+        }
+        CfgEntry::NameValue { name, name_span, value, span } => {
+            if let ShouldEmit::ErrorsAndLints = emit_lints {
+                match sess.psess.check_config.expecteds.get(name) {
+                    Some(ExpectedValues::Some(values))
+                        if !values.contains(&value.map(|(v, _)| v)) =>
                     {
-                        feature_err(
+                        id.emit_span_lint(
                             sess,
-                            sym::cfg_target_compact,
-                            cfg.span,
-                            fluent_generated::attr_parsing_unstable_cfg_target_compact,
-                        )
-                        .emit();
+                            UNEXPECTED_CFGS,
+                            *span,
+                            BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
+                        );
                     }
-
-                    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
+                    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/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
index cb3956d46a0..34d9b048348 100644
--- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -15,7 +15,7 @@ pub(crate) struct OptimizeParser;
 
 impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
     const PATH: &[Symbol] = &[sym::optimize];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(List: "size|speed|none");
 
@@ -56,7 +56,7 @@ pub(crate) struct ExportNameParser;
 
 impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
     const PATH: &[rustc_span::Symbol] = &[sym::export_name];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
 
@@ -334,3 +334,11 @@ impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser {
         features
     }
 }
+
+pub(crate) struct OmitGdbPrettyPrinterSectionParser;
+
+impl<S: Stage> NoArgsAttributeParser<S> for OmitGdbPrettyPrinterSectionParser {
+    const PATH: &[Symbol] = &[sym::omit_gdb_pretty_printer_section];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::OmitGdbPrettyPrinterSection;
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
index 702ad66f578..08cf1ab5d19 100644
--- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
@@ -36,7 +36,7 @@ fn get<S: Stage>(
 
 impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
     const PATH: &[Symbol] = &[sym::deprecated];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
     const TEMPLATE: AttributeTemplate = template!(
         Word,
diff --git a/compiler/rustc_attr_parsing/src/attributes/dummy.rs b/compiler/rustc_attr_parsing/src/attributes/dummy.rs
new file mode 100644
index 00000000000..e5e1c3bb6b6
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs
@@ -0,0 +1,19 @@
+use rustc_attr_data_structures::AttributeKind;
+use rustc_feature::{AttributeTemplate, template};
+use rustc_span::{Symbol, sym};
+
+use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
+use crate::context::{AcceptContext, Stage};
+use crate::parser::ArgParser;
+
+pub(crate) struct DummyParser;
+impl<S: Stage> SingleAttributeParser<S> for DummyParser {
+    const PATH: &[Symbol] = &[sym::rustc_dummy];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
+    const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really
+
+    fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser<'_>) -> Option<AttributeKind> {
+        Some(AttributeKind::Dummy)
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs
index 11844f4cd95..fe812175218 100644
--- a/compiler/rustc_attr_parsing/src/attributes/inline.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs
@@ -16,7 +16,7 @@ pub(crate) struct InlineParser;
 
 impl<S: Stage> SingleAttributeParser<S> for InlineParser {
     const PATH: &'static [Symbol] = &[sym::inline];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(Word, List: "always|never");
 
@@ -57,7 +57,7 @@ pub(crate) struct RustcForceInlineParser;
 
 impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
     const PATH: &'static [Symbol] = &[sym::rustc_force_inline];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(Word, List: "reason", NameValueStr: "reason");
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
index e298053ab76..960cebd8925 100644
--- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
@@ -1,18 +1,20 @@
 use rustc_attr_data_structures::AttributeKind;
-use rustc_attr_data_structures::AttributeKind::{LinkName, LinkSection};
+use rustc_attr_data_structures::AttributeKind::{LinkName, LinkOrdinal, LinkSection};
 use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Symbol, sym};
+use rustc_span::{Span, Symbol, sym};
 
-use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::{AcceptContext, Stage};
+use crate::attributes::{
+    AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
+};
+use crate::context::{AcceptContext, Stage, parse_single_integer};
 use crate::parser::ArgParser;
-use crate::session_diagnostics::NullOnLinkSection;
+use crate::session_diagnostics::{LinkOrdinalOutOfRange, NullOnLinkSection};
 
 pub(crate) struct LinkNameParser;
 
 impl<S: Stage> SingleAttributeParser<S> for LinkNameParser {
     const PATH: &[Symbol] = &[sym::link_name];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
 
@@ -34,7 +36,7 @@ pub(crate) struct LinkSectionParser;
 
 impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
     const PATH: &[Symbol] = &[sym::link_section];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
 
@@ -57,3 +59,64 @@ impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
         Some(LinkSection { name, span: cx.attr_span })
     }
 }
+
+pub(crate) struct ExportStableParser;
+impl<S: Stage> NoArgsAttributeParser<S> for ExportStableParser {
+    const PATH: &[Symbol] = &[sym::export_stable];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ExportStable;
+}
+
+pub(crate) struct FfiConstParser;
+impl<S: Stage> NoArgsAttributeParser<S> for FfiConstParser {
+    const PATH: &[Symbol] = &[sym::ffi_const];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiConst;
+}
+
+pub(crate) struct FfiPureParser;
+impl<S: Stage> NoArgsAttributeParser<S> for FfiPureParser {
+    const PATH: &[Symbol] = &[sym::ffi_pure];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure;
+}
+
+pub(crate) struct StdInternalSymbolParser;
+impl<S: Stage> NoArgsAttributeParser<S> for StdInternalSymbolParser {
+    const PATH: &[Symbol] = &[sym::rustc_std_internal_symbol];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::StdInternalSymbol;
+}
+
+pub(crate) struct LinkOrdinalParser;
+
+impl<S: Stage> SingleAttributeParser<S> for LinkOrdinalParser {
+    const PATH: &[Symbol] = &[sym::link_ordinal];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const TEMPLATE: AttributeTemplate = template!(List: "ordinal");
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let ordinal = parse_single_integer(cx, args)?;
+
+        // According to the table at
+        // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-header, the
+        // ordinal must fit into 16 bits. Similarly, the Ordinal field in COFFShortExport (defined
+        // in llvm/include/llvm/Object/COFFImportFile.h), which we use to communicate import
+        // information to LLVM for `#[link(kind = "raw-dylib"_])`, is also defined to be uint16_t.
+        //
+        // FIXME: should we allow an ordinal of 0?  The MSVC toolchain has inconsistent support for
+        // this: both LINK.EXE and LIB.EXE signal errors and abort when given a .DEF file that
+        // specifies a zero ordinal. However, llvm-dlltool is perfectly happy to generate an import
+        // library for such a .DEF file, and MSVC's LINK.EXE is also perfectly happy to consume an
+        // import library produced by LLVM with an ordinal of 0, and it generates an .EXE.  (I
+        // don't know yet if the resulting EXE runs, as I haven't yet built the necessary DLL --
+        // see earlier comment about LINK.EXE failing.)
+        let Ok(ordinal) = ordinal.try_into() else {
+            cx.emit_err(LinkOrdinalOutOfRange { span: cx.attr_span, ordinal });
+            return None;
+        };
+
+        Some(LinkOrdinal { ordinal, span: cx.attr_span })
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
index 8ad98c8d1d4..0eceff53e8b 100644
--- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
@@ -24,3 +24,10 @@ impl<S: Stage> NoArgsAttributeParser<S> for PassByValueParser {
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::PassByValue;
 }
+
+pub(crate) struct AutomaticallyDerivedParser;
+impl<S: Stage> NoArgsAttributeParser<S> for AutomaticallyDerivedParser {
+    const PATH: &[Symbol] = &[sym::automatically_derived];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::AutomaticallyDerived;
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index ba7572434df..200f1381960 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -27,9 +27,11 @@ 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;
+pub(crate) mod dummy;
 pub(crate) mod inline;
 pub(crate) mod link_attrs;
 pub(crate) mod lint_helpers;
@@ -139,7 +141,7 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
             if let Some(pa) = T::convert(cx, args) {
                 match T::ATTRIBUTE_ORDER {
                     // keep the first and report immediately. ignore this attribute
-                    AttributeOrder::KeepFirst => {
+                    AttributeOrder::KeepInnermost => {
                         if let Some((_, unused)) = group.1 {
                             T::ON_DUPLICATE.exec::<T>(cx, cx.attr_span, unused);
                             return;
@@ -147,7 +149,7 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
                     }
                     // keep the new one and warn about the previous,
                     // then replace
-                    AttributeOrder::KeepLast => {
+                    AttributeOrder::KeepOutermost => {
                         if let Some((_, used)) = group.1 {
                             T::ON_DUPLICATE.exec::<T>(cx, used, cx.attr_span);
                         }
@@ -164,9 +166,6 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
     }
 }
 
-// FIXME(jdonszelmann): logic is implemented but the attribute parsers needing
-// them will be merged in another PR
-#[allow(unused)]
 pub(crate) enum OnDuplicate<S: Stage> {
     /// Give a default warning
     Warn,
@@ -212,25 +211,29 @@ impl<S: Stage> OnDuplicate<S> {
         }
     }
 }
-//
-// FIXME(jdonszelmann): logic is implemented but the attribute parsers needing
-// them will be merged in another PR
-#[allow(unused)]
+
 pub(crate) enum AttributeOrder {
-    /// Duplicates after the first attribute will be an error.
+    /// Duplicates after the innermost instance of the attribute will be an error/warning.
+    /// Only keep the lowest attribute.
     ///
-    /// This should be used where duplicates would be ignored, but carry extra
-    /// meaning that could cause confusion. For example, `#[stable(since="1.0")]
-    /// #[stable(since="2.0")]`, which version should be used for `stable`?
-    KeepFirst,
-
-    /// Duplicates preceding the last instance of the attribute will be a
-    /// warning, with a note that this will be an error in the future.
+    /// Attributes are processed from bottom to top, so this raises a warning/error on all the attributes
+    /// further above the lowest one:
+    /// ```
+    /// #[stable(since="1.0")] //~ WARNING duplicated attribute
+    /// #[stable(since="2.0")]
+    /// ```
+    KeepInnermost,
+
+    /// Duplicates before the outermost instance of the attribute will be an error/warning.
+    /// Only keep the highest attribute.
     ///
-    /// This is the same as `FutureWarnFollowing`, except the last attribute is
-    /// the one that is "used". Ideally these can eventually migrate to
-    /// `ErrorPreceding`.
-    KeepLast,
+    /// Attributes are processed from bottom to top, so this raises a warning/error on all the attributes
+    /// below the highest one:
+    /// ```
+    /// #[path="foo.rs"]
+    /// #[path="bar.rs"] //~ WARNING duplicated attribute
+    /// ```
+    KeepOutermost,
 }
 
 /// An even simpler version of [`SingleAttributeParser`]:
@@ -256,7 +259,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> Default for WithoutArgs<T, S> {
 
 impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for WithoutArgs<T, S> {
     const PATH: &[Symbol] = T::PATH;
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = T::ON_DUPLICATE;
     const TEMPLATE: AttributeTemplate = template!(Word);
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs
index b5eb85f68b4..e0a3e675509 100644
--- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs
@@ -12,7 +12,7 @@ pub(crate) struct MustUseParser;
 
 impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
     const PATH: &[Symbol] = &[sym::must_use];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(Word, NameValueStr: "reason");
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/path.rs b/compiler/rustc_attr_parsing/src/attributes/path.rs
index 0dfbc9a9aa8..febb1b45a18 100644
--- a/compiler/rustc_attr_parsing/src/attributes/path.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/path.rs
@@ -10,7 +10,7 @@ pub(crate) struct PathParser;
 
 impl<S: Stage> SingleAttributeParser<S> for PathParser {
     const PATH: &[Symbol] = &[sym::path];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(NameValueStr: "file");
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
index e6b6a6fe3c9..7ca951dc0bb 100644
--- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
@@ -1,23 +1,22 @@
-use rustc_ast::LitKind;
 use rustc_attr_data_structures::AttributeKind;
 use rustc_feature::{AttributeTemplate, template};
 use rustc_span::{Symbol, sym};
 
 use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::{AcceptContext, Stage};
+use crate::context::{AcceptContext, Stage, parse_single_integer};
 use crate::parser::ArgParser;
 
 pub(crate) struct RustcLayoutScalarValidRangeStart;
 
 impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeStart {
     const PATH: &'static [Symbol] = &[sym::rustc_layout_scalar_valid_range_start];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
     const TEMPLATE: AttributeTemplate = template!(List: "start");
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
-        parse_rustc_layout_scalar_valid_range(cx, args)
-            .map(|n| AttributeKind::RustcLayoutScalarValidRangeStart(n, cx.attr_span))
+        parse_single_integer(cx, args)
+            .map(|n| AttributeKind::RustcLayoutScalarValidRangeStart(Box::new(n), cx.attr_span))
     }
 }
 
@@ -25,44 +24,21 @@ pub(crate) struct RustcLayoutScalarValidRangeEnd;
 
 impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeEnd {
     const PATH: &'static [Symbol] = &[sym::rustc_layout_scalar_valid_range_end];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
     const TEMPLATE: AttributeTemplate = template!(List: "end");
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
-        parse_rustc_layout_scalar_valid_range(cx, args)
-            .map(|n| AttributeKind::RustcLayoutScalarValidRangeEnd(n, cx.attr_span))
+        parse_single_integer(cx, args)
+            .map(|n| AttributeKind::RustcLayoutScalarValidRangeEnd(Box::new(n), cx.attr_span))
     }
 }
 
-fn parse_rustc_layout_scalar_valid_range<S: Stage>(
-    cx: &mut AcceptContext<'_, '_, S>,
-    args: &ArgParser<'_>,
-) -> Option<Box<u128>> {
-    let Some(list) = args.list() else {
-        cx.expected_list(cx.attr_span);
-        return None;
-    };
-    let Some(single) = list.single() else {
-        cx.expected_single_argument(list.span);
-        return None;
-    };
-    let Some(lit) = single.lit() else {
-        cx.expected_integer_literal(single.span());
-        return None;
-    };
-    let LitKind::Int(num, _ty) = lit.kind else {
-        cx.expected_integer_literal(single.span());
-        return None;
-    };
-    Some(Box::new(num.0))
-}
-
 pub(crate) struct RustcObjectLifetimeDefaultParser;
 
 impl<S: Stage> SingleAttributeParser<S> for RustcObjectLifetimeDefaultParser {
     const PATH: &[rustc_span::Symbol] = &[sym::rustc_object_lifetime_default];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
     const TEMPLATE: AttributeTemplate = template!(Word);
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 6bccd0042a8..8f405e5aad9 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -98,6 +98,16 @@ impl<S: Stage> AttributeParser<S> for StabilityParser {
             }
         }
 
+        if let Some((Stability { level: StabilityLevel::Stable { .. }, .. }, _)) = self.stability {
+            for other_attr in cx.all_attrs {
+                if other_attr.word_is(sym::unstable_feature_bound) {
+                    cx.emit_err(session_diagnostics::UnstableFeatureBoundIncompatibleStability {
+                        span: cx.target_span,
+                    });
+                }
+            }
+        }
+
         let (stability, span) = self.stability?;
 
         Some(AttributeKind::Stability { stability, span })
diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
index cea3ee52ff4..ee81f64860f 100644
--- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
@@ -11,7 +11,7 @@ pub(crate) struct IgnoreParser;
 
 impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
     const PATH: &[Symbol] = &[sym::ignore];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
     const TEMPLATE: AttributeTemplate = template!(Word, NameValueStr: "reason");
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs
index 83a98c53c7f..e69a533699b 100644
--- a/compiler/rustc_attr_parsing/src/attributes/traits.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs
@@ -2,17 +2,18 @@ use core::mem;
 
 use rustc_attr_data_structures::AttributeKind;
 use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Symbol, sym};
+use rustc_span::{Span, Symbol, sym};
 
-use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
+use crate::attributes::{
+    AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
+};
 use crate::context::{AcceptContext, Stage};
 use crate::parser::ArgParser;
 
 pub(crate) struct SkipDuringMethodDispatchParser;
-
 impl<S: Stage> SingleAttributeParser<S> for SkipDuringMethodDispatchParser {
     const PATH: &[Symbol] = &[sym::rustc_skip_during_method_dispatch];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
 
     const TEMPLATE: AttributeTemplate = template!(List: "array, boxed_slice");
@@ -52,3 +53,103 @@ impl<S: Stage> SingleAttributeParser<S> for SkipDuringMethodDispatchParser {
         Some(AttributeKind::SkipDuringMethodDispatch { array, boxed_slice, span: cx.attr_span })
     }
 }
+
+pub(crate) struct ParenSugarParser;
+impl<S: Stage> NoArgsAttributeParser<S> for ParenSugarParser {
+    const PATH: &[Symbol] = &[sym::rustc_paren_sugar];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::ParenSugar;
+}
+
+pub(crate) struct TypeConstParser;
+impl<S: Stage> NoArgsAttributeParser<S> for TypeConstParser {
+    const PATH: &[Symbol] = &[sym::type_const];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::TypeConst;
+}
+
+// Markers
+
+pub(crate) struct MarkerParser;
+impl<S: Stage> NoArgsAttributeParser<S> for MarkerParser {
+    const PATH: &[Symbol] = &[sym::marker];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::Marker;
+}
+
+pub(crate) struct DenyExplicitImplParser;
+impl<S: Stage> NoArgsAttributeParser<S> for DenyExplicitImplParser {
+    const PATH: &[Symbol] = &[sym::rustc_deny_explicit_impl];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::DenyExplicitImpl;
+}
+
+pub(crate) struct DoNotImplementViaObjectParser;
+impl<S: Stage> NoArgsAttributeParser<S> for DoNotImplementViaObjectParser {
+    const PATH: &[Symbol] = &[sym::rustc_do_not_implement_via_object];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::DoNotImplementViaObject;
+}
+
+// FIXME(const_trait_impl): remove this
+// Const traits
+
+pub(crate) struct ConstTraitParser;
+impl<S: Stage> NoArgsAttributeParser<S> for ConstTraitParser {
+    const PATH: &[Symbol] = &[sym::const_trait];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::ConstTrait;
+}
+
+// Specialization
+
+pub(crate) struct SpecializationTraitParser;
+impl<S: Stage> NoArgsAttributeParser<S> for SpecializationTraitParser {
+    const PATH: &[Symbol] = &[sym::rustc_specialization_trait];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::SpecializationTrait;
+}
+
+pub(crate) struct UnsafeSpecializationMarkerParser;
+impl<S: Stage> NoArgsAttributeParser<S> for UnsafeSpecializationMarkerParser {
+    const PATH: &[Symbol] = &[sym::rustc_unsafe_specialization_marker];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::UnsafeSpecializationMarker;
+}
+
+// Coherence
+
+pub(crate) struct CoinductiveParser;
+impl<S: Stage> NoArgsAttributeParser<S> for CoinductiveParser {
+    const PATH: &[Symbol] = &[sym::rustc_coinductive];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::Coinductive;
+}
+
+pub(crate) struct AllowIncoherentImplParser;
+impl<S: Stage> NoArgsAttributeParser<S> for AllowIncoherentImplParser {
+    const PATH: &[Symbol] = &[sym::rustc_allow_incoherent_impl];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::AllowIncoherentImpl;
+}
+
+pub(crate) struct CoherenceIsCoreParser;
+impl<S: Stage> NoArgsAttributeParser<S> for CoherenceIsCoreParser {
+    const PATH: &[Symbol] = &[sym::rustc_coherence_is_core];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::CoherenceIsCore;
+}
+
+pub(crate) struct FundamentalParser;
+impl<S: Stage> NoArgsAttributeParser<S> for FundamentalParser {
+    const PATH: &[Symbol] = &[sym::fundamental];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Fundamental;
+}
+
+pub(crate) struct PointeeParser;
+impl<S: Stage> NoArgsAttributeParser<S> for PointeeParser {
+    const PATH: &[Symbol] = &[sym::pointee];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::Pointee;
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
index ce5ceb9139a..c9fdc57cc06 100644
--- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
@@ -14,7 +14,7 @@ pub(crate) struct TransparencyParser;
 #[allow(rustc::diagnostic_outside_of_impl)]
 impl<S: Stage> SingleAttributeParser<S> for TransparencyParser {
     const PATH: &[Symbol] = &[sym::rustc_macro_transparency];
-    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Custom(|cx, used, unused| {
         cx.dcx().span_err(vec![used, unused], "multiple macro transparency attributes");
     });
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 939f4a6fde7..1449680e35b 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -1,11 +1,10 @@
 use std::cell::RefCell;
 use std::collections::BTreeMap;
-use std::marker::PhantomData;
 use std::ops::{Deref, DerefMut};
 use std::sync::LazyLock;
 
 use private::Sealed;
-use rustc_ast::{self as ast, MetaItemLit, NodeId};
+use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId};
 use rustc_attr_data_structures::AttributeKind;
 use rustc_attr_data_structures::lints::{AttributeLint, AttributeLintKind};
 use rustc_errors::{DiagCtxtHandle, Diagnostic};
@@ -14,16 +13,24 @@ use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, HirI
 use rustc_session::Session;
 use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
 
-use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser};
+use crate::attributes::allow_unstable::{
+    AllowConstFnUnstableParser, AllowInternalUnstableParser, UnstableFeatureBoundParser,
+};
 use crate::attributes::codegen_attrs::{
-    ColdParser, ExportNameParser, NakedParser, NoMangleParser, OptimizeParser, TargetFeatureParser,
-    TrackCallerParser, UsedParser,
+    ColdParser, ExportNameParser, NakedParser, NoMangleParser, OmitGdbPrettyPrinterSectionParser,
+    OptimizeParser, TargetFeatureParser, TrackCallerParser, UsedParser,
 };
 use crate::attributes::confusables::ConfusablesParser;
 use crate::attributes::deprecation::DeprecationParser;
+use crate::attributes::dummy::DummyParser;
 use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
-use crate::attributes::link_attrs::{LinkNameParser, LinkSectionParser};
-use crate::attributes::lint_helpers::{AsPtrParser, PassByValueParser, PubTransparentParser};
+use crate::attributes::link_attrs::{
+    ExportStableParser, FfiConstParser, FfiPureParser, LinkNameParser, LinkOrdinalParser,
+    LinkSectionParser, StdInternalSymbolParser,
+};
+use crate::attributes::lint_helpers::{
+    AsPtrParser, AutomaticallyDerivedParser, PassByValueParser, PubTransparentParser,
+};
 use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser};
 use crate::attributes::must_use::MustUseParser;
 use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser;
@@ -39,7 +46,12 @@ use crate::attributes::stability::{
     BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser,
 };
 use crate::attributes::test_attrs::IgnoreParser;
-use crate::attributes::traits::SkipDuringMethodDispatchParser;
+use crate::attributes::traits::{
+    AllowIncoherentImplParser, CoherenceIsCoreParser, CoinductiveParser, ConstTraitParser,
+    DenyExplicitImplParser, DoNotImplementViaObjectParser, FundamentalParser, MarkerParser,
+    ParenSugarParser, PointeeParser, SkipDuringMethodDispatchParser, SpecializationTraitParser,
+    TypeConstParser, UnsafeSpecializationMarkerParser,
+};
 use crate::attributes::transparency::TransparencyParser;
 use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
 use crate::parser::{ArgParser, MetaItemParser, PathParser};
@@ -123,14 +135,17 @@ attribute_parsers!(
         Combine<AllowInternalUnstableParser>,
         Combine<ReprParser>,
         Combine<TargetFeatureParser>,
+        Combine<UnstableFeatureBoundParser>,
         // tidy-alphabetical-end
 
         // tidy-alphabetical-start
         Single<DeprecationParser>,
+        Single<DummyParser>,
         Single<ExportNameParser>,
         Single<IgnoreParser>,
         Single<InlineParser>,
         Single<LinkNameParser>,
+        Single<LinkOrdinalParser>,
         Single<LinkSectionParser>,
         Single<MustUseParser>,
         Single<OptimizeParser>,
@@ -141,18 +156,37 @@ attribute_parsers!(
         Single<RustcObjectLifetimeDefaultParser>,
         Single<SkipDuringMethodDispatchParser>,
         Single<TransparencyParser>,
+        Single<WithoutArgs<AllowIncoherentImplParser>>,
         Single<WithoutArgs<AsPtrParser>>,
+        Single<WithoutArgs<AutomaticallyDerivedParser>>,
+        Single<WithoutArgs<CoherenceIsCoreParser>>,
+        Single<WithoutArgs<CoinductiveParser>>,
         Single<WithoutArgs<ColdParser>>,
         Single<WithoutArgs<ConstContinueParser>>,
         Single<WithoutArgs<ConstStabilityIndirectParser>>,
+        Single<WithoutArgs<ConstTraitParser>>,
+        Single<WithoutArgs<DenyExplicitImplParser>>,
+        Single<WithoutArgs<DoNotImplementViaObjectParser>>,
+        Single<WithoutArgs<ExportStableParser>>,
+        Single<WithoutArgs<FfiConstParser>>,
+        Single<WithoutArgs<FfiPureParser>>,
+        Single<WithoutArgs<FundamentalParser>>,
         Single<WithoutArgs<LoopMatchParser>>,
+        Single<WithoutArgs<MarkerParser>>,
         Single<WithoutArgs<MayDangleParser>>,
         Single<WithoutArgs<NoImplicitPreludeParser>>,
         Single<WithoutArgs<NoMangleParser>>,
         Single<WithoutArgs<NonExhaustiveParser>>,
+        Single<WithoutArgs<OmitGdbPrettyPrinterSectionParser>>,
+        Single<WithoutArgs<ParenSugarParser>>,
         Single<WithoutArgs<PassByValueParser>>,
+        Single<WithoutArgs<PointeeParser>>,
         Single<WithoutArgs<PubTransparentParser>>,
+        Single<WithoutArgs<SpecializationTraitParser>>,
+        Single<WithoutArgs<StdInternalSymbolParser>>,
         Single<WithoutArgs<TrackCallerParser>>,
+        Single<WithoutArgs<TypeConstParser>>,
+        Single<WithoutArgs<UnsafeSpecializationMarkerParser>>,
         // tidy-alphabetical-end
     ];
 );
@@ -171,7 +205,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
@@ -183,8 +221,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.should_emit() {
+            sess.dcx().emit_err(diag)
+        } else {
+            sess.dcx().create_err(diag).delay_as_bug()
+        }
     }
 }
 
@@ -197,20 +243,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: ShouldEmit,
+}
 /// 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,
@@ -226,7 +281,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
@@ -441,7 +496,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>,
@@ -503,13 +558,32 @@ pub enum OmitDoc {
     Skip,
 }
 
+#[derive(Copy, Clone)]
+pub enum ShouldEmit {
+    /// The operation will emit errors and lints.
+    /// This is usually what you need.
+    ErrorsAndLints,
+    /// The operation will emit *not* errors and lints.
+    /// Use this if you are *sure* that this operation will be called at a different time with `ShouldEmit::Emit`.
+    Nothing,
+}
+
+impl ShouldEmit {
+    pub fn should_emit(&self) -> bool {
+        match self {
+            ShouldEmit::ErrorsAndLints => true,
+            ShouldEmit::Nothing => false,
+        }
+    }
+}
+
 /// Context created once, for example as part of the ast lowering
 /// context, through which all attributes can be lowered.
 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.
     ///
@@ -538,13 +612,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: ShouldEmit::Nothing },
         };
         let mut parsed = p.parse_attribute_list(
             attrs,
@@ -560,11 +635,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: ShouldEmit,
+        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 {
@@ -575,6 +694,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()
     }
@@ -709,6 +832,11 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
         attributes
     }
 
+    /// Returns whether there is a parser for an attribute with this name
+    pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
+        Late::parsers().0.contains_key(path)
+    }
+
     fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {
         match args {
             ast::AttrArgs::Empty => AttrArgs::Empty,
@@ -741,3 +869,32 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
         }
     }
 }
+
+/// Parse a single integer.
+///
+/// Used by attributes that take a single integer as argument, such as
+/// `#[link_ordinal]` and `#[rustc_layout_scalar_valid_range_start]`.
+/// `cx` is the context given to the attribute.
+/// `args` is the parser for the attribute arguments.
+pub(crate) fn parse_single_integer<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    args: &ArgParser<'_>,
+) -> Option<u128> {
+    let Some(list) = args.list() else {
+        cx.expected_list(cx.attr_span);
+        return None;
+    };
+    let Some(single) = list.single() else {
+        cx.expected_single_argument(list.span);
+        return None;
+    };
+    let Some(lit) = single.lit() else {
+        cx.expected_integer_literal(single.span());
+        return None;
+    };
+    let LitKind::Int(num, _ty) = lit.kind else {
+        cx.expected_integer_literal(single.span());
+        return None;
+    };
+    Some(num.0)
+}
diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs
index 47eeb63bad3..dc54cb6b840 100644
--- a/compiler/rustc_attr_parsing/src/lib.rs
+++ b/compiler/rustc_attr_parsing/src/lib.rs
@@ -90,11 +90,12 @@ 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,
 };
-pub use context::{AttributeParser, Early, Late, OmitDoc};
+pub use context::{AttributeParser, Early, Late, OmitDoc, ShouldEmit};
 pub use lints::emit_attribute_lint;
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
index 28f6786f37f..5b0bf0e6662 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -504,6 +504,14 @@ pub(crate) struct UnrecognizedReprHint {
 }
 
 #[derive(Diagnostic)]
+#[diag(attr_parsing_unstable_feature_bound_incompatible_stability)]
+#[help]
+pub(crate) struct UnstableFeatureBoundIncompatibleStability {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(attr_parsing_naked_functions_incompatible_attribute, code = E0736)]
 pub(crate) struct NakedFunctionIncompatibleAttribute {
     #[primary_span]
@@ -514,6 +522,15 @@ pub(crate) struct NakedFunctionIncompatibleAttribute {
     pub attr: String,
 }
 
+#[derive(Diagnostic)]
+#[diag(attr_parsing_link_ordinal_out_of_range)]
+#[note]
+pub(crate) struct LinkOrdinalOutOfRange {
+    #[primary_span]
+    pub span: Span,
+    pub ordinal: u128,
+}
+
 pub(crate) enum AttributeParseErrorReason {
     ExpectedNoArgs,
     ExpectedStringLiteral { byte_string: Option<Span> },
@@ -584,7 +601,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(