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.rs32
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/body.rs12
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg.rs10
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg_old.rs2
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs417
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/confusables.rs16
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/crate_level.rs185
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/deprecation.rs48
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/dummy.rs4
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/inline.rs40
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/link_attrs.rs547
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs28
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/loop_match.rs8
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs135
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs33
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/must_use.rs35
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs8
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs14
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/path.rs15
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/prelude.rs29
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs139
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/prototype.rs141
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/repr.rs60
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs16
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/semantics.rs7
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs144
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/test_attrs.rs78
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/traits.rs40
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/transparency.rs12
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/util.rs41
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs560
-rw-r--r--compiler/rustc_attr_parsing/src/interface.rs362
-rw-r--r--compiler/rustc_attr_parsing/src/lib.rs34
-rw-r--r--compiler/rustc_attr_parsing/src/lints.rs52
-rw-r--r--compiler/rustc_attr_parsing/src/parser.rs490
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs338
-rw-r--r--compiler/rustc_attr_parsing/src/target_checking.rs278
-rw-r--r--compiler/rustc_attr_parsing/src/validate_attr.rs340
38 files changed, 3848 insertions, 902 deletions
diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
index a6bd2306ec5..088fa73d742 100644
--- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
@@ -1,12 +1,6 @@
 use std::iter;
 
-use rustc_attr_data_structures::AttributeKind;
-use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Span, Symbol, sym};
-
-use super::{CombineAttributeParser, ConvertFn};
-use crate::context::{AcceptContext, Stage};
-use crate::parser::ArgParser;
+use super::prelude::*;
 use crate::session_diagnostics;
 
 pub(crate) struct AllowInternalUnstableParser;
@@ -15,7 +9,13 @@ impl<S: Stage> CombineAttributeParser<S> for AllowInternalUnstableParser {
     type Item = (Symbol, Span);
     const CONVERT: ConvertFn<Self::Item> =
         |items, span| AttributeKind::AllowInternalUnstable(items, span);
-    const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ...");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::MacroDef),
+        Allow(Target::Fn),
+        Warn(Target::Field),
+        Warn(Target::Arm),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
@@ -32,7 +32,12 @@ 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, ...");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Impl { of_trait: true }),
+        Allow(Target::Trait),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
@@ -53,7 +58,14 @@ impl<S: Stage> CombineAttributeParser<S> for AllowConstFnUnstableParser {
     type Item = Symbol;
     const CONVERT: ConvertFn<Self::Item> =
         |items, first_span| AttributeKind::AllowConstFnUnstable(items, first_span);
-    const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ...");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
diff --git a/compiler/rustc_attr_parsing/src/attributes/body.rs b/compiler/rustc_attr_parsing/src/attributes/body.rs
new file mode 100644
index 00000000000..a1492d76194
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/body.rs
@@ -0,0 +1,12 @@
+//! Attributes that can be found in function body.
+
+use super::prelude::*;
+
+pub(crate) struct CoroutineParser;
+
+impl<S: Stage> NoArgsAttributeParser<S> for CoroutineParser {
+    const PATH: &[Symbol] = &[sym::coroutine];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Closure)]);
+    const CREATE: fn(rustc_span::Span) -> AttributeKind = |span| AttributeKind::Coroutine(span);
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
index 6373cf6e08a..70855611079 100644
--- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
@@ -1,6 +1,7 @@
 use rustc_ast::{LitKind, NodeId};
-use rustc_attr_data_structures::{CfgEntry, RustcVersion};
 use rustc_feature::{AttributeTemplate, Features, template};
+use rustc_hir::RustcVersion;
+use rustc_hir::attrs::CfgEntry;
 use rustc_session::Session;
 use rustc_session::config::ExpectedValues;
 use rustc_session::lint::BuiltinLintDiag;
@@ -15,7 +16,10 @@ use crate::{
     CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
 };
 
-pub const CFG_TEMPLATE: AttributeTemplate = template!(List: "predicate");
+pub const CFG_TEMPLATE: AttributeTemplate = template!(
+    List: &["predicate"],
+    "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
+);
 
 pub fn parse_cfg_attr<'c, S: Stage>(
     cx: &'c mut AcceptContext<'_, '_, S>,
@@ -32,7 +36,7 @@ pub fn parse_cfg_attr<'c, S: Stage>(
     parse_cfg_entry(cx, single)
 }
 
-fn parse_cfg_entry<S: Stage>(
+pub(crate) fn parse_cfg_entry<S: Stage>(
     cx: &mut AcceptContext<'_, '_, S>,
     item: &MetaItemOrLitParser<'_>,
 ) -> Option<CfgEntry> {
diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs
index c5025a8b6ea..3257d898ecc 100644
--- a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs
@@ -1,7 +1,7 @@
 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_hir::RustcVersion;
 use rustc_session::Session;
 use rustc_session::config::ExpectedValues;
 use rustc_session::lint::builtin::UNEXPECTED_CFGS;
diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
index 34d9b048348..262b8213977 100644
--- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -1,15 +1,11 @@
-use rustc_attr_data_structures::{AttributeKind, OptimizeAttr, UsedBy};
-use rustc_feature::{AttributeTemplate, template};
+use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, SanitizerSet, UsedBy};
 use rustc_session::parse::feature_err;
-use rustc_span::{Span, Symbol, sym};
 
-use super::{
-    AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn,
-    NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
+use super::prelude::*;
+use crate::session_diagnostics::{
+    NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector,
+    ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
 };
-use crate::context::{AcceptContext, FinalizeContext, Stage};
-use crate::parser::ArgParser;
-use crate::session_diagnostics::{NakedFunctionIncompatibleAttribute, NullOnExport};
 
 pub(crate) struct OptimizeParser;
 
@@ -17,7 +13,14 @@ impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
     const PATH: &[Symbol] = &[sym::optimize];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(List: "size|speed|none");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Closure),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Method(MethodKind::Inherent)),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let Some(list) = args.list() else {
@@ -35,7 +38,7 @@ impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
             Some(sym::speed) => OptimizeAttr::Speed,
             Some(sym::none) => OptimizeAttr::DoNotOptimize,
             _ => {
-                cx.expected_specific_argument(single.span(), vec!["size", "speed", "none"]);
+                cx.expected_specific_argument(single.span(), &[sym::size, sym::speed, sym::none]);
                 OptimizeAttr::Default
             }
         };
@@ -49,15 +52,86 @@ pub(crate) struct ColdParser;
 impl<S: Stage> NoArgsAttributeParser<S> for ColdParser {
     const PATH: &[Symbol] = &[sym::cold];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::ForeignFn),
+        Allow(Target::Closure),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::Cold;
 }
 
+pub(crate) struct CoverageParser;
+
+impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
+    const PATH: &[Symbol] = &[sym::coverage];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Closure),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Impl { of_trait: true }),
+        Allow(Target::Impl { of_trait: false }),
+        Allow(Target::Mod),
+        Allow(Target::Crate),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]);
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(args) = args.list() else {
+            cx.expected_specific_argument_and_list(cx.attr_span, &[sym::on, sym::off]);
+            return None;
+        };
+
+        let Some(arg) = args.single() else {
+            cx.expected_single_argument(args.span);
+            return None;
+        };
+
+        let fail_incorrect_argument =
+            |span| cx.expected_specific_argument(span, &[sym::on, sym::off]);
+
+        let Some(arg) = arg.meta_item() else {
+            fail_incorrect_argument(args.span);
+            return None;
+        };
+
+        let kind = match arg.path().word_sym() {
+            Some(sym::off) => CoverageAttrKind::Off,
+            Some(sym::on) => CoverageAttrKind::On,
+            None | Some(_) => {
+                fail_incorrect_argument(arg.span());
+                return None;
+            }
+        };
+
+        Some(AttributeKind::Coverage(cx.attr_span, kind))
+    }
+}
+
 pub(crate) struct ExportNameParser;
 
 impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
     const PATH: &[rustc_span::Symbol] = &[sym::export_name];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Static),
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Warn(Target::Field),
+        Warn(Target::Arm),
+        Warn(Target::MacroDef),
+        Warn(Target::MacroCall),
+    ]);
     const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
@@ -79,6 +153,70 @@ impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
     }
 }
 
+pub(crate) struct ObjcClassParser;
+
+impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_class];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName");
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(nv) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+        let Some(classname) = nv.value_as_str() else {
+            // `#[rustc_objc_class = ...]` is expected to be used as an implementatioin detail
+            // inside a standard library macro, but `cx.expected_string_literal` exposes too much.
+            // Use a custom error message instead.
+            cx.emit_err(ObjcClassExpectedStringLiteral { span: nv.value_span });
+            return None;
+        };
+        if classname.as_str().contains('\0') {
+            // `#[rustc_objc_class = ...]` will be converted to a null-terminated string,
+            // so it may not contain any null characters.
+            cx.emit_err(NullOnObjcClass { span: nv.value_span });
+            return None;
+        }
+        Some(AttributeKind::ObjcClass { classname, span: cx.attr_span })
+    }
+}
+
+pub(crate) struct ObjcSelectorParser;
+
+impl<S: Stage> SingleAttributeParser<S> for ObjcSelectorParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_selector];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName");
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(nv) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+        let Some(methname) = nv.value_as_str() else {
+            // `#[rustc_objc_selector = ...]` is expected to be used as an implementatioin detail
+            // inside a standard library macro, but `cx.expected_string_literal` exposes too much.
+            // Use a custom error message instead.
+            cx.emit_err(ObjcSelectorExpectedStringLiteral { span: nv.value_span });
+            return None;
+        };
+        if methname.as_str().contains('\0') {
+            // `#[rustc_objc_selector = ...]` will be converted to a null-terminated string,
+            // so it may not contain any null characters.
+            cx.emit_err(NullOnObjcSelector { span: nv.value_span });
+            return None;
+        }
+        Some(AttributeKind::ObjcSelector { methname, span: cx.attr_span })
+    }
+}
+
 #[derive(Default)]
 pub(crate) struct NakedParser {
     span: Option<Span>,
@@ -99,6 +237,13 @@ impl<S: Stage> AttributeParser<S> for NakedParser {
                 this.span = Some(cx.attr_span);
             }
         })];
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Warn(Target::MacroCall),
+    ]);
 
     fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         // FIXME(jdonszelmann): upgrade this list to *parsed* attributes
@@ -138,7 +283,9 @@ impl<S: Stage> AttributeParser<S> for NakedParser {
             sym::instruction_set,
             sym::repr,
             sym::rustc_std_internal_symbol,
-            sym::align,
+            // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity
+            sym::rustc_align,
+            sym::rustc_align_static,
             // obviously compatible with self
             sym::naked,
             // documentation
@@ -190,6 +337,19 @@ pub(crate) struct TrackCallerParser;
 impl<S: Stage> NoArgsAttributeParser<S> for TrackCallerParser {
     const PATH: &[Symbol] = &[sym::track_caller];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::ForeignFn),
+        Allow(Target::Closure),
+        Warn(Target::MacroDef),
+        Warn(Target::Arm),
+        Warn(Target::Field),
+        Warn(Target::MacroCall),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller;
 }
 
@@ -197,6 +357,12 @@ pub(crate) struct NoMangleParser;
 impl<S: Stage> NoArgsAttributeParser<S> for NoMangleParser {
     const PATH: &[Symbol] = &[sym::no_mangle];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+        Allow(Target::Fn),
+        Allow(Target::Static),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle;
 }
 
@@ -213,7 +379,7 @@ pub(crate) struct UsedParser {
 impl<S: Stage> AttributeParser<S> for UsedParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[(
         &[sym::used],
-        template!(Word, List: "compiler|linker"),
+        template!(Word, List: &["compiler", "linker"]),
         |group: &mut Self, cx, args| {
             let used_by = match args {
                 ArgParser::NoArgs => UsedBy::Linker,
@@ -249,7 +415,7 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
                             UsedBy::Linker
                         }
                         _ => {
-                            cx.expected_specific_argument(l.span(), vec!["compiler", "linker"]);
+                            cx.expected_specific_argument(l.span(), &[sym::compiler, sym::linker]);
                             return;
                         }
                     }
@@ -270,6 +436,8 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
             }
         },
     )];
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::Static), Warn(Target::MacroCall)]);
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         // Ratcheting behaviour, if both `linker` and `compiler` are specified, use `linker`
@@ -281,64 +449,207 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
     }
 }
 
+fn parse_tf_attribute<'c, S: Stage>(
+    cx: &'c mut AcceptContext<'_, '_, S>,
+    args: &'c ArgParser<'_>,
+) -> impl IntoIterator<Item = (Symbol, Span)> + 'c {
+    let mut features = Vec::new();
+    let ArgParser::List(list) = args else {
+        cx.expected_list(cx.attr_span);
+        return features;
+    };
+    if list.is_empty() {
+        cx.warn_empty_attribute(cx.attr_span);
+        return features;
+    }
+    for item in list.mixed() {
+        let Some(name_value) = item.meta_item() else {
+            cx.expected_name_value(item.span(), Some(sym::enable));
+            return features;
+        };
+
+        // Validate name
+        let Some(name) = name_value.path().word_sym() else {
+            cx.expected_name_value(name_value.path().span(), Some(sym::enable));
+            return features;
+        };
+        if name != sym::enable {
+            cx.expected_name_value(name_value.path().span(), Some(sym::enable));
+            return features;
+        }
+
+        // Use value
+        let Some(name_value) = name_value.args().name_value() else {
+            cx.expected_name_value(item.span(), Some(sym::enable));
+            return features;
+        };
+        let Some(value_str) = name_value.value_as_str() else {
+            cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit()));
+            return features;
+        };
+        for feature in value_str.as_str().split(",") {
+            features.push((Symbol::intern(feature), item.span()));
+        }
+    }
+    features
+}
+
 pub(crate) struct TargetFeatureParser;
 
 impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser {
     type Item = (Symbol, Span);
     const PATH: &[Symbol] = &[sym::target_feature];
-    const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature(items, span);
-    const TEMPLATE: AttributeTemplate = template!(List: "enable = \"feat1, feat2\"");
+    const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature {
+        features: items,
+        attr_span: span,
+        was_forced: false,
+    };
+    const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
         args: &'c ArgParser<'_>,
     ) -> impl IntoIterator<Item = Self::Item> + 'c {
-        let mut features = Vec::new();
-        let ArgParser::List(list) = args else {
+        parse_tf_attribute(cx, args)
+    }
+
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Warn(Target::Statement),
+        Warn(Target::Field),
+        Warn(Target::Arm),
+        Warn(Target::MacroDef),
+        Warn(Target::MacroCall),
+    ]);
+}
+
+pub(crate) struct ForceTargetFeatureParser;
+
+impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
+    type Item = (Symbol, Span);
+    const PATH: &[Symbol] = &[sym::force_target_feature];
+    const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature {
+        features: items,
+        attr_span: span,
+        was_forced: true,
+    };
+    const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+    ]);
+
+    fn extend<'c>(
+        cx: &'c mut AcceptContext<'_, '_, S>,
+        args: &'c ArgParser<'_>,
+    ) -> impl IntoIterator<Item = Self::Item> + 'c {
+        parse_tf_attribute(cx, args)
+    }
+}
+
+pub(crate) struct SanitizeParser;
+
+impl<S: Stage> SingleAttributeParser<S> for SanitizeParser {
+    const PATH: &[Symbol] = &[sym::sanitize];
+
+    // FIXME: still checked in check_attrs.rs
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
+
+    const TEMPLATE: AttributeTemplate = template!(List: &[
+        r#"address = "on|off""#,
+        r#"kernel_address = "on|off""#,
+        r#"cfi = "on|off""#,
+        r#"hwaddress = "on|off""#,
+        r#"kcfi = "on|off""#,
+        r#"memory = "on|off""#,
+        r#"memtag = "on|off""#,
+        r#"shadow_call_stack = "on|off""#,
+        r#"thread = "on|off""#
+    ]);
+
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(list) = args.list() else {
             cx.expected_list(cx.attr_span);
-            return features;
+            return None;
         };
-        if list.is_empty() {
-            cx.warn_empty_attribute(cx.attr_span);
-            return features;
-        }
+
+        let mut on_set = SanitizerSet::empty();
+        let mut off_set = SanitizerSet::empty();
+
         for item in list.mixed() {
-            let Some(name_value) = item.meta_item() else {
-                cx.expected_name_value(item.span(), Some(sym::enable));
-                return features;
+            let Some(item) = item.meta_item() else {
+                cx.expected_name_value(item.span(), None);
+                continue;
             };
 
-            // Validate name
-            let Some(name) = name_value.path().word_sym() else {
-                cx.expected_name_value(name_value.path().span(), Some(sym::enable));
-                return features;
+            let path = item.path().word_sym();
+            let Some(value) = item.args().name_value() else {
+                cx.expected_name_value(item.span(), path);
+                continue;
             };
-            if name != sym::enable {
-                cx.expected_name_value(name_value.path().span(), Some(sym::enable));
-                return features;
-            }
 
-            // Use value
-            let Some(name_value) = name_value.args().name_value() else {
-                cx.expected_name_value(item.span(), Some(sym::enable));
-                return features;
-            };
-            let Some(value_str) = name_value.value_as_str() else {
-                cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit()));
-                return features;
+            let mut apply = |s: SanitizerSet| {
+                let is_on = match value.value_as_str() {
+                    Some(sym::on) => true,
+                    Some(sym::off) => false,
+                    Some(_) => {
+                        cx.expected_specific_argument_strings(
+                            value.value_span,
+                            &[sym::on, sym::off],
+                        );
+                        return;
+                    }
+                    None => {
+                        cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
+                        return;
+                    }
+                };
+
+                if is_on {
+                    on_set |= s;
+                } else {
+                    off_set |= s;
+                }
             };
-            for feature in value_str.as_str().split(",") {
-                features.push((Symbol::intern(feature), item.span()));
+
+            match path {
+                Some(sym::address) | Some(sym::kernel_address) => {
+                    apply(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS)
+                }
+                Some(sym::cfi) => apply(SanitizerSet::CFI),
+                Some(sym::kcfi) => apply(SanitizerSet::KCFI),
+                Some(sym::memory) => apply(SanitizerSet::MEMORY),
+                Some(sym::memtag) => apply(SanitizerSet::MEMTAG),
+                Some(sym::shadow_call_stack) => apply(SanitizerSet::SHADOWCALLSTACK),
+                Some(sym::thread) => apply(SanitizerSet::THREAD),
+                Some(sym::hwaddress) => apply(SanitizerSet::HWADDRESS),
+                _ => {
+                    cx.expected_specific_argument_strings(
+                        item.path().span(),
+                        &[
+                            sym::address,
+                            sym::cfi,
+                            sym::kcfi,
+                            sym::memory,
+                            sym::memtag,
+                            sym::shadow_call_stack,
+                            sym::thread,
+                            sym::hwaddress,
+                        ],
+                    );
+                    continue;
+                }
             }
         }
-        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;
+        Some(AttributeKind::Sanitize { on_set, off_set, span: cx.attr_span })
+    }
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/confusables.rs b/compiler/rustc_attr_parsing/src/attributes/confusables.rs
index c911908dfb3..97e78dfb136 100644
--- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs
@@ -1,11 +1,5 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_feature::template;
-use rustc_span::{Span, Symbol, sym};
-use thin_vec::ThinVec;
-
-use super::{AcceptMapping, AttributeParser};
-use crate::context::{FinalizeContext, Stage};
-use crate::session_diagnostics;
+use super::prelude::*;
+use crate::session_diagnostics::EmptyConfusables;
 
 #[derive(Default)]
 pub(crate) struct ConfusablesParser {
@@ -16,7 +10,7 @@ pub(crate) struct ConfusablesParser {
 impl<S: Stage> AttributeParser<S> for ConfusablesParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[(
         &[sym::rustc_confusables],
-        template!(List: r#""name1", "name2", ..."#),
+        template!(List: &[r#""name1", "name2", ..."#]),
         |this, cx, args| {
             let Some(list) = args.list() else {
                 cx.expected_list(cx.attr_span);
@@ -24,7 +18,7 @@ impl<S: Stage> AttributeParser<S> for ConfusablesParser {
             };
 
             if list.is_empty() {
-                cx.emit_err(session_diagnostics::EmptyConfusables { span: cx.attr_span });
+                cx.emit_err(EmptyConfusables { span: cx.attr_span });
             }
 
             for param in list.mixed() {
@@ -41,6 +35,8 @@ impl<S: Stage> AttributeParser<S> for ConfusablesParser {
             this.first_span.get_or_insert(cx.attr_span);
         },
     )];
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::Method(MethodKind::Inherent))]);
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         if self.confusables.is_empty() {
diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs
new file mode 100644
index 00000000000..0a340cd5e93
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs
@@ -0,0 +1,185 @@
+use std::num::IntErrorKind;
+
+use rustc_hir::limit::Limit;
+
+use super::prelude::*;
+use crate::session_diagnostics::LimitInvalid;
+
+impl<S: Stage> AcceptContext<'_, '_, S> {
+    fn parse_limit_int(&self, nv: &NameValueParser) -> Option<Limit> {
+        let Some(limit) = nv.value_as_str() else {
+            self.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
+            return None;
+        };
+
+        let error_str = match limit.as_str().parse() {
+            Ok(i) => return Some(Limit::new(i)),
+            Err(e) => match e.kind() {
+                IntErrorKind::PosOverflow => "`limit` is too large",
+                IntErrorKind::Empty => "`limit` must be a non-negative integer",
+                IntErrorKind::InvalidDigit => "not a valid integer",
+                IntErrorKind::NegOverflow => {
+                    panic!(
+                        "`limit` should never negatively overflow since we're parsing into a usize and we'd get Empty instead"
+                    )
+                }
+                IntErrorKind::Zero => {
+                    panic!("zero is a valid `limit` so should have returned Ok() when parsing")
+                }
+                kind => panic!("unimplemented IntErrorKind variant: {:?}", kind),
+            },
+        };
+
+        self.emit_err(LimitInvalid { span: self.attr_span, value_span: nv.value_span, error_str });
+
+        None
+    }
+}
+
+pub(crate) struct CrateNameParser;
+
+impl<S: Stage> SingleAttributeParser<S> for CrateNameParser {
+    const PATH: &[Symbol] = &[sym::crate_name];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let ArgParser::NameValue(n) = args else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+
+        let Some(name) = n.value_as_str() else {
+            cx.expected_string_literal(n.value_span, Some(n.value_as_lit()));
+            return None;
+        };
+
+        Some(AttributeKind::CrateName {
+            name,
+            name_span: n.value_span,
+            attr_span: cx.attr_span,
+            style: cx.attr_style,
+        })
+    }
+}
+
+pub(crate) struct RecursionLimitParser;
+
+impl<S: Stage> SingleAttributeParser<S> for RecursionLimitParser {
+    const PATH: &[Symbol] = &[sym::recursion_limit];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let ArgParser::NameValue(nv) = args else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+
+        Some(AttributeKind::RecursionLimit {
+            limit: cx.parse_limit_int(nv)?,
+            attr_span: cx.attr_span,
+            limit_span: nv.value_span,
+        })
+    }
+}
+
+pub(crate) struct MoveSizeLimitParser;
+
+impl<S: Stage> SingleAttributeParser<S> for MoveSizeLimitParser {
+    const PATH: &[Symbol] = &[sym::move_size_limit];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let ArgParser::NameValue(nv) = args else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+
+        Some(AttributeKind::MoveSizeLimit {
+            limit: cx.parse_limit_int(nv)?,
+            attr_span: cx.attr_span,
+            limit_span: nv.value_span,
+        })
+    }
+}
+
+pub(crate) struct TypeLengthLimitParser;
+
+impl<S: Stage> SingleAttributeParser<S> for TypeLengthLimitParser {
+    const PATH: &[Symbol] = &[sym::type_length_limit];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let ArgParser::NameValue(nv) = args else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+
+        Some(AttributeKind::TypeLengthLimit {
+            limit: cx.parse_limit_int(nv)?,
+            attr_span: cx.attr_span,
+            limit_span: nv.value_span,
+        })
+    }
+}
+
+pub(crate) struct PatternComplexityLimitParser;
+
+impl<S: Stage> SingleAttributeParser<S> for PatternComplexityLimitParser {
+    const PATH: &[Symbol] = &[sym::pattern_complexity_limit];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let ArgParser::NameValue(nv) = args else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+
+        Some(AttributeKind::PatternComplexityLimit {
+            limit: cx.parse_limit_int(nv)?,
+            attr_span: cx.attr_span,
+            limit_span: nv.value_span,
+        })
+    }
+}
+
+pub(crate) struct NoCoreParser;
+
+impl<S: Stage> NoArgsAttributeParser<S> for NoCoreParser {
+    const PATH: &[Symbol] = &[sym::no_core];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoCore;
+}
+
+pub(crate) struct NoStdParser;
+
+impl<S: Stage> NoArgsAttributeParser<S> for NoStdParser {
+    const PATH: &[Symbol] = &[sym::no_std];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoStd;
+}
+
+pub(crate) struct RustcCoherenceIsCoreParser;
+
+impl<S: Stage> NoArgsAttributeParser<S> for RustcCoherenceIsCoreParser {
+    const PATH: &[Symbol] = &[sym::rustc_coherence_is_core];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcCoherenceIsCore;
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
index 08cf1ab5d19..f96477e28cd 100644
--- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
@@ -1,12 +1,10 @@
-use rustc_attr_data_structures::{AttributeKind, DeprecatedSince, Deprecation};
-use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Span, Symbol, sym};
+use rustc_hir::attrs::{DeprecatedSince, Deprecation};
 
+use super::prelude::*;
 use super::util::parse_version;
-use super::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::{AcceptContext, Stage};
-use crate::parser::ArgParser;
-use crate::session_diagnostics;
+use crate::session_diagnostics::{
+    DeprecatedItemSuggestion, InvalidSince, MissingNote, MissingSince,
+};
 
 pub(crate) struct DeprecationParser;
 
@@ -38,9 +36,35 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
     const PATH: &[Symbol] = &[sym::deprecated];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+        Allow(Target::Fn),
+        Allow(Target::Mod),
+        Allow(Target::Struct),
+        Allow(Target::Enum),
+        Allow(Target::Union),
+        Allow(Target::Const),
+        Allow(Target::Static),
+        Allow(Target::MacroDef),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::TyAlias),
+        Allow(Target::Use),
+        Allow(Target::ForeignFn),
+        Allow(Target::ForeignStatic),
+        Allow(Target::ForeignTy),
+        Allow(Target::Field),
+        Allow(Target::Trait),
+        Allow(Target::AssocTy),
+        Allow(Target::AssocConst),
+        Allow(Target::Variant),
+        Allow(Target::Impl { of_trait: false }),
+        Allow(Target::Crate),
+        Error(Target::WherePredicate),
+    ]);
     const TEMPLATE: AttributeTemplate = template!(
         Word,
-        List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#,
+        List: &[r#"since = "version""#, r#"note = "reason""#, r#"since = "version", note = "reason""#],
         NameValueStr: "reason"
     );
 
@@ -75,7 +99,7 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
                         }
                         Some(name @ sym::suggestion) => {
                             if !features.deprecated_suggestion() {
-                                cx.emit_err(session_diagnostics::DeprecatedItemSuggestion {
+                                cx.emit_err(DeprecatedItemSuggestion {
                                     span: param.span(),
                                     is_nightly: cx.sess().is_nightly_build(),
                                     details: (),
@@ -117,18 +141,18 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
             } else if let Some(version) = parse_version(since) {
                 DeprecatedSince::RustcVersion(version)
             } else {
-                cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
+                cx.emit_err(InvalidSince { span: cx.attr_span });
                 DeprecatedSince::Err
             }
         } else if is_rustc {
-            cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
+            cx.emit_err(MissingSince { span: cx.attr_span });
             DeprecatedSince::Err
         } else {
             DeprecatedSince::Unspecified
         };
 
         if is_rustc && note.is_none() {
-            cx.emit_err(session_diagnostics::MissingNote { span: cx.attr_span });
+            cx.emit_err(MissingNote { span: cx.attr_span });
             return None;
         }
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/dummy.rs b/compiler/rustc_attr_parsing/src/attributes/dummy.rs
index e5e1c3bb6b6..7293cee842c 100644
--- a/compiler/rustc_attr_parsing/src/attributes/dummy.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs
@@ -1,16 +1,18 @@
-use rustc_attr_data_structures::AttributeKind;
 use rustc_feature::{AttributeTemplate, template};
+use rustc_hir::attrs::AttributeKind;
 use rustc_span::{Symbol, sym};
 
 use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
 use crate::context::{AcceptContext, Stage};
 use crate::parser::ArgParser;
+use crate::target_checking::{ALL_TARGETS, AllowedTargets};
 
 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
     const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really
 
     fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser<'_>) -> Option<AttributeKind> {
diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs
index fe812175218..a73430c9d00 100644
--- a/compiler/rustc_attr_parsing/src/attributes/inline.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs
@@ -2,15 +2,9 @@
 //                      note: need to model better how duplicate attr errors work when not using
 //                      SingleAttributeParser which is what we have two of here.
 
-use rustc_attr_data_structures::lints::AttributeLintKind;
-use rustc_attr_data_structures::{AttributeKind, InlineAttr};
-use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Symbol, sym};
+use rustc_hir::attrs::{AttributeKind, InlineAttr};
 
-use super::{AcceptContext, AttributeOrder, OnDuplicate};
-use crate::attributes::SingleAttributeParser;
-use crate::context::Stage;
-use crate::parser::ArgParser;
+use super::prelude::*;
 
 pub(crate) struct InlineParser;
 
@@ -18,7 +12,26 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
     const PATH: &'static [Symbol] = &[sym::inline];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(Word, List: "always|never");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Closure),
+        Allow(Target::Delegation { mac: false }),
+        Warn(Target::Method(MethodKind::Trait { body: false })),
+        Warn(Target::ForeignFn),
+        Warn(Target::Field),
+        Warn(Target::MacroDef),
+        Warn(Target::Arm),
+        Warn(Target::AssocConst),
+        Warn(Target::MacroCall),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(
+        Word,
+        List: &["always", "never"],
+        "https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         match args {
@@ -37,14 +50,14 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
                         Some(AttributeKind::Inline(InlineAttr::Never, cx.attr_span))
                     }
                     _ => {
-                        cx.expected_specific_argument(l.span(), vec!["always", "never"]);
+                        cx.expected_specific_argument(l.span(), &[sym::always, sym::never]);
                         return None;
                     }
                 }
             }
             ArgParser::NameValue(_) => {
-                let suggestions =
-                    <Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "inline");
+                let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
+                    .suggestions(cx.attr_style, "inline");
                 let span = cx.attr_span;
                 cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span);
                 return None;
@@ -59,7 +72,8 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
     const PATH: &'static [Symbol] = &[sym::rustc_force_inline];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(Word, List: "reason", NameValueStr: "reason");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]);
+    const TEMPLATE: AttributeTemplate = template!(Word, List: &["reason"], NameValueStr: "reason");
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let reason = match args {
diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
index 960cebd8925..d4942e56f42 100644
--- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
@@ -1,14 +1,21 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_attr_data_structures::AttributeKind::{LinkName, LinkOrdinal, LinkSection};
-use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Span, Symbol, sym};
+use rustc_feature::Features;
+use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection};
+use rustc_hir::attrs::*;
+use rustc_session::Session;
+use rustc_session::parse::feature_err;
+use rustc_span::kw;
+use rustc_target::spec::BinaryFormat;
 
-use crate::attributes::{
-    AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
+use super::prelude::*;
+use super::util::parse_single_integer;
+use crate::attributes::cfg::parse_cfg_entry;
+use crate::fluent_generated;
+use crate::session_diagnostics::{
+    AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ImportNameTypeRaw, ImportNameTypeX86,
+    IncompatibleWasmLink, InvalidLinkModifier, LinkFrameworkApple, LinkOrdinalOutOfRange,
+    LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows,
+    WholeArchiveNeedsStatic,
 };
-use crate::context::{AcceptContext, Stage, parse_single_integer};
-use crate::parser::ArgParser;
-use crate::session_diagnostics::{LinkOrdinalOutOfRange, NullOnLinkSection};
 
 pub(crate) struct LinkNameParser;
 
@@ -16,7 +23,14 @@ impl<S: Stage> SingleAttributeParser<S> for LinkNameParser {
     const PATH: &[Symbol] = &[sym::link_name];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+        Allow(Target::ForeignFn),
+        Allow(Target::ForeignStatic),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(
+        NameValueStr: "name",
+        "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let Some(nv) = args.name_value() else {
@@ -32,13 +46,421 @@ impl<S: Stage> SingleAttributeParser<S> for LinkNameParser {
     }
 }
 
+pub(crate) struct LinkParser;
+
+impl<S: Stage> CombineAttributeParser<S> for LinkParser {
+    type Item = LinkEntry;
+    const PATH: &[Symbol] = &[sym::link];
+    const CONVERT: ConvertFn<Self::Item> = AttributeKind::Link;
+    const TEMPLATE: AttributeTemplate = template!(List: &[
+            r#"name = "...""#,
+            r#"name = "...", kind = "dylib|static|...""#,
+            r#"name = "...", wasm_import_module = "...""#,
+            r#"name = "...", import_name_type = "decorated|noprefix|undecorated""#,
+            r#"name = "...", kind = "dylib|static|...", wasm_import_module = "...", import_name_type = "decorated|noprefix|undecorated""#,
+        ], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
+
+    fn extend<'c>(
+        cx: &'c mut AcceptContext<'_, '_, S>,
+        args: &'c ArgParser<'_>,
+    ) -> impl IntoIterator<Item = Self::Item> + 'c {
+        let mut result = None;
+        let Some(items) = args.list() else {
+            cx.expected_list(cx.attr_span);
+            return result;
+        };
+
+        let sess = cx.sess();
+        let features = cx.features();
+
+        let mut name = None;
+        let mut kind = None;
+        let mut modifiers = None;
+        let mut cfg = None;
+        let mut wasm_import_module = None;
+        let mut import_name_type = None;
+        for item in items.mixed() {
+            let Some(item) = item.meta_item() else {
+                cx.unexpected_literal(item.span());
+                continue;
+            };
+
+            let cont = match item.path().word().map(|ident| ident.name) {
+                Some(sym::name) => Self::parse_link_name(item, &mut name, cx),
+                Some(sym::kind) => Self::parse_link_kind(item, &mut kind, cx, sess, features),
+                Some(sym::modifiers) => Self::parse_link_modifiers(item, &mut modifiers, cx),
+                Some(sym::cfg) => Self::parse_link_cfg(item, &mut cfg, cx, sess, features),
+                Some(sym::wasm_import_module) => {
+                    Self::parse_link_wasm_import_module(item, &mut wasm_import_module, cx)
+                }
+                Some(sym::import_name_type) => {
+                    Self::parse_link_import_name_type(item, &mut import_name_type, cx)
+                }
+                _ => {
+                    cx.expected_specific_argument_strings(
+                        item.span(),
+                        &[
+                            sym::name,
+                            sym::kind,
+                            sym::modifiers,
+                            sym::cfg,
+                            sym::wasm_import_module,
+                            sym::import_name_type,
+                        ],
+                    );
+                    true
+                }
+            };
+            if !cont {
+                return result;
+            }
+        }
+
+        // Do this outside the above loop so we don't depend on modifiers coming after kinds
+        let mut verbatim = None;
+        if let Some((modifiers, span)) = modifiers {
+            for modifier in modifiers.as_str().split(',') {
+                let (modifier, value): (Symbol, bool) = match modifier.strip_prefix(&['+', '-']) {
+                    Some(m) => (Symbol::intern(m), modifier.starts_with('+')),
+                    None => {
+                        cx.emit_err(InvalidLinkModifier { span });
+                        continue;
+                    }
+                };
+
+                macro report_unstable_modifier($feature: ident) {
+                    if !features.$feature() {
+                        // FIXME: make this translatable
+                        #[expect(rustc::untranslatable_diagnostic)]
+                        feature_err(
+                            sess,
+                            sym::$feature,
+                            span,
+                            format!("linking modifier `{modifier}` is unstable"),
+                        )
+                        .emit();
+                    }
+                }
+                let assign_modifier = |dst: &mut Option<bool>| {
+                    if dst.is_some() {
+                        cx.emit_err(MultipleModifiers { span, modifier });
+                    } else {
+                        *dst = Some(value);
+                    }
+                };
+                match (modifier, &mut kind) {
+                    (sym::bundle, Some(NativeLibKind::Static { bundle, .. })) => {
+                        assign_modifier(bundle)
+                    }
+                    (sym::bundle, _) => {
+                        cx.emit_err(BundleNeedsStatic { span });
+                    }
+
+                    (sym::verbatim, _) => assign_modifier(&mut verbatim),
+
+                    (
+                        sym::whole_dash_archive,
+                        Some(NativeLibKind::Static { whole_archive, .. }),
+                    ) => assign_modifier(whole_archive),
+                    (sym::whole_dash_archive, _) => {
+                        cx.emit_err(WholeArchiveNeedsStatic { span });
+                    }
+
+                    (sym::as_dash_needed, Some(NativeLibKind::Dylib { as_needed }))
+                    | (sym::as_dash_needed, Some(NativeLibKind::Framework { as_needed })) => {
+                        report_unstable_modifier!(native_link_modifiers_as_needed);
+                        assign_modifier(as_needed)
+                    }
+                    (sym::as_dash_needed, _) => {
+                        cx.emit_err(AsNeededCompatibility { span });
+                    }
+
+                    _ => {
+                        cx.expected_specific_argument_strings(
+                            span,
+                            &[
+                                sym::bundle,
+                                sym::verbatim,
+                                sym::whole_dash_archive,
+                                sym::as_dash_needed,
+                            ],
+                        );
+                    }
+                }
+            }
+        }
+
+        if let Some((_, span)) = wasm_import_module {
+            if name.is_some() || kind.is_some() || modifiers.is_some() || cfg.is_some() {
+                cx.emit_err(IncompatibleWasmLink { span });
+            }
+        }
+
+        if wasm_import_module.is_some() {
+            (name, kind) = (wasm_import_module, Some(NativeLibKind::WasmImportModule));
+        }
+        let Some((name, name_span)) = name else {
+            cx.emit_err(LinkRequiresName { span: cx.attr_span });
+            return result;
+        };
+
+        // Do this outside of the loop so that `import_name_type` can be specified before `kind`.
+        if let Some((_, span)) = import_name_type {
+            if kind != Some(NativeLibKind::RawDylib) {
+                cx.emit_err(ImportNameTypeRaw { span });
+            }
+        }
+
+        if let Some(NativeLibKind::RawDylib) = kind
+            && name.as_str().contains('\0')
+        {
+            cx.emit_err(RawDylibNoNul { span: name_span });
+        }
+
+        result = Some(LinkEntry {
+            span: cx.attr_span,
+            kind: kind.unwrap_or(NativeLibKind::Unspecified),
+            name,
+            cfg,
+            verbatim,
+            import_name_type,
+        });
+        result
+    }
+}
+
+impl LinkParser {
+    fn parse_link_name<S: Stage>(
+        item: &MetaItemParser<'_>,
+        name: &mut Option<(Symbol, Span)>,
+        cx: &mut AcceptContext<'_, '_, S>,
+    ) -> bool {
+        if name.is_some() {
+            cx.duplicate_key(item.span(), sym::name);
+            return true;
+        }
+        let Some(nv) = item.args().name_value() else {
+            cx.expected_name_value(item.span(), Some(sym::name));
+            return false;
+        };
+        let Some(link_name) = nv.value_as_str() else {
+            cx.expected_name_value(item.span(), Some(sym::name));
+            return false;
+        };
+
+        if link_name.is_empty() {
+            cx.emit_err(EmptyLinkName { span: nv.value_span });
+        }
+        *name = Some((link_name, nv.value_span));
+        true
+    }
+
+    fn parse_link_kind<S: Stage>(
+        item: &MetaItemParser<'_>,
+        kind: &mut Option<NativeLibKind>,
+        cx: &mut AcceptContext<'_, '_, S>,
+        sess: &Session,
+        features: &Features,
+    ) -> bool {
+        if kind.is_some() {
+            cx.duplicate_key(item.span(), sym::kind);
+            return true;
+        }
+        let Some(nv) = item.args().name_value() else {
+            cx.expected_name_value(item.span(), Some(sym::kind));
+            return true;
+        };
+        let Some(link_kind) = nv.value_as_str() else {
+            cx.expected_name_value(item.span(), Some(sym::kind));
+            return true;
+        };
+
+        let link_kind = match link_kind {
+            kw::Static => NativeLibKind::Static { bundle: None, whole_archive: None },
+            sym::dylib => NativeLibKind::Dylib { as_needed: None },
+            sym::framework => {
+                if !sess.target.is_like_darwin {
+                    cx.emit_err(LinkFrameworkApple { span: nv.value_span });
+                }
+                NativeLibKind::Framework { as_needed: None }
+            }
+            sym::raw_dash_dylib => {
+                if sess.target.is_like_windows {
+                    // raw-dylib is stable and working on Windows
+                } else if sess.target.binary_format == BinaryFormat::Elf && features.raw_dylib_elf()
+                {
+                    // raw-dylib is unstable on ELF, but the user opted in
+                } else if sess.target.binary_format == BinaryFormat::Elf && sess.is_nightly_build()
+                {
+                    feature_err(
+                        sess,
+                        sym::raw_dylib_elf,
+                        nv.value_span,
+                        fluent_generated::attr_parsing_raw_dylib_elf_unstable,
+                    )
+                    .emit();
+                } else {
+                    cx.emit_err(RawDylibOnlyWindows { span: nv.value_span });
+                }
+
+                NativeLibKind::RawDylib
+            }
+            sym::link_dash_arg => {
+                if !features.link_arg_attribute() {
+                    feature_err(
+                        sess,
+                        sym::link_arg_attribute,
+                        nv.value_span,
+                        fluent_generated::attr_parsing_link_arg_unstable,
+                    )
+                    .emit();
+                }
+                NativeLibKind::LinkArg
+            }
+            _kind => {
+                cx.expected_specific_argument_strings(
+                    nv.value_span,
+                    &[
+                        kw::Static,
+                        sym::dylib,
+                        sym::framework,
+                        sym::raw_dash_dylib,
+                        sym::link_dash_arg,
+                    ],
+                );
+                return true;
+            }
+        };
+        *kind = Some(link_kind);
+        true
+    }
+
+    fn parse_link_modifiers<S: Stage>(
+        item: &MetaItemParser<'_>,
+        modifiers: &mut Option<(Symbol, Span)>,
+        cx: &mut AcceptContext<'_, '_, S>,
+    ) -> bool {
+        if modifiers.is_some() {
+            cx.duplicate_key(item.span(), sym::modifiers);
+            return true;
+        }
+        let Some(nv) = item.args().name_value() else {
+            cx.expected_name_value(item.span(), Some(sym::modifiers));
+            return true;
+        };
+        let Some(link_modifiers) = nv.value_as_str() else {
+            cx.expected_name_value(item.span(), Some(sym::modifiers));
+            return true;
+        };
+        *modifiers = Some((link_modifiers, nv.value_span));
+        true
+    }
+
+    fn parse_link_cfg<S: Stage>(
+        item: &MetaItemParser<'_>,
+        cfg: &mut Option<CfgEntry>,
+        cx: &mut AcceptContext<'_, '_, S>,
+        sess: &Session,
+        features: &Features,
+    ) -> bool {
+        if cfg.is_some() {
+            cx.duplicate_key(item.span(), sym::cfg);
+            return true;
+        }
+        let Some(link_cfg) = item.args().list() else {
+            cx.expected_list(item.span());
+            return true;
+        };
+        let Some(link_cfg) = link_cfg.single() else {
+            cx.expected_single_argument(item.span());
+            return true;
+        };
+        if !features.link_cfg() {
+            feature_err(
+                sess,
+                sym::link_cfg,
+                item.span(),
+                fluent_generated::attr_parsing_link_cfg_unstable,
+            )
+            .emit();
+        }
+        *cfg = parse_cfg_entry(cx, link_cfg);
+        true
+    }
+
+    fn parse_link_wasm_import_module<S: Stage>(
+        item: &MetaItemParser<'_>,
+        wasm_import_module: &mut Option<(Symbol, Span)>,
+        cx: &mut AcceptContext<'_, '_, S>,
+    ) -> bool {
+        if wasm_import_module.is_some() {
+            cx.duplicate_key(item.span(), sym::wasm_import_module);
+            return true;
+        }
+        let Some(nv) = item.args().name_value() else {
+            cx.expected_name_value(item.span(), Some(sym::wasm_import_module));
+            return true;
+        };
+        let Some(link_wasm_import_module) = nv.value_as_str() else {
+            cx.expected_name_value(item.span(), Some(sym::wasm_import_module));
+            return true;
+        };
+        *wasm_import_module = Some((link_wasm_import_module, item.span()));
+        true
+    }
+
+    fn parse_link_import_name_type<S: Stage>(
+        item: &MetaItemParser<'_>,
+        import_name_type: &mut Option<(PeImportNameType, Span)>,
+        cx: &mut AcceptContext<'_, '_, S>,
+    ) -> bool {
+        if import_name_type.is_some() {
+            cx.duplicate_key(item.span(), sym::import_name_type);
+            return true;
+        }
+        let Some(nv) = item.args().name_value() else {
+            cx.expected_name_value(item.span(), Some(sym::import_name_type));
+            return true;
+        };
+        let Some(link_import_name_type) = nv.value_as_str() else {
+            cx.expected_name_value(item.span(), Some(sym::import_name_type));
+            return true;
+        };
+        if cx.sess().target.arch != "x86" {
+            cx.emit_err(ImportNameTypeX86 { span: item.span() });
+            return true;
+        }
+
+        let link_import_name_type = match link_import_name_type {
+            sym::decorated => PeImportNameType::Decorated,
+            sym::noprefix => PeImportNameType::NoPrefix,
+            sym::undecorated => PeImportNameType::Undecorated,
+            _ => {
+                cx.expected_specific_argument_strings(
+                    item.span(),
+                    &[sym::decorated, sym::noprefix, sym::undecorated],
+                );
+                return true;
+            }
+        };
+        *import_name_type = Some((link_import_name_type, item.span()));
+        true
+    }
+}
+
 pub(crate) struct LinkSectionParser;
 
 impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
     const PATH: &[Symbol] = &[sym::link_section];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowListWarnRest(&[Allow(Target::Static), Allow(Target::Fn)]);
+    const TEMPLATE: AttributeTemplate = template!(
+        NameValueStr: "name",
+        "https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let Some(nv) = args.name_value() else {
@@ -64,6 +486,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
     const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ExportStable;
 }
 
@@ -71,6 +494,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiConst;
 }
 
@@ -78,6 +502,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure;
 }
 
@@ -85,6 +510,12 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::ForeignFn),
+        Allow(Target::Static),
+        Allow(Target::ForeignStatic),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::StdInternalSymbol;
 }
 
@@ -94,7 +525,15 @@ 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");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::ForeignFn),
+        Allow(Target::ForeignStatic),
+        Warn(Target::MacroCall),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(
+        List: &["ordinal"],
+        "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let ordinal = parse_single_integer(cx, args)?;
@@ -120,3 +559,87 @@ impl<S: Stage> SingleAttributeParser<S> for LinkOrdinalParser {
         Some(LinkOrdinal { ordinal, span: cx.attr_span })
     }
 }
+
+pub(crate) struct LinkageParser;
+
+impl<S: Stage> SingleAttributeParser<S> for LinkageParser {
+    const PATH: &[Symbol] = &[sym::linkage];
+
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Static),
+        Allow(Target::ForeignStatic),
+        Allow(Target::ForeignFn),
+    ]);
+
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: [
+        "available_externally",
+        "common",
+        "extern_weak",
+        "external",
+        "internal",
+        "linkonce",
+        "linkonce_odr",
+        "weak",
+        "weak_odr",
+    ]);
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(name_value) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, Some(sym::linkage));
+            return None;
+        };
+
+        let Some(value) = name_value.value_as_str() else {
+            cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit()));
+            return None;
+        };
+
+        // Use the names from src/llvm/docs/LangRef.rst here. Most types are only
+        // applicable to variable declarations and may not really make sense for
+        // Rust code in the first place but allow them anyway and trust that the
+        // user knows what they're doing. Who knows, unanticipated use cases may pop
+        // up in the future.
+        //
+        // ghost, dllimport, dllexport and linkonce_odr_autohide are not supported
+        // and don't have to be, LLVM treats them as no-ops.
+        let linkage = match value {
+            sym::available_externally => Linkage::AvailableExternally,
+            sym::common => Linkage::Common,
+            sym::extern_weak => Linkage::ExternalWeak,
+            sym::external => Linkage::External,
+            sym::internal => Linkage::Internal,
+            sym::linkonce => Linkage::LinkOnceAny,
+            sym::linkonce_odr => Linkage::LinkOnceODR,
+            sym::weak => Linkage::WeakAny,
+            sym::weak_odr => Linkage::WeakODR,
+
+            _ => {
+                cx.expected_specific_argument(
+                    name_value.value_span,
+                    &[
+                        sym::available_externally,
+                        sym::common,
+                        sym::extern_weak,
+                        sym::external,
+                        sym::internal,
+                        sym::linkonce,
+                        sym::linkonce_odr,
+                        sym::weak,
+                        sym::weak_odr,
+                    ],
+                );
+                return None;
+            }
+        };
+
+        Some(AttributeKind::Linkage(linkage, 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 0eceff53e8b..63b0809d0d8 100644
--- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
@@ -1,13 +1,16 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_span::{Span, Symbol, sym};
-
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::Stage;
+use super::prelude::*;
 
 pub(crate) struct AsPtrParser;
 impl<S: Stage> NoArgsAttributeParser<S> for AsPtrParser {
     const PATH: &[Symbol] = &[sym::rustc_as_ptr];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::AsPtr;
 }
 
@@ -15,6 +18,11 @@ pub(crate) struct PubTransparentParser;
 impl<S: Stage> NoArgsAttributeParser<S> for PubTransparentParser {
     const PATH: &[Symbol] = &[sym::rustc_pub_transparent];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Struct),
+        Allow(Target::Enum),
+        Allow(Target::Union),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::PubTransparent;
 }
 
@@ -22,6 +30,11 @@ pub(crate) struct PassByValueParser;
 impl<S: Stage> NoArgsAttributeParser<S> for PassByValueParser {
     const PATH: &[Symbol] = &[sym::rustc_pass_by_value];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Struct),
+        Allow(Target::Enum),
+        Allow(Target::TyAlias),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::PassByValue;
 }
 
@@ -29,5 +42,10 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+        Allow(Target::Impl { of_trait: true }),
+        Error(Target::Crate),
+        Error(Target::WherePredicate),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::AutomaticallyDerived;
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs
index 80808b90dc6..528090b8673 100644
--- a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs
@@ -1,13 +1,10 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_span::{Span, Symbol, sym};
-
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::Stage;
+use super::prelude::*;
 
 pub(crate) struct LoopMatchParser;
 impl<S: Stage> NoArgsAttributeParser<S> for LoopMatchParser {
     const PATH: &[Symbol] = &[sym::loop_match];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Expression)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::LoopMatch;
 }
 
@@ -15,5 +12,6 @@ pub(crate) struct ConstContinueParser;
 impl<S: Stage> NoArgsAttributeParser<S> for ConstContinueParser {
     const PATH: &[Symbol] = &[sym::const_continue];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Expression)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::ConstContinue;
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs
new file mode 100644
index 00000000000..180130c7be4
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs
@@ -0,0 +1,135 @@
+use rustc_errors::DiagArgValue;
+use rustc_hir::attrs::MacroUseArgs;
+
+use super::prelude::*;
+use crate::session_diagnostics::IllFormedAttributeInputLint;
+
+pub(crate) struct MacroEscapeParser;
+impl<S: Stage> NoArgsAttributeParser<S> for MacroEscapeParser {
+    const PATH: &[Symbol] = &[sym::macro_escape];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = MACRO_USE_ALLOWED_TARGETS;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::MacroEscape;
+}
+
+/// `#[macro_use]` attributes can either:
+/// - Use all macros from a crate, if provided without arguments
+/// - Use specific macros from a crate, if provided with arguments `#[macro_use(macro1, macro2)]`
+/// A warning should be provided if an use all is combined with specific uses, or if multiple use-alls are used.
+#[derive(Default)]
+pub(crate) struct MacroUseParser {
+    state: MacroUseArgs,
+
+    /// Spans of all `#[macro_use]` arguments with arguments, used for linting
+    uses_attr_spans: ThinVec<Span>,
+    /// If `state` is `UseSpecific`, stores the span of the first `#[macro_use]` argument, used as the span for this attribute
+    /// If `state` is `UseAll`, stores the span of the first `#[macro_use]` arguments without arguments
+    first_span: Option<Span>,
+}
+
+const MACRO_USE_TEMPLATE: AttributeTemplate = template!(
+    Word, List: &["name1, name2, ..."],
+    "https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute"
+);
+const MACRO_USE_ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+    Allow(Target::Mod),
+    Allow(Target::ExternCrate),
+    Allow(Target::Crate),
+    Error(Target::WherePredicate),
+]);
+
+impl<S: Stage> AttributeParser<S> for MacroUseParser {
+    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
+        &[sym::macro_use],
+        MACRO_USE_TEMPLATE,
+        |group: &mut Self, cx: &mut AcceptContext<'_, '_, S>, args| {
+            let span = cx.attr_span;
+            group.first_span.get_or_insert(span);
+            match args {
+                ArgParser::NoArgs => {
+                    match group.state {
+                        MacroUseArgs::UseAll => {
+                            let first_span = group.first_span.expect(
+                                "State is UseAll is some so this is not the first attribute",
+                            );
+                            // Since there is a `#[macro_use]` import already, give a warning
+                            cx.warn_unused_duplicate(first_span, span);
+                        }
+                        MacroUseArgs::UseSpecific(_) => {
+                            group.state = MacroUseArgs::UseAll;
+                            group.first_span = Some(span);
+                            // If there is a `#[macro_use]` attribute, warn on all `#[macro_use(...)]` attributes since everything is already imported
+                            for specific_use in group.uses_attr_spans.drain(..) {
+                                cx.warn_unused_duplicate(span, specific_use);
+                            }
+                        }
+                    }
+                }
+                ArgParser::List(list) => {
+                    if list.is_empty() {
+                        cx.warn_empty_attribute(list.span);
+                        return;
+                    }
+
+                    match &mut group.state {
+                        MacroUseArgs::UseAll => {
+                            let first_span = group.first_span.expect(
+                                "State is UseAll is some so this is not the first attribute",
+                            );
+                            cx.warn_unused_duplicate(first_span, span);
+                        }
+                        MacroUseArgs::UseSpecific(arguments) => {
+                            // Store here so if we encounter a `UseAll` later we can still lint this attribute
+                            group.uses_attr_spans.push(cx.attr_span);
+
+                            for item in list.mixed() {
+                                let Some(item) = item.meta_item() else {
+                                    cx.expected_identifier(item.span());
+                                    continue;
+                                };
+                                if let Err(err_span) = item.args().no_args() {
+                                    cx.expected_no_args(err_span);
+                                    continue;
+                                }
+                                let Some(item) = item.path().word() else {
+                                    cx.expected_identifier(item.span());
+                                    continue;
+                                };
+                                arguments.push(item);
+                            }
+                        }
+                    }
+                }
+                ArgParser::NameValue(_) => {
+                    let suggestions = MACRO_USE_TEMPLATE.suggestions(cx.attr_style, sym::macro_use);
+                    cx.emit_err(IllFormedAttributeInputLint {
+                        num_suggestions: suggestions.len(),
+                        suggestions: DiagArgValue::StrListSepByAnd(
+                            suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
+                        ),
+                        span,
+                    });
+                }
+            }
+        },
+    )];
+    const ALLOWED_TARGETS: AllowedTargets = MACRO_USE_ALLOWED_TARGETS;
+
+    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
+        Some(AttributeKind::MacroUse { span: self.first_span?, arguments: self.state })
+    }
+}
+
+pub(crate) struct AllowInternalUnsafeParser;
+
+impl<S: Stage> NoArgsAttributeParser<S> for AllowInternalUnsafeParser {
+    const PATH: &[Symbol] = &[sym::allow_internal_unsafe];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::MacroDef),
+        Warn(Target::Field),
+        Warn(Target::Arm),
+    ]);
+    const CREATE: fn(Span) -> AttributeKind = |span| AttributeKind::AllowInternalUnsafe(span);
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index 200f1381960..4ed13d239b9 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -7,39 +7,48 @@
 //! Specifically, you might not care about managing the state of your [`AttributeParser`]
 //! state machine yourself. In this case you can choose to implement:
 //!
-//! - [`SingleAttributeParser`]: makes it easy to implement an attribute which should error if it
+//! - [`SingleAttributeParser`](crate::attributes::SingleAttributeParser): makes it easy to implement an attribute which should error if it
 //! appears more than once in a list of attributes
-//! - [`CombineAttributeParser`]: makes it easy to implement an attribute which should combine the
+//! - [`CombineAttributeParser`](crate::attributes::CombineAttributeParser): makes it easy to implement an attribute which should combine the
 //! contents of attributes, if an attribute appear multiple times in a list
 //!
 //! Attributes should be added to `crate::context::ATTRIBUTE_PARSERS` to be parsed.
 
 use std::marker::PhantomData;
 
-use rustc_attr_data_structures::AttributeKind;
 use rustc_feature::{AttributeTemplate, template};
+use rustc_hir::attrs::AttributeKind;
 use rustc_span::{Span, Symbol};
 use thin_vec::ThinVec;
 
 use crate::context::{AcceptContext, FinalizeContext, Stage};
 use crate::parser::ArgParser;
 use crate::session_diagnostics::UnusedMultiple;
+use crate::target_checking::AllowedTargets;
+
+/// All the parsers require roughly the same imports, so this prelude has most of the often-needed ones.
+mod prelude;
 
 pub(crate) mod allow_unstable;
+pub(crate) mod body;
 pub(crate) mod cfg;
 pub(crate) mod cfg_old;
 pub(crate) mod codegen_attrs;
 pub(crate) mod confusables;
+pub(crate) mod crate_level;
 pub(crate) mod deprecation;
 pub(crate) mod dummy;
 pub(crate) mod inline;
 pub(crate) mod link_attrs;
 pub(crate) mod lint_helpers;
 pub(crate) mod loop_match;
+pub(crate) mod macro_attrs;
 pub(crate) mod must_use;
 pub(crate) mod no_implicit_prelude;
 pub(crate) mod non_exhaustive;
 pub(crate) mod path;
+pub(crate) mod proc_macro_attrs;
+pub(crate) mod prototype;
 pub(crate) mod repr;
 pub(crate) mod rustc_internal;
 pub(crate) mod semantics;
@@ -76,6 +85,7 @@ pub(crate) trait AttributeParser<S: Stage>: Default + 'static {
     ///
     /// If an attribute has this symbol, the `accept` function will be called on it.
     const ATTRIBUTES: AcceptMapping<Self, S>;
+    const ALLOWED_TARGETS: AllowedTargets;
 
     /// The parser has gotten a chance to accept the attributes on an item,
     /// here it can produce an attribute.
@@ -113,6 +123,8 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
     /// and this specified whether to, for example, warn or error on the other one.
     const ON_DUPLICATE: OnDuplicate<S>;
 
+    const ALLOWED_TARGETS: AllowedTargets;
+
     /// The template this attribute parser should implement. Used for diagnostics.
     const TEMPLATE: AttributeTemplate;
 
@@ -160,6 +172,7 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
             }
         },
     )];
+    const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         Some(self.1?.0)
@@ -244,6 +257,7 @@ pub(crate) enum AttributeOrder {
 pub(crate) trait NoArgsAttributeParser<S: Stage>: 'static {
     const PATH: &[Symbol];
     const ON_DUPLICATE: OnDuplicate<S>;
+    const ALLOWED_TARGETS: AllowedTargets;
 
     /// Create the [`AttributeKind`] given attribute's [`Span`].
     const CREATE: fn(Span) -> AttributeKind;
@@ -261,6 +275,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for Without
     const PATH: &[Symbol] = T::PATH;
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = T::ON_DUPLICATE;
+    const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
     const TEMPLATE: AttributeTemplate = template!(Word);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
@@ -290,6 +305,8 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
     ///  where `x` is a vec of these individual reprs.
     const CONVERT: ConvertFn<Self::Item>;
 
+    const ALLOWED_TARGETS: AllowedTargets;
+
     /// The template this attribute parser should implement. Used for diagnostics.
     const TEMPLATE: AttributeTemplate;
 
@@ -321,15 +338,13 @@ impl<T: CombineAttributeParser<S>, S: Stage> Default for Combine<T, S> {
 }
 
 impl<T: CombineAttributeParser<S>, S: Stage> AttributeParser<S> for Combine<T, S> {
-    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
-        T::PATH,
-        <T as CombineAttributeParser<S>>::TEMPLATE,
-        |group: &mut Combine<T, S>, cx, args| {
+    const ATTRIBUTES: AcceptMapping<Self, S> =
+        &[(T::PATH, T::TEMPLATE, |group: &mut Combine<T, S>, cx, args| {
             // Keep track of the span of the first attribute, for diagnostics
             group.first_span.get_or_insert(cx.attr_span);
             group.items.extend(T::extend(cx, args))
-        },
-    )];
+        })];
+    const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         if let Some(first_span) = self.first_span {
diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs
index e0a3e675509..e6a5141d783 100644
--- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs
@@ -1,12 +1,7 @@
-use rustc_attr_data_structures::AttributeKind;
 use rustc_errors::DiagArgValue;
-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;
-use crate::session_diagnostics;
+use super::prelude::*;
+use crate::session_diagnostics::IllFormedAttributeInputLint;
 
 pub(crate) struct MustUseParser;
 
@@ -14,7 +9,25 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
     const PATH: &[Symbol] = &[sym::must_use];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(Word, NameValueStr: "reason");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
+        Allow(Target::Fn),
+        Allow(Target::Enum),
+        Allow(Target::Struct),
+        Allow(Target::Union),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::ForeignFn),
+        // `impl Trait` in return position can trip
+        // `unused_must_use` if `Trait` is marked as
+        // `#[must_use]`
+        Allow(Target::Trait),
+        Error(Target::WherePredicate),
+    ]);
+    const TEMPLATE: AttributeTemplate = template!(
+        Word, NameValueStr: "reason",
+        "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         Some(AttributeKind::MustUse {
@@ -32,9 +45,9 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
                     Some(value_str)
                 }
                 ArgParser::List(_) => {
-                    let suggestions =
-                        <Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "must_use");
-                    cx.emit_err(session_diagnostics::MustUseIllFormedAttributeInput {
+                    let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
+                        .suggestions(cx.attr_style, "must_use");
+                    cx.emit_err(IllFormedAttributeInputLint {
                         num_suggestions: suggestions.len(),
                         suggestions: DiagArgValue::StrListSepByAnd(
                             suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
diff --git a/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs b/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs
index 47cc925f7f6..40073ea0f46 100644
--- a/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs
@@ -1,13 +1,11 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_span::{Span, sym};
-
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::Stage;
+use super::prelude::*;
 
 pub(crate) struct NoImplicitPreludeParser;
 
 impl<S: Stage> NoArgsAttributeParser<S> for NoImplicitPreludeParser {
     const PATH: &[rustc_span::Symbol] = &[sym::no_implicit_prelude];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowListWarnRest(&[Allow(Target::Mod), Allow(Target::Crate)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoImplicitPrelude;
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs
index 94f6a65c74e..fc41c073fd2 100644
--- a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs
@@ -1,13 +1,25 @@
-use rustc_attr_data_structures::AttributeKind;
+use rustc_hir::Target;
+use rustc_hir::attrs::AttributeKind;
 use rustc_span::{Span, Symbol, sym};
 
 use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
 use crate::context::Stage;
+use crate::target_checking::AllowedTargets;
+use crate::target_checking::Policy::{Allow, Warn};
 
 pub(crate) struct NonExhaustiveParser;
 
 impl<S: Stage> NoArgsAttributeParser<S> for NonExhaustiveParser {
     const PATH: &[Symbol] = &[sym::non_exhaustive];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Enum),
+        Allow(Target::Struct),
+        Allow(Target::Variant),
+        Warn(Target::Field),
+        Warn(Target::Arm),
+        Warn(Target::MacroDef),
+        Warn(Target::MacroCall),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::NonExhaustive;
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/path.rs b/compiler/rustc_attr_parsing/src/attributes/path.rs
index febb1b45a18..e4cb806bb42 100644
--- a/compiler/rustc_attr_parsing/src/attributes/path.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/path.rs
@@ -1,10 +1,4 @@
-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;
+use super::prelude::*;
 
 pub(crate) struct PathParser;
 
@@ -12,7 +6,12 @@ impl<S: Stage> SingleAttributeParser<S> for PathParser {
     const PATH: &[Symbol] = &[sym::path];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
-    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "file");
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowListWarnRest(&[Allow(Target::Mod), Error(Target::Crate)]);
+    const TEMPLATE: AttributeTemplate = template!(
+        NameValueStr: "file",
+        "https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let Some(nv) = args.name_value() else {
diff --git a/compiler/rustc_attr_parsing/src/attributes/prelude.rs b/compiler/rustc_attr_parsing/src/attributes/prelude.rs
new file mode 100644
index 00000000000..980366b5c37
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs
@@ -0,0 +1,29 @@
+// data structures
+#[doc(hidden)]
+pub(super) use rustc_feature::{AttributeTemplate, template};
+#[doc(hidden)]
+pub(super) use rustc_hir::attrs::AttributeKind;
+#[doc(hidden)]
+pub(super) use rustc_hir::lints::AttributeLintKind;
+#[doc(hidden)]
+pub(super) use rustc_hir::{MethodKind, Target};
+#[doc(hidden)]
+pub(super) use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
+#[doc(hidden)]
+pub(super) use thin_vec::ThinVec;
+
+#[doc(hidden)]
+pub(super) use crate::attributes::{
+    AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn,
+    NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
+};
+// contexts
+#[doc(hidden)]
+pub(super) use crate::context::{AcceptContext, FinalizeContext, Stage};
+#[doc(hidden)]
+pub(super) use crate::parser::*;
+// target checking
+#[doc(hidden)]
+pub(super) use crate::target_checking::Policy::{Allow, Error, Warn};
+#[doc(hidden)]
+pub(super) use crate::target_checking::{ALL_TARGETS, AllowedTargets};
diff --git a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs
new file mode 100644
index 00000000000..b9929d6f1f8
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs
@@ -0,0 +1,139 @@
+use super::prelude::*;
+
+const PROC_MACRO_ALLOWED_TARGETS: AllowedTargets =
+    AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate), Warn(Target::MacroCall)]);
+
+pub(crate) struct ProcMacroParser;
+impl<S: Stage> NoArgsAttributeParser<S> for ProcMacroParser {
+    const PATH: &[Symbol] = &[sym::proc_macro];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::ProcMacro;
+}
+
+pub(crate) struct ProcMacroAttributeParser;
+impl<S: Stage> NoArgsAttributeParser<S> for ProcMacroAttributeParser {
+    const PATH: &[Symbol] = &[sym::proc_macro_attribute];
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS;
+    const CREATE: fn(Span) -> AttributeKind = AttributeKind::ProcMacroAttribute;
+}
+
+pub(crate) struct ProcMacroDeriveParser;
+impl<S: Stage> SingleAttributeParser<S> for ProcMacroDeriveParser {
+    const PATH: &[Symbol] = &[sym::proc_macro_derive];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS;
+    const TEMPLATE: AttributeTemplate = template!(
+        List: &["TraitName", "TraitName, attributes(name1, name2, ...)"],
+        "https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros"
+    );
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let (trait_name, helper_attrs) = parse_derive_like(cx, args, true)?;
+        Some(AttributeKind::ProcMacroDerive {
+            trait_name: trait_name.expect("Trait name is mandatory, so it is present"),
+            helper_attrs,
+            span: cx.attr_span,
+        })
+    }
+}
+
+pub(crate) struct RustcBuiltinMacroParser;
+impl<S: Stage> SingleAttributeParser<S> for RustcBuiltinMacroParser {
+    const PATH: &[Symbol] = &[sym::rustc_builtin_macro];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]);
+    const TEMPLATE: AttributeTemplate =
+        template!(List: &["TraitName", "TraitName, attributes(name1, name2, ...)"]);
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let (builtin_name, helper_attrs) = parse_derive_like(cx, args, false)?;
+        Some(AttributeKind::RustcBuiltinMacro { builtin_name, helper_attrs, span: cx.attr_span })
+    }
+}
+
+fn parse_derive_like<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    args: &ArgParser<'_>,
+    trait_name_mandatory: bool,
+) -> Option<(Option<Symbol>, ThinVec<Symbol>)> {
+    let Some(list) = args.list() else {
+        // For #[rustc_builtin_macro], it is permitted to leave out the trait name
+        if args.no_args().is_ok() && !trait_name_mandatory {
+            return Some((None, ThinVec::new()));
+        }
+        cx.expected_list(cx.attr_span);
+        return None;
+    };
+    let mut items = list.mixed();
+
+    // Parse the name of the trait that is derived.
+    let Some(trait_attr) = items.next() else {
+        cx.expected_at_least_one_argument(list.span);
+        return None;
+    };
+    let Some(trait_attr) = trait_attr.meta_item() else {
+        cx.unexpected_literal(trait_attr.span());
+        return None;
+    };
+    let Some(trait_ident) = trait_attr.path().word() else {
+        cx.expected_identifier(trait_attr.path().span());
+        return None;
+    };
+    if !trait_ident.name.can_be_raw() {
+        cx.expected_identifier(trait_ident.span);
+        return None;
+    }
+    if let Err(e) = trait_attr.args().no_args() {
+        cx.expected_no_args(e);
+        return None;
+    };
+
+    // Parse optional attributes
+    let mut attributes = ThinVec::new();
+    if let Some(attrs) = items.next() {
+        let Some(attr_list) = attrs.meta_item() else {
+            cx.expected_list(attrs.span());
+            return None;
+        };
+        if !attr_list.path().word_is(sym::attributes) {
+            cx.expected_specific_argument(attrs.span(), &[sym::attributes]);
+            return None;
+        }
+        let Some(attr_list) = attr_list.args().list() else {
+            cx.expected_list(attrs.span());
+            return None;
+        };
+
+        // Parse item in `attributes(...)` argument
+        for attr in attr_list.mixed() {
+            let Some(attr) = attr.meta_item() else {
+                cx.expected_identifier(attr.span());
+                return None;
+            };
+            if let Err(e) = attr.args().no_args() {
+                cx.expected_no_args(e);
+                return None;
+            };
+            let Some(ident) = attr.path().word() else {
+                cx.expected_identifier(attr.path().span());
+                return None;
+            };
+            if !ident.name.can_be_raw() {
+                cx.expected_identifier(ident.span);
+                return None;
+            }
+            attributes.push(ident.name);
+        }
+    }
+
+    // If anything else is specified, we should reject it
+    if let Some(next) = items.next() {
+        cx.expected_no_args(next.span());
+    }
+
+    Some((Some(trait_ident.name), attributes))
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs
new file mode 100644
index 00000000000..80fe82bf542
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs
@@ -0,0 +1,141 @@
+//! Attributes that are only used on function prototypes.
+
+use rustc_feature::{AttributeTemplate, template};
+use rustc_hir::Target;
+use rustc_hir::attrs::{AttributeKind, MirDialect, MirPhase};
+use rustc_span::{Span, Symbol, sym};
+
+use super::{AttributeOrder, OnDuplicate};
+use crate::attributes::SingleAttributeParser;
+use crate::context::{AcceptContext, Stage};
+use crate::parser::ArgParser;
+use crate::target_checking::AllowedTargets;
+use crate::target_checking::Policy::Allow;
+
+pub(crate) struct CustomMirParser;
+
+impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::custom_mir];
+
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]);
+
+    const TEMPLATE: AttributeTemplate = template!(List: &[r#"dialect = "...", phase = "...""#]);
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(list) = args.list() else {
+            cx.expected_list(cx.attr_span);
+            return None;
+        };
+
+        let mut dialect = None;
+        let mut phase = None;
+        let mut failed = false;
+
+        for item in list.mixed() {
+            let Some(meta_item) = item.meta_item() else {
+                cx.expected_name_value(item.span(), None);
+                failed = true;
+                break;
+            };
+
+            if let Some(arg) = meta_item.word_is(sym::dialect) {
+                extract_value(cx, sym::dialect, arg, meta_item.span(), &mut dialect, &mut failed);
+            } else if let Some(arg) = meta_item.word_is(sym::phase) {
+                extract_value(cx, sym::phase, arg, meta_item.span(), &mut phase, &mut failed);
+            } else if let Some(word) = meta_item.path().word() {
+                let word = word.to_string();
+                cx.unknown_key(meta_item.span(), word, &["dialect", "phase"]);
+                failed = true;
+            } else {
+                cx.expected_name_value(meta_item.span(), None);
+                failed = true;
+            };
+        }
+
+        let dialect = parse_dialect(cx, dialect, &mut failed);
+        let phase = parse_phase(cx, phase, &mut failed);
+
+        if failed {
+            return None;
+        }
+
+        Some(AttributeKind::CustomMir(dialect, phase, cx.attr_span))
+    }
+}
+
+fn extract_value<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    key: Symbol,
+    arg: &ArgParser<'_>,
+    span: Span,
+    out_val: &mut Option<(Symbol, Span)>,
+    failed: &mut bool,
+) {
+    if out_val.is_some() {
+        cx.duplicate_key(span, key);
+        *failed = true;
+        return;
+    }
+
+    let Some(val) = arg.name_value() else {
+        cx.expected_single_argument(arg.span().unwrap_or(span));
+        *failed = true;
+        return;
+    };
+
+    let Some(value_sym) = val.value_as_str() else {
+        cx.expected_string_literal(val.value_span, Some(val.value_as_lit()));
+        *failed = true;
+        return;
+    };
+
+    *out_val = Some((value_sym, val.value_span));
+}
+
+fn parse_dialect<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    dialect: Option<(Symbol, Span)>,
+    failed: &mut bool,
+) -> Option<(MirDialect, Span)> {
+    let (dialect, span) = dialect?;
+
+    let dialect = match dialect {
+        sym::analysis => MirDialect::Analysis,
+        sym::built => MirDialect::Built,
+        sym::runtime => MirDialect::Runtime,
+
+        _ => {
+            cx.expected_specific_argument(span, &[sym::analysis, sym::built, sym::runtime]);
+            *failed = true;
+            return None;
+        }
+    };
+
+    Some((dialect, span))
+}
+
+fn parse_phase<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    phase: Option<(Symbol, Span)>,
+    failed: &mut bool,
+) -> Option<(MirPhase, Span)> {
+    let (phase, span) = phase?;
+
+    let phase = match phase {
+        sym::initial => MirPhase::Initial,
+        sym::post_cleanup => MirPhase::PostCleanup,
+        sym::optimized => MirPhase::Optimized,
+
+        _ => {
+            cx.expected_specific_argument(span, &[sym::initial, sym::post_cleanup, sym::optimized]);
+            *failed = true;
+            return None;
+        }
+    };
+
+    Some((phase, span))
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs
index 6a45832ed7f..0330e2515c7 100644
--- a/compiler/rustc_attr_parsing/src/attributes/repr.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs
@@ -1,14 +1,9 @@
 use rustc_abi::Align;
 use rustc_ast::{IntTy, LitIntType, LitKind, UintTy};
-use rustc_attr_data_structures::{AttributeKind, IntType, ReprAttr};
-use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{DUMMY_SP, Span, Symbol, sym};
+use rustc_hir::attrs::{IntType, ReprAttr};
 
-use super::{AcceptMapping, AttributeParser, CombineAttributeParser, ConvertFn, FinalizeContext};
-use crate::context::{AcceptContext, Stage};
-use crate::parser::{ArgParser, MetaItemListParser, MetaItemParser};
-use crate::session_diagnostics;
-use crate::session_diagnostics::IncorrectReprFormatGenericCause;
+use super::prelude::*;
+use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
 
 /// Parse #[repr(...)] forms.
 ///
@@ -26,8 +21,10 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
     const CONVERT: ConvertFn<Self::Item> =
         |items, first_span| AttributeKind::Repr { reprs: items, first_span };
     // FIXME(jdonszelmann): never used
-    const TEMPLATE: AttributeTemplate =
-        template!(List: "C | Rust | align(...) | packed(...) | <integer type> | transparent");
+    const TEMPLATE: AttributeTemplate = template!(
+        List: &["C", "Rust", "transparent", "align(...)", "packed(...)", "<integer type>"],
+        "https://doc.rust-lang.org/reference/type-layout.html#representations"
+    );
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
@@ -58,6 +55,10 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
 
         reprs
     }
+
+    //FIXME Still checked fully in `check_attr.rs`
+    //This one is slightly more complicated because the allowed targets depend on the arguments
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
 }
 
 macro_rules! int_pat {
@@ -274,8 +275,8 @@ fn parse_alignment(node: &LitKind) -> Result<Align, &'static str> {
 pub(crate) struct AlignParser(Option<(Align, Span)>);
 
 impl AlignParser {
-    const PATH: &'static [Symbol] = &[sym::align];
-    const TEMPLATE: AttributeTemplate = template!(List: "<alignment in bytes>");
+    const PATH: &'static [Symbol] = &[sym::rustc_align];
+    const TEMPLATE: AttributeTemplate = template!(List: &["<alignment in bytes>"]);
 
     fn parse<'c, S: Stage>(
         &mut self,
@@ -316,9 +317,44 @@ impl AlignParser {
 
 impl<S: Stage> AttributeParser<S> for AlignParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[(Self::PATH, Self::TEMPLATE, Self::parse)];
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::ForeignFn),
+    ]);
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         let (align, span) = self.0?;
         Some(AttributeKind::Align { align, span })
     }
 }
+
+#[derive(Default)]
+pub(crate) struct AlignStaticParser(AlignParser);
+
+impl AlignStaticParser {
+    const PATH: &'static [Symbol] = &[sym::rustc_align_static];
+    const TEMPLATE: AttributeTemplate = AlignParser::TEMPLATE;
+
+    fn parse<'c, S: Stage>(
+        &mut self,
+        cx: &'c mut AcceptContext<'_, '_, S>,
+        args: &'c ArgParser<'_>,
+    ) {
+        self.0.parse(cx, args)
+    }
+}
+
+impl<S: Stage> AttributeParser<S> for AlignStaticParser {
+    const ATTRIBUTES: AcceptMapping<Self, S> = &[(Self::PATH, Self::TEMPLATE, Self::parse)];
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::Static), Allow(Target::ForeignStatic)]);
+
+    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
+        let (align, span) = self.0.0?;
+        Some(AttributeKind::Align { align, span })
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
index 7ca951dc0bb..a995549fc7c 100644
--- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
@@ -1,10 +1,5 @@
-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, parse_single_integer};
-use crate::parser::ArgParser;
+use super::prelude::*;
+use super::util::parse_single_integer;
 
 pub(crate) struct RustcLayoutScalarValidRangeStart;
 
@@ -12,7 +7,8 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeStart {
     const PATH: &'static [Symbol] = &[sym::rustc_layout_scalar_valid_range_start];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
-    const TEMPLATE: AttributeTemplate = template!(List: "start");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
+    const TEMPLATE: AttributeTemplate = template!(List: &["start"]);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         parse_single_integer(cx, args)
@@ -26,7 +22,8 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeEnd {
     const PATH: &'static [Symbol] = &[sym::rustc_layout_scalar_valid_range_end];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
-    const TEMPLATE: AttributeTemplate = template!(List: "end");
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
+    const TEMPLATE: AttributeTemplate = template!(List: &["end"]);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         parse_single_integer(cx, args)
@@ -40,6 +37,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcObjectLifetimeDefaultParser {
     const PATH: &[rustc_span::Symbol] = &[sym::rustc_object_lifetime_default];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
     const TEMPLATE: AttributeTemplate = template!(Word);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
diff --git a/compiler/rustc_attr_parsing/src/attributes/semantics.rs b/compiler/rustc_attr_parsing/src/attributes/semantics.rs
index 74fdff5d2e1..d7f62483297 100644
--- a/compiler/rustc_attr_parsing/src/attributes/semantics.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/semantics.rs
@@ -1,12 +1,9 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_span::{Span, Symbol, sym};
-
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::Stage;
+use super::prelude::*;
 
 pub(crate) struct MayDangleParser;
 impl<S: Stage> NoArgsAttributeParser<S> for MayDangleParser {
     const PATH: &[Symbol] = &[sym::may_dangle];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
     const CREATE: fn(span: Span) -> AttributeKind = AttributeKind::MayDangle;
 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 8f405e5aad9..b94e23477ff 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -1,18 +1,13 @@
 use std::num::NonZero;
 
-use rustc_attr_data_structures::{
-    AttributeKind, DefaultBodyStability, PartialConstStability, Stability, StabilityLevel,
-    StableSince, UnstableReason, VERSION_PLACEHOLDER,
-};
 use rustc_errors::ErrorGuaranteed;
-use rustc_feature::template;
-use rustc_span::{Ident, Span, Symbol, sym};
+use rustc_hir::{
+    DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel,
+    StableSince, Target, UnstableReason, VERSION_PLACEHOLDER,
+};
 
+use super::prelude::*;
 use super::util::parse_version;
-use super::{AcceptMapping, AttributeParser, OnDuplicate};
-use crate::attributes::NoArgsAttributeParser;
-use crate::context::{AcceptContext, FinalizeContext, Stage};
-use crate::parser::{ArgParser, MetaItemParser};
 use crate::session_diagnostics::{self, UnsupportedLiteralReason};
 
 macro_rules! reject_outside_std {
@@ -25,6 +20,36 @@ macro_rules! reject_outside_std {
     };
 }
 
+const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+    Allow(Target::Fn),
+    Allow(Target::Struct),
+    Allow(Target::Enum),
+    Allow(Target::Union),
+    Allow(Target::Method(MethodKind::Inherent)),
+    Allow(Target::Method(MethodKind::Trait { body: false })),
+    Allow(Target::Method(MethodKind::Trait { body: true })),
+    Allow(Target::Method(MethodKind::TraitImpl)),
+    Allow(Target::Impl { of_trait: false }),
+    Allow(Target::Impl { of_trait: true }),
+    Allow(Target::MacroDef),
+    Allow(Target::Crate),
+    Allow(Target::Mod),
+    Allow(Target::Use), // FIXME I don't think this does anything?
+    Allow(Target::Const),
+    Allow(Target::AssocConst),
+    Allow(Target::AssocTy),
+    Allow(Target::Trait),
+    Allow(Target::TraitAlias),
+    Allow(Target::TyAlias),
+    Allow(Target::Variant),
+    Allow(Target::Field),
+    Allow(Target::Param),
+    Allow(Target::Static),
+    Allow(Target::ForeignFn),
+    Allow(Target::ForeignStatic),
+    Allow(Target::ExternCrate),
+]);
+
 #[derive(Default)]
 pub(crate) struct StabilityParser {
     allowed_through_unstable_modules: Option<Symbol>,
@@ -47,7 +72,7 @@ impl<S: Stage> AttributeParser<S> for StabilityParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[
         (
             &[sym::stable],
-            template!(List: r#"feature = "name", since = "version""#),
+            template!(List: &[r#"feature = "name", since = "version""#]),
             |this, cx, args| {
                 reject_outside_std!(cx);
                 if !this.check_duplicate(cx)
@@ -59,7 +84,7 @@ impl<S: Stage> AttributeParser<S> for StabilityParser {
         ),
         (
             &[sym::unstable],
-            template!(List: r#"feature = "name", reason = "...", issue = "N""#),
+            template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]),
             |this, cx, args| {
                 reject_outside_std!(cx);
                 if !this.check_duplicate(cx)
@@ -74,11 +99,19 @@ impl<S: Stage> AttributeParser<S> for StabilityParser {
             template!(NameValueStr: "deprecation message"),
             |this, cx, args| {
                 reject_outside_std!(cx);
-                this.allowed_through_unstable_modules =
-                    args.name_value().and_then(|i| i.value_as_str())
+                let Some(nv) = args.name_value() else {
+                    cx.expected_name_value(cx.attr_span, None);
+                    return;
+                };
+                let Some(value_str) = nv.value_as_str() else {
+                    cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
+                    return;
+                };
+                this.allowed_through_unstable_modules = Some(value_str);
             },
         ),
     ];
+    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
 
     fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         if let Some(atum) = self.allowed_through_unstable_modules {
@@ -123,7 +156,7 @@ pub(crate) struct BodyStabilityParser {
 impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[(
         &[sym::rustc_default_body_unstable],
-        template!(List: r#"feature = "name", reason = "...", issue = "N""#),
+        template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]),
         |this, cx, args| {
             reject_outside_std!(cx);
             if this.stability.is_some() {
@@ -134,6 +167,7 @@ impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
             }
         },
     )];
+    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         let (stability, span) = self.stability?;
@@ -146,6 +180,10 @@ pub(crate) struct ConstStabilityIndirectParser;
 impl<S: Stage> NoArgsAttributeParser<S> for ConstStabilityIndirectParser {
     const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ConstStabilityIndirect;
 }
 
@@ -169,34 +207,43 @@ impl ConstStabilityParser {
 
 impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[
-        (&[sym::rustc_const_stable], template!(List: r#"feature = "name""#), |this, cx, args| {
-            reject_outside_std!(cx);
+        (
+            &[sym::rustc_const_stable],
+            template!(List: &[r#"feature = "name""#]),
+            |this, cx, args| {
+                reject_outside_std!(cx);
 
-            if !this.check_duplicate(cx)
-                && let Some((feature, level)) = parse_stability(cx, args)
-            {
-                this.stability = Some((
-                    PartialConstStability { level, feature, promotable: false },
-                    cx.attr_span,
-                ));
-            }
-        }),
-        (&[sym::rustc_const_unstable], template!(List: r#"feature = "name""#), |this, cx, args| {
-            reject_outside_std!(cx);
-            if !this.check_duplicate(cx)
-                && let Some((feature, level)) = parse_unstability(cx, args)
-            {
-                this.stability = Some((
-                    PartialConstStability { level, feature, promotable: false },
-                    cx.attr_span,
-                ));
-            }
-        }),
+                if !this.check_duplicate(cx)
+                    && let Some((feature, level)) = parse_stability(cx, args)
+                {
+                    this.stability = Some((
+                        PartialConstStability { level, feature, promotable: false },
+                        cx.attr_span,
+                    ));
+                }
+            },
+        ),
+        (
+            &[sym::rustc_const_unstable],
+            template!(List: &[r#"feature = "name""#]),
+            |this, cx, args| {
+                reject_outside_std!(cx);
+                if !this.check_duplicate(cx)
+                    && let Some((feature, level)) = parse_unstability(cx, args)
+                {
+                    this.stability = Some((
+                        PartialConstStability { level, feature, promotable: false },
+                        cx.attr_span,
+                    ));
+                }
+            },
+        ),
         (&[sym::rustc_promotable], template!(Word), |this, cx, _| {
             reject_outside_std!(cx);
             this.promotable = true;
         }),
     ];
+    const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
 
     fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         if self.promotable {
@@ -247,7 +294,12 @@ pub(crate) fn parse_stability<S: Stage>(
     let mut feature = None;
     let mut since = None;
 
-    for param in args.list()?.mixed() {
+    let ArgParser::List(list) = args else {
+        cx.expected_list(cx.attr_span);
+        return None;
+    };
+
+    for param in list.mixed() {
         let param_span = param.span();
         let Some(param) = param.meta_item() else {
             cx.emit_err(session_diagnostics::UnsupportedLiteral {
@@ -292,12 +344,12 @@ pub(crate) fn parse_stability<S: Stage>(
         } else if let Some(version) = parse_version(since) {
             StableSince::Version(version)
         } else {
-            cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
-            StableSince::Err
+            let err = cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
+            StableSince::Err(err)
         }
     } else {
-        cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
-        StableSince::Err
+        let err = cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
+        StableSince::Err(err)
     };
 
     match feature {
@@ -322,7 +374,13 @@ pub(crate) fn parse_unstability<S: Stage>(
     let mut is_soft = false;
     let mut implied_by = None;
     let mut old_name = None;
-    for param in args.list()?.mixed() {
+
+    let ArgParser::List(list) = args else {
+        cx.expected_list(cx.attr_span);
+        return None;
+    };
+
+    for param in list.mixed() {
         let Some(param) = param.meta_item() else {
             cx.emit_err(session_diagnostics::UnsupportedLiteral {
                 span: param.span(),
diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
index ee81f64860f..510ff1ded49 100644
--- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
@@ -1,11 +1,4 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_attr_data_structures::lints::AttributeLintKind;
-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;
+use super::prelude::*;
 
 pub(crate) struct IgnoreParser;
 
@@ -13,7 +6,12 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
     const PATH: &[Symbol] = &[sym::ignore];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
-    const TEMPLATE: AttributeTemplate = template!(Word, NameValueStr: "reason");
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowListWarnRest(&[Allow(Target::Fn), Error(Target::WherePredicate)]);
+    const TEMPLATE: AttributeTemplate = template!(
+        Word, NameValueStr: "reason",
+        "https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         Some(AttributeKind::Ignore {
@@ -23,7 +21,7 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
                 ArgParser::NameValue(name_value) => {
                     let Some(str_value) = name_value.value_as_str() else {
                         let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
-                            .suggestions(false, "ignore");
+                            .suggestions(cx.attr_style, "ignore");
                         let span = cx.attr_span;
                         cx.emit_lint(
                             AttributeLintKind::IllFormedAttributeInput { suggestions },
@@ -34,8 +32,8 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
                     Some(str_value)
                 }
                 ArgParser::List(_) => {
-                    let suggestions =
-                        <Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "ignore");
+                    let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
+                        .suggestions(cx.attr_style, "ignore");
                     let span = cx.attr_span;
                     cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span);
                     return None;
@@ -44,3 +42,59 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
         })
     }
 }
+
+pub(crate) struct ShouldPanicParser;
+
+impl<S: Stage> SingleAttributeParser<S> for ShouldPanicParser {
+    const PATH: &[Symbol] = &[sym::should_panic];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowListWarnRest(&[Allow(Target::Fn), Error(Target::WherePredicate)]);
+    const TEMPLATE: AttributeTemplate = template!(
+        Word, List: &[r#"expected = "reason""#], NameValueStr: "reason",
+        "https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute"
+    );
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        Some(AttributeKind::ShouldPanic {
+            span: cx.attr_span,
+            reason: match args {
+                ArgParser::NoArgs => None,
+                ArgParser::NameValue(name_value) => {
+                    let Some(str_value) = name_value.value_as_str() else {
+                        cx.expected_string_literal(
+                            name_value.value_span,
+                            Some(name_value.value_as_lit()),
+                        );
+                        return None;
+                    };
+                    Some(str_value)
+                }
+                ArgParser::List(list) => {
+                    let Some(single) = list.single() else {
+                        cx.expected_single_argument(list.span);
+                        return None;
+                    };
+                    let Some(single) = single.meta_item() else {
+                        cx.expected_name_value(single.span(), Some(sym::expected));
+                        return None;
+                    };
+                    if !single.path().word_is(sym::expected) {
+                        cx.expected_specific_argument_strings(list.span, &[sym::expected]);
+                        return None;
+                    }
+                    let Some(nv) = single.args().name_value() else {
+                        cx.expected_name_value(single.span(), Some(sym::expected));
+                        return None;
+                    };
+                    let Some(expected) = nv.value_as_str() else {
+                        cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
+                        return None;
+                    };
+                    Some(expected)
+                }
+            },
+        })
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs
index e69a533699b..ced3bcad229 100644
--- a/compiler/rustc_attr_parsing/src/attributes/traits.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs
@@ -1,22 +1,22 @@
-use core::mem;
-
-use rustc_attr_data_structures::AttributeKind;
-use rustc_feature::{AttributeTemplate, template};
-use rustc_span::{Span, Symbol, sym};
+use std::mem;
 
+use super::prelude::*;
 use crate::attributes::{
     AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
 };
 use crate::context::{AcceptContext, Stage};
 use crate::parser::ArgParser;
+use crate::target_checking::Policy::{Allow, Warn};
+use crate::target_checking::{ALL_TARGETS, AllowedTargets};
 
 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::KeepInnermost;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
 
-    const TEMPLATE: AttributeTemplate = template!(List: "array, boxed_slice");
+    const TEMPLATE: AttributeTemplate = template!(List: &["array, boxed_slice"]);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let mut array = false;
@@ -42,7 +42,7 @@ impl<S: Stage> SingleAttributeParser<S> for SkipDuringMethodDispatchParser {
                 Some(key @ sym::array) => (key, &mut array),
                 Some(key @ sym::boxed_slice) => (key, &mut boxed_slice),
                 _ => {
-                    cx.expected_specific_argument(path.span(), vec!["array", "boxed_slice"]);
+                    cx.expected_specific_argument(path.span(), &[sym::array, sym::boxed_slice]);
                     continue;
                 }
             };
@@ -58,6 +58,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::ParenSugar;
 }
 
@@ -65,6 +66,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::AssocConst)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::TypeConst;
 }
 
@@ -74,6 +76,12 @@ pub(crate) struct MarkerParser;
 impl<S: Stage> NoArgsAttributeParser<S> for MarkerParser {
     const PATH: &[Symbol] = &[sym::marker];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Trait),
+        Warn(Target::Field),
+        Warn(Target::Arm),
+        Warn(Target::MacroDef),
+    ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::Marker;
 }
 
@@ -81,6 +89,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::DenyExplicitImpl;
 }
 
@@ -88,6 +97,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::DoNotImplementViaObject;
 }
 
@@ -98,6 +108,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::ConstTrait;
 }
 
@@ -107,6 +118,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::SpecializationTrait;
 }
 
@@ -114,6 +126,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::UnsafeSpecializationMarker;
 }
 
@@ -123,6 +136,7 @@ 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::Coinductive;
 }
 
@@ -130,20 +144,17 @@ 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 ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::Method(MethodKind::Inherent))]);
     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 ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::Struct), Allow(Target::Trait)]);
     const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Fundamental;
 }
 
@@ -151,5 +162,6 @@ pub(crate) struct PointeeParser;
 impl<S: Stage> NoArgsAttributeParser<S> for PointeeParser {
     const PATH: &[Symbol] = &[sym::pointee];
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
     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 c9fdc57cc06..ea1f5549c4e 100644
--- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
@@ -1,11 +1,6 @@
-use rustc_attr_data_structures::AttributeKind;
-use rustc_feature::{AttributeTemplate, template};
 use rustc_span::hygiene::Transparency;
-use rustc_span::{Symbol, sym};
 
-use super::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::{AcceptContext, Stage};
-use crate::parser::ArgParser;
+use super::prelude::*;
 
 pub(crate) struct TransparencyParser;
 
@@ -18,8 +13,9 @@ impl<S: Stage> SingleAttributeParser<S> for TransparencyParser {
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Custom(|cx, used, unused| {
         cx.dcx().span_err(vec![used, unused], "multiple macro transparency attributes");
     });
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]);
     const TEMPLATE: AttributeTemplate =
-        template!(NameValueStr: "transparent|semitransparent|opaque");
+        template!(NameValueStr: ["transparent", "semitransparent", "opaque"]);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let Some(nv) = args.name_value() else {
@@ -33,7 +29,7 @@ impl<S: Stage> SingleAttributeParser<S> for TransparencyParser {
             Some(_) => {
                 cx.expected_specific_argument_strings(
                     nv.value_span,
-                    vec!["transparent", "semitransparent", "opaque"],
+                    &[sym::transparent, sym::semitransparent, sym::opaque],
                 );
                 None
             }
diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs
index 503d2f1fae1..77e8c32e59d 100644
--- a/compiler/rustc_attr_parsing/src/attributes/util.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/util.rs
@@ -1,8 +1,12 @@
-use rustc_ast::attr::{AttributeExt, first_attr_value_str_by_name};
-use rustc_attr_data_structures::RustcVersion;
+use rustc_ast::LitKind;
+use rustc_ast::attr::AttributeExt;
 use rustc_feature::is_builtin_attr_name;
+use rustc_hir::RustcVersion;
 use rustc_span::{Symbol, sym};
 
+use crate::context::{AcceptContext, Stage};
+use crate::parser::ArgParser;
+
 /// Parse a rustc version number written inside string literal in an attribute,
 /// like appears in `since = "1.0.0"`. Suffixes like "-dev" and "-nightly" are
 /// not accepted in this position, unlike when parsing CFG_RELEASE.
@@ -23,10 +27,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
     attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
 }
 
-pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> {
-    first_attr_value_str_by_name(attrs, sym::crate_name)
-}
-
 pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
     attrs: impl Iterator<Item = &'tcx T>,
     symbol: Symbol,
@@ -56,3 +56,32 @@ pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
     }
     false
 }
+
+/// 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/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 1449680e35b..ee5b7322b02 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -4,39 +4,53 @@ use std::ops::{Deref, DerefMut};
 use std::sync::LazyLock;
 
 use private::Sealed;
-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};
-use rustc_feature::{AttributeTemplate, Features};
-use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, HirId};
+use rustc_ast::{AttrStyle, CRATE_NODE_ID, MetaItemLit, NodeId};
+use rustc_errors::{Diag, Diagnostic, Level};
+use rustc_feature::AttributeTemplate;
+use rustc_hir::attrs::AttributeKind;
+use rustc_hir::lints::{AttributeLint, AttributeLintKind};
+use rustc_hir::{AttrPath, CRATE_HIR_ID, HirId};
 use rustc_session::Session;
-use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
+use rustc_span::{ErrorGuaranteed, Span, Symbol};
 
+use crate::AttributeParser;
 use crate::attributes::allow_unstable::{
     AllowConstFnUnstableParser, AllowInternalUnstableParser, UnstableFeatureBoundParser,
 };
+use crate::attributes::body::CoroutineParser;
 use crate::attributes::codegen_attrs::{
-    ColdParser, ExportNameParser, NakedParser, NoMangleParser, OmitGdbPrettyPrinterSectionParser,
-    OptimizeParser, TargetFeatureParser, TrackCallerParser, UsedParser,
+    ColdParser, CoverageParser, ExportNameParser, ForceTargetFeatureParser, NakedParser,
+    NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser, SanitizeParser,
+    TargetFeatureParser, TrackCallerParser, UsedParser,
 };
 use crate::attributes::confusables::ConfusablesParser;
+use crate::attributes::crate_level::{
+    CrateNameParser, MoveSizeLimitParser, NoCoreParser, NoStdParser, PatternComplexityLimitParser,
+    RecursionLimitParser, RustcCoherenceIsCoreParser, TypeLengthLimitParser,
+};
 use crate::attributes::deprecation::DeprecationParser;
 use crate::attributes::dummy::DummyParser;
 use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
 use crate::attributes::link_attrs::{
     ExportStableParser, FfiConstParser, FfiPureParser, LinkNameParser, LinkOrdinalParser,
-    LinkSectionParser, StdInternalSymbolParser,
+    LinkParser, LinkSectionParser, LinkageParser, StdInternalSymbolParser,
 };
 use crate::attributes::lint_helpers::{
     AsPtrParser, AutomaticallyDerivedParser, PassByValueParser, PubTransparentParser,
 };
 use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser};
+use crate::attributes::macro_attrs::{
+    AllowInternalUnsafeParser, MacroEscapeParser, MacroUseParser,
+};
 use crate::attributes::must_use::MustUseParser;
 use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser;
 use crate::attributes::non_exhaustive::NonExhaustiveParser;
 use crate::attributes::path::PathParser as PathAttributeParser;
-use crate::attributes::repr::{AlignParser, ReprParser};
+use crate::attributes::proc_macro_attrs::{
+    ProcMacroAttributeParser, ProcMacroDeriveParser, ProcMacroParser, RustcBuiltinMacroParser,
+};
+use crate::attributes::prototype::CustomMirParser;
+use crate::attributes::repr::{AlignParser, AlignStaticParser, ReprParser};
 use crate::attributes::rustc_internal::{
     RustcLayoutScalarValidRangeEnd, RustcLayoutScalarValidRangeStart,
     RustcObjectLifetimeDefaultParser,
@@ -45,27 +59,37 @@ use crate::attributes::semantics::MayDangleParser;
 use crate::attributes::stability::{
     BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser,
 };
-use crate::attributes::test_attrs::IgnoreParser;
+use crate::attributes::test_attrs::{IgnoreParser, ShouldPanicParser};
 use crate::attributes::traits::{
-    AllowIncoherentImplParser, CoherenceIsCoreParser, CoinductiveParser, ConstTraitParser,
-    DenyExplicitImplParser, DoNotImplementViaObjectParser, FundamentalParser, MarkerParser,
-    ParenSugarParser, PointeeParser, SkipDuringMethodDispatchParser, SpecializationTraitParser,
-    TypeConstParser, UnsafeSpecializationMarkerParser,
+    AllowIncoherentImplParser, 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};
+use crate::parser::{ArgParser, PathParser};
 use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem};
+use crate::target_checking::AllowedTargets;
 
-macro_rules! group_type {
-    ($stage: ty) => {
-         LazyLock<(
-            BTreeMap<&'static [Symbol], Vec<(AttributeTemplate, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $stage>, &ArgParser<'a>) + Send + Sync>)>>,
-            Vec<Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, $stage>) -> Option<AttributeKind>>>
-        )>
-    };
+type GroupType<S> = LazyLock<GroupTypeInner<S>>;
+
+pub(super) struct GroupTypeInner<S: Stage> {
+    pub(super) accepters: BTreeMap<&'static [Symbol], Vec<GroupTypeInnerAccept<S>>>,
+    pub(super) finalizers: Vec<FinalizeFn<S>>,
+}
+
+pub(super) struct GroupTypeInnerAccept<S: Stage> {
+    pub(super) template: AttributeTemplate,
+    pub(super) accept_fn: AcceptFn<S>,
+    pub(super) allowed_targets: AllowedTargets,
 }
 
+type AcceptFn<S> =
+    Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser<'a>) + Send + Sync>;
+type FinalizeFn<S> =
+    Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, S>) -> Option<AttributeKind>>;
+
 macro_rules! attribute_parsers {
     (
         pub(crate) static $name: ident = [$($names: ty),* $(,)?];
@@ -88,11 +112,11 @@ macro_rules! attribute_parsers {
         }
     };
     (
-        @[$ty: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?];
+        @[$stage: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?];
     ) => {
-        pub(crate) static $name: group_type!($ty) = LazyLock::new(|| {
-            let mut accepts = BTreeMap::<_, Vec<(AttributeTemplate, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $ty>, &ArgParser<'a>) + Send + Sync>)>>::new();
-            let mut finalizes = Vec::<Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, $ty>) -> Option<AttributeKind>>>::new();
+        pub(crate) static $name: GroupType<$stage> = LazyLock::new(|| {
+            let mut accepts = BTreeMap::<_, Vec<GroupTypeInnerAccept<$stage>>>::new();
+            let mut finalizes = Vec::<FinalizeFn<$stage>>::new();
             $(
                 {
                     thread_local! {
@@ -100,11 +124,15 @@ macro_rules! attribute_parsers {
                     };
 
                     for (path, template, accept_fn) in <$names>::ATTRIBUTES {
-                        accepts.entry(*path).or_default().push((*template, Box::new(|cx, args| {
-                            STATE_OBJECT.with_borrow_mut(|s| {
-                                accept_fn(s, cx, args)
-                            })
-                        })));
+                        accepts.entry(*path).or_default().push(GroupTypeInnerAccept {
+                            template: *template,
+                            accept_fn: Box::new(|cx, args| {
+                                STATE_OBJECT.with_borrow_mut(|s| {
+                                    accept_fn(s, cx, args)
+                                })
+                            }),
+                            allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS,
+                        });
                     }
 
                     finalizes.push(Box::new(|cx| {
@@ -114,7 +142,7 @@ macro_rules! attribute_parsers {
                 }
             )*
 
-            (accepts, finalizes)
+            GroupTypeInner { accepters:accepts, finalizers:finalizes }
         });
     };
 }
@@ -122,9 +150,11 @@ attribute_parsers!(
     pub(crate) static ATTRIBUTE_PARSERS = [
         // tidy-alphabetical-start
         AlignParser,
+        AlignStaticParser,
         BodyStabilityParser,
         ConfusablesParser,
         ConstStabilityParser,
+        MacroUseParser,
         NakedParser,
         StabilityParser,
         UsedParser,
@@ -133,12 +163,17 @@ attribute_parsers!(
         // tidy-alphabetical-start
         Combine<AllowConstFnUnstableParser>,
         Combine<AllowInternalUnstableParser>,
+        Combine<ForceTargetFeatureParser>,
+        Combine<LinkParser>,
         Combine<ReprParser>,
         Combine<TargetFeatureParser>,
         Combine<UnstableFeatureBoundParser>,
         // tidy-alphabetical-end
 
         // tidy-alphabetical-start
+        Single<CoverageParser>,
+        Single<CrateNameParser>,
+        Single<CustomMirParser>,
         Single<DeprecationParser>,
         Single<DummyParser>,
         Single<ExportNameParser>,
@@ -147,24 +182,36 @@ attribute_parsers!(
         Single<LinkNameParser>,
         Single<LinkOrdinalParser>,
         Single<LinkSectionParser>,
+        Single<LinkageParser>,
+        Single<MoveSizeLimitParser>,
         Single<MustUseParser>,
+        Single<ObjcClassParser>,
+        Single<ObjcSelectorParser>,
         Single<OptimizeParser>,
         Single<PathAttributeParser>,
+        Single<PatternComplexityLimitParser>,
+        Single<ProcMacroDeriveParser>,
+        Single<RecursionLimitParser>,
+        Single<RustcBuiltinMacroParser>,
         Single<RustcForceInlineParser>,
         Single<RustcLayoutScalarValidRangeEnd>,
         Single<RustcLayoutScalarValidRangeStart>,
         Single<RustcObjectLifetimeDefaultParser>,
+        Single<SanitizeParser>,
+        Single<ShouldPanicParser>,
         Single<SkipDuringMethodDispatchParser>,
         Single<TransparencyParser>,
+        Single<TypeLengthLimitParser>,
         Single<WithoutArgs<AllowIncoherentImplParser>>,
+        Single<WithoutArgs<AllowInternalUnsafeParser>>,
         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<CoroutineParser>>,
         Single<WithoutArgs<DenyExplicitImplParser>>,
         Single<WithoutArgs<DoNotImplementViaObjectParser>>,
         Single<WithoutArgs<ExportStableParser>>,
@@ -172,16 +219,21 @@ attribute_parsers!(
         Single<WithoutArgs<FfiPureParser>>,
         Single<WithoutArgs<FundamentalParser>>,
         Single<WithoutArgs<LoopMatchParser>>,
+        Single<WithoutArgs<MacroEscapeParser>>,
         Single<WithoutArgs<MarkerParser>>,
         Single<WithoutArgs<MayDangleParser>>,
+        Single<WithoutArgs<NoCoreParser>>,
         Single<WithoutArgs<NoImplicitPreludeParser>>,
         Single<WithoutArgs<NoMangleParser>>,
+        Single<WithoutArgs<NoStdParser>>,
         Single<WithoutArgs<NonExhaustiveParser>>,
-        Single<WithoutArgs<OmitGdbPrettyPrinterSectionParser>>,
         Single<WithoutArgs<ParenSugarParser>>,
         Single<WithoutArgs<PassByValueParser>>,
         Single<WithoutArgs<PointeeParser>>,
+        Single<WithoutArgs<ProcMacroAttributeParser>>,
+        Single<WithoutArgs<ProcMacroParser>>,
         Single<WithoutArgs<PubTransparentParser>>,
+        Single<WithoutArgs<RustcCoherenceIsCoreParser>>,
         Single<WithoutArgs<SpecializationTraitParser>>,
         Single<WithoutArgs<StdInternalSymbolParser>>,
         Single<WithoutArgs<TrackCallerParser>>,
@@ -201,24 +253,26 @@ mod private {
 #[allow(private_interfaces)]
 pub trait Stage: Sized + 'static + Sealed {
     type Id: Copy;
-    const SHOULD_EMIT_LINTS: bool;
 
-    fn parsers() -> &'static group_type!(Self);
+    fn parsers() -> &'static GroupType<Self>;
 
     fn emit_err<'sess>(
         &self,
         sess: &'sess Session,
         diag: impl for<'x> Diagnostic<'x>,
     ) -> ErrorGuaranteed;
+
+    fn should_emit(&self) -> ShouldEmit;
+
+    fn id_is_crate_root(id: Self::Id) -> bool;
 }
 
 // allow because it's a sealed trait
 #[allow(private_interfaces)]
 impl Stage for Early {
     type Id = NodeId;
-    const SHOULD_EMIT_LINTS: bool = false;
 
-    fn parsers() -> &'static group_type!(Self) {
+    fn parsers() -> &'static GroupType<Self> {
         &early::ATTRIBUTE_PARSERS
     }
     fn emit_err<'sess>(
@@ -226,11 +280,15 @@ impl Stage for Early {
         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()
-        }
+        self.should_emit().emit_err(sess.dcx().create_err(diag))
+    }
+
+    fn should_emit(&self) -> ShouldEmit {
+        self.emit_errors
+    }
+
+    fn id_is_crate_root(id: Self::Id) -> bool {
+        id == CRATE_NODE_ID
     }
 }
 
@@ -238,9 +296,8 @@ impl Stage for Early {
 #[allow(private_interfaces)]
 impl Stage for Late {
     type Id = HirId;
-    const SHOULD_EMIT_LINTS: bool = true;
 
-    fn parsers() -> &'static group_type!(Self) {
+    fn parsers() -> &'static GroupType<Self> {
         &late::ATTRIBUTE_PARSERS
     }
     fn emit_err<'sess>(
@@ -250,6 +307,14 @@ impl Stage for Late {
     ) -> ErrorGuaranteed {
         tcx.dcx().emit_err(diag)
     }
+
+    fn should_emit(&self) -> ShouldEmit {
+        ShouldEmit::ErrorsAndLints
+    }
+
+    fn id_is_crate_root(id: Self::Id) -> bool {
+        id == CRATE_HIR_ID
+    }
 }
 
 /// used when parsing attributes for miscellaneous things *before* ast lowering
@@ -270,6 +335,9 @@ pub struct AcceptContext<'f, 'sess, S: Stage> {
     /// The span of the attribute currently being parsed
     pub(crate) attr_span: Span,
 
+    /// Whether it is an inner or outer attribute
+    pub(crate) attr_style: AttrStyle,
+
     /// The expected structure of the attribute.
     ///
     /// Used in reporting errors to give a hint to users what the attribute *should* look like.
@@ -288,7 +356,10 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> {
     /// must be delayed until after HIR is built. This method will take care of the details of
     /// that.
     pub(crate) fn emit_lint(&mut self, lint: AttributeLintKind, span: Span) {
-        if !S::SHOULD_EMIT_LINTS {
+        if !matches!(
+            self.stage.should_emit(),
+            ShouldEmit::ErrorsAndLints | ShouldEmit::EarlyFatal { also_emit_lints: true }
+        ) {
             return;
         }
         let id = self.target_id;
@@ -351,6 +422,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
                     i.kind.is_bytestr().then(|| self.sess().source_map().start_point(i.span))
                 }),
             },
+            attr_style: self.attr_style,
         })
     }
 
@@ -361,6 +433,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::ExpectedIntegerLiteral,
+            attr_style: self.attr_style,
         })
     }
 
@@ -371,6 +444,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::ExpectedList,
+            attr_style: self.attr_style,
         })
     }
 
@@ -381,6 +455,19 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::ExpectedNoArgs,
+            attr_style: self.attr_style,
+        })
+    }
+
+    /// emit an error that a `name` was expected here
+    pub(crate) fn expected_identifier(&self, span: Span) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedIdentifier,
+            attr_style: self.attr_style,
         })
     }
 
@@ -393,6 +480,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::ExpectedNameValue(name),
+            attr_style: self.attr_style,
         })
     }
 
@@ -404,6 +492,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::DuplicateKey(key),
+            attr_style: self.attr_style,
         })
     }
 
@@ -416,6 +505,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::UnexpectedLiteral,
+            attr_style: self.attr_style,
         })
     }
 
@@ -426,6 +516,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::ExpectedSingleArgument,
+            attr_style: self.attr_style,
         })
     }
 
@@ -436,13 +527,15 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             template: self.template.clone(),
             attribute: self.attr_path.clone(),
             reason: AttributeParseErrorReason::ExpectedAtLeastOneArgument,
+            attr_style: self.attr_style,
         })
     }
 
+    /// produces an error along the lines of `expected one of [foo, meow]`
     pub(crate) fn expected_specific_argument(
         &self,
         span: Span,
-        possibilities: Vec<&'static str>,
+        possibilities: &[Symbol],
     ) -> ErrorGuaranteed {
         self.emit_err(AttributeParseError {
             span,
@@ -452,14 +545,38 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             reason: AttributeParseErrorReason::ExpectedSpecificArgument {
                 possibilities,
                 strings: false,
+                list: false,
             },
+            attr_style: self.attr_style,
         })
     }
 
+    /// produces an error along the lines of `expected one of [foo, meow] as an argument`.
+    /// i.e. slightly different wording to [`expected_specific_argument`](Self::expected_specific_argument).
+    pub(crate) fn expected_specific_argument_and_list(
+        &self,
+        span: Span,
+        possibilities: &[Symbol],
+    ) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedSpecificArgument {
+                possibilities,
+                strings: false,
+                list: true,
+            },
+            attr_style: self.attr_style,
+        })
+    }
+
+    /// produces an error along the lines of `expected one of ["foo", "meow"]`
     pub(crate) fn expected_specific_argument_strings(
         &self,
         span: Span,
-        possibilities: Vec<&'static str>,
+        possibilities: &[Symbol],
     ) -> ErrorGuaranteed {
         self.emit_err(AttributeParseError {
             span,
@@ -469,7 +586,9 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
             reason: AttributeParseErrorReason::ExpectedSpecificArgument {
                 possibilities,
                 strings: true,
+                list: false,
             },
+            attr_style: self.attr_style,
         })
     }
 
@@ -505,7 +624,7 @@ pub struct SharedContext<'p, 'sess, S: Stage> {
     /// The id ([`NodeId`] if `S` is `Early`, [`HirId`] if `S` is `Late`) of the syntactical component this attribute was applied to
     pub(crate) target_id: S::Id,
 
-    emit_lint: &'p mut dyn FnMut(AttributeLint<S::Id>),
+    pub(crate) emit_lint: &'p mut dyn FnMut(AttributeLint<S::Id>),
 }
 
 /// Context given to every attribute parser during finalization.
@@ -558,343 +677,28 @@ pub enum OmitDoc {
     Skip,
 }
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug)]
 pub enum ShouldEmit {
+    /// The operations will emit errors, and lints, and errors are fatal.
+    ///
+    /// Only relevant when early parsing, in late parsing equivalent to `ErrorsAndLints`.
+    /// Late parsing is never fatal, and instead tries to emit as many diagnostics as possible.
+    EarlyFatal { also_emit_lints: bool },
     /// 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`.
+    /// Use this if you are *sure* that this operation will be called at a different time with `ShouldEmit::ErrorsAndLints`.
     Nothing,
 }
 
 impl ShouldEmit {
-    pub fn should_emit(&self) -> bool {
+    pub(crate) fn emit_err(&self, diag: Diag<'_>) -> ErrorGuaranteed {
         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: S,
-
-    /// *Only* parse attributes with this symbol.
-    ///
-    /// Used in cases where we want the lowering infrastructure for parse just a single attribute.
-    parse_only: Option<Symbol>,
-}
-
-impl<'sess> AttributeParser<'sess, Early> {
-    /// This method allows you to parse attributes *before* you have access to features or tools.
-    /// One example where this is necessary, is to parse `feature` attributes themselves for
-    /// example.
-    ///
-    /// Try to use this as little as possible. Attributes *should* be lowered during
-    /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
-    /// crash if you tried to do so through [`parse_limited`](Self::parse_limited).
-    ///
-    /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
-    /// that symbol are picked out of the list of instructions and parsed. Those are returned.
-    ///
-    /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while
-    /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed
-    /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors
-    pub fn parse_limited(
-        sess: &'sess Session,
-        attrs: &[ast::Attribute],
-        sym: Symbol,
-        target_span: Span,
-        target_node_id: NodeId,
-        features: Option<&'sess Features>,
-    ) -> Option<Attribute> {
-        let mut p = Self {
-            features,
-            tools: Vec::new(),
-            parse_only: Some(sym),
-            sess,
-            stage: Early { emit_errors: ShouldEmit::Nothing },
-        };
-        let mut parsed = p.parse_attribute_list(
-            attrs,
-            target_span,
-            target_node_id,
-            OmitDoc::Skip,
-            std::convert::identity,
-            |_lint| {
-                panic!("can't emit lints here for now (nothing uses this atm)");
-            },
-        );
-        assert!(parsed.len() <= 1);
-
-        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>,
-        stage: S,
-    ) -> Self {
-        Self { features: Some(features), tools, parse_only: None, sess, stage }
-    }
-
-    pub(crate) fn sess(&self) -> &'sess Session {
-        &self.sess
-    }
-
-    pub(crate) fn features(&self) -> &'sess Features {
-        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()
-    }
-
-    /// Parse a list of attributes.
-    ///
-    /// `target_span` is the span of the thing this list of attributes is applied to,
-    /// and when `omit_doc` is set, doc attributes are filtered out.
-    pub fn parse_attribute_list(
-        &mut self,
-        attrs: &[ast::Attribute],
-        target_span: Span,
-        target_id: S::Id,
-        omit_doc: OmitDoc,
-
-        lower_span: impl Copy + Fn(Span) -> Span,
-        mut emit_lint: impl FnMut(AttributeLint<S::Id>),
-    ) -> Vec<Attribute> {
-        let mut attributes = Vec::new();
-        let mut attr_paths = Vec::new();
-
-        for attr in attrs {
-            // If we're only looking for a single attribute, skip all the ones we don't care about.
-            if let Some(expected) = self.parse_only {
-                if !attr.has_name(expected) {
-                    continue;
-                }
-            }
-
-            // Sometimes, for example for `#![doc = include_str!("readme.md")]`,
-            // doc still contains a non-literal. You might say, when we're lowering attributes
-            // that's expanded right? But no, sometimes, when parsing attributes on macros,
-            // we already use the lowering logic and these are still there. So, when `omit_doc`
-            // is set we *also* want to ignore these.
-            if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) {
-                continue;
-            }
-
-            match &attr.kind {
-                ast::AttrKind::DocComment(comment_kind, symbol) => {
-                    if omit_doc == OmitDoc::Skip {
-                        continue;
-                    }
-
-                    attributes.push(Attribute::Parsed(AttributeKind::DocComment {
-                        style: attr.style,
-                        kind: *comment_kind,
-                        span: lower_span(attr.span),
-                        comment: *symbol,
-                    }))
-                }
-                // // FIXME: make doc attributes go through a proper attribute parser
-                // ast::AttrKind::Normal(n) if n.has_name(sym::doc) => {
-                //     let p = GenericMetaItemParser::from_attr(&n, self.dcx());
-                //
-                //     attributes.push(Attribute::Parsed(AttributeKind::DocComment {
-                //         style: attr.style,
-                //         kind: CommentKind::Line,
-                //         span: attr.span,
-                //         comment: p.args().name_value(),
-                //     }))
-                // }
-                ast::AttrKind::Normal(n) => {
-                    attr_paths.push(PathParser::Ast(&n.item.path));
-
-                    let parser = MetaItemParser::from_attr(n, self.dcx());
-                    let path = parser.path();
-                    let args = parser.args();
-                    let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
-
-                    if let Some(accepts) = S::parsers().0.get(parts.as_slice()) {
-                        for (template, accept) in accepts {
-                            let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
-                                shared: SharedContext {
-                                    cx: self,
-                                    target_span,
-                                    target_id,
-                                    emit_lint: &mut emit_lint,
-                                },
-                                attr_span: lower_span(attr.span),
-                                template,
-                                attr_path: path.get_attribute_path(),
-                            };
-
-                            accept(&mut cx, args)
-                        }
-                    } else {
-                        // If we're here, we must be compiling a tool attribute... Or someone
-                        // forgot to parse their fancy new attribute. Let's warn them in any case.
-                        // If you are that person, and you really think your attribute should
-                        // remain unparsed, carefully read the documentation in this module and if
-                        // you still think so you can add an exception to this assertion.
-
-                        // FIXME(jdonszelmann): convert other attributes, and check with this that
-                        // we caught em all
-                        // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg];
-                        // assert!(
-                        //     self.tools.contains(&parts[0]) || true,
-                        //     // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]),
-                        //     "attribute {path} wasn't parsed and isn't a know tool attribute",
-                        // );
-
-                        attributes.push(Attribute::Unparsed(Box::new(AttrItem {
-                            path: AttrPath::from_ast(&n.item.path),
-                            args: self.lower_attr_args(&n.item.args, lower_span),
-                            id: HashIgnoredAttrId { attr_id: attr.id },
-                            style: attr.style,
-                            span: lower_span(attr.span),
-                        })));
-                    }
-                }
-            }
+            ShouldEmit::EarlyFatal { .. } if diag.level() == Level::DelayedBug => diag.emit(),
+            ShouldEmit::EarlyFatal { .. } => diag.upgrade_to_fatal().emit(),
+            ShouldEmit::ErrorsAndLints => diag.emit(),
+            ShouldEmit::Nothing => diag.delay_as_bug(),
         }
-
-        let mut parsed_attributes = Vec::new();
-        for f in &S::parsers().1 {
-            if let Some(attr) = f(&mut FinalizeContext {
-                shared: SharedContext {
-                    cx: self,
-                    target_span,
-                    target_id,
-                    emit_lint: &mut emit_lint,
-                },
-                all_attrs: &attr_paths,
-            }) {
-                parsed_attributes.push(Attribute::Parsed(attr));
-            }
-        }
-
-        attributes.extend(parsed_attributes);
-
-        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,
-            ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()),
-            // This is an inert key-value attribute - it will never be visible to macros
-            // after it gets lowered to HIR. Therefore, we can extract literals to handle
-            // nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
-            ast::AttrArgs::Eq { eq_span, expr } => {
-                // In valid code the value always ends up as a single literal. Otherwise, a dummy
-                // literal suffices because the error is handled elsewhere.
-                let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
-                    && let Ok(lit) =
-                        ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span))
-                {
-                    lit
-                } else {
-                    let guar = self.dcx().span_delayed_bug(
-                        args.span().unwrap_or(DUMMY_SP),
-                        "expr in place where literal is expected (builtin attr parsing)",
-                    );
-                    ast::MetaItemLit {
-                        symbol: sym::dummy,
-                        suffix: None,
-                        kind: ast::LitKind::Err(guar),
-                        span: DUMMY_SP,
-                    }
-                };
-                AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit }
-            }
-        }
-    }
-}
-
-/// 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/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs
new file mode 100644
index 00000000000..8f2de4af14e
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/interface.rs
@@ -0,0 +1,362 @@
+use std::borrow::Cow;
+
+use rustc_ast as ast;
+use rustc_ast::NodeId;
+use rustc_errors::DiagCtxtHandle;
+use rustc_feature::{AttributeTemplate, Features};
+use rustc_hir::attrs::AttributeKind;
+use rustc_hir::lints::AttributeLint;
+use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target};
+use rustc_session::Session;
+use rustc_span::{DUMMY_SP, Span, Symbol, sym};
+
+use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage};
+use crate::parser::{ArgParser, MetaItemParser, PathParser};
+use crate::{Early, Late, OmitDoc, ShouldEmit};
+
+/// 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>,
+    pub(crate) features: Option<&'sess Features>,
+    pub(crate) sess: &'sess Session,
+    pub(crate) stage: S,
+
+    /// *Only* parse attributes with this symbol.
+    ///
+    /// Used in cases where we want the lowering infrastructure for parse just a single attribute.
+    parse_only: Option<Symbol>,
+}
+
+impl<'sess> AttributeParser<'sess, Early> {
+    /// This method allows you to parse attributes *before* you have access to features or tools.
+    /// One example where this is necessary, is to parse `feature` attributes themselves for
+    /// example.
+    ///
+    /// Try to use this as little as possible. Attributes *should* be lowered during
+    /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
+    /// crash if you tried to do so through [`parse_limited`](Self::parse_limited).
+    ///
+    /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
+    /// that symbol are picked out of the list of instructions and parsed. Those are returned.
+    ///
+    /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while
+    /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed
+    /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors
+    pub fn parse_limited(
+        sess: &'sess Session,
+        attrs: &[ast::Attribute],
+        sym: Symbol,
+        target_span: Span,
+        target_node_id: NodeId,
+        features: Option<&'sess Features>,
+    ) -> Option<Attribute> {
+        Self::parse_limited_should_emit(
+            sess,
+            attrs,
+            sym,
+            target_span,
+            target_node_id,
+            features,
+            ShouldEmit::Nothing,
+        )
+    }
+
+    /// Usually you want `parse_limited`, which defaults to no errors.
+    pub fn parse_limited_should_emit(
+        sess: &'sess Session,
+        attrs: &[ast::Attribute],
+        sym: Symbol,
+        target_span: Span,
+        target_node_id: NodeId,
+        features: Option<&'sess Features>,
+        should_emit: ShouldEmit,
+    ) -> Option<Attribute> {
+        let mut parsed = Self::parse_limited_all(
+            sess,
+            attrs,
+            Some(sym),
+            Target::Crate, // Does not matter, we're not going to emit errors anyways
+            target_span,
+            target_node_id,
+            features,
+            should_emit,
+        );
+        assert!(parsed.len() <= 1);
+        parsed.pop()
+    }
+
+    pub fn parse_limited_all(
+        sess: &'sess Session,
+        attrs: &[ast::Attribute],
+        parse_only: Option<Symbol>,
+        target: Target,
+        target_span: Span,
+        target_node_id: NodeId,
+        features: Option<&'sess Features>,
+        emit_errors: ShouldEmit,
+    ) -> Vec<Attribute> {
+        let mut p =
+            Self { features, tools: Vec::new(), parse_only, sess, stage: Early { emit_errors } };
+        p.parse_attribute_list(
+            attrs,
+            target_span,
+            target_node_id,
+            target,
+            OmitDoc::Skip,
+            std::convert::identity,
+            |lint| {
+                crate::lints::emit_attribute_lint(&lint, sess);
+            },
+        )
+    }
+
+    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<'_>) -> Option<T>,
+        template: &AttributeTemplate,
+    ) -> Option<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 parts =
+            normal_attr.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
+        let meta_parser = MetaItemParser::from_attr(normal_attr, &parts, &sess.psess, emit_errors)?;
+        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| {
+                    crate::lints::emit_attribute_lint(&lint, sess);
+                },
+            },
+            attr_span: attr.span,
+            attr_style: attr.style,
+            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>,
+        stage: S,
+    ) -> Self {
+        Self { features: Some(features), tools, parse_only: None, sess, stage }
+    }
+
+    pub(crate) fn sess(&self) -> &'sess Session {
+        &self.sess
+    }
+
+    pub(crate) fn features(&self) -> &'sess Features {
+        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()
+    }
+
+    /// Parse a list of attributes.
+    ///
+    /// `target_span` is the span of the thing this list of attributes is applied to,
+    /// and when `omit_doc` is set, doc attributes are filtered out.
+    pub fn parse_attribute_list(
+        &mut self,
+        attrs: &[ast::Attribute],
+        target_span: Span,
+        target_id: S::Id,
+        target: Target,
+        omit_doc: OmitDoc,
+
+        lower_span: impl Copy + Fn(Span) -> Span,
+        mut emit_lint: impl FnMut(AttributeLint<S::Id>),
+    ) -> Vec<Attribute> {
+        let mut attributes = Vec::new();
+        let mut attr_paths = Vec::new();
+
+        for attr in attrs {
+            // If we're only looking for a single attribute, skip all the ones we don't care about.
+            if let Some(expected) = self.parse_only {
+                if !attr.has_name(expected) {
+                    continue;
+                }
+            }
+
+            // Sometimes, for example for `#![doc = include_str!("readme.md")]`,
+            // doc still contains a non-literal. You might say, when we're lowering attributes
+            // that's expanded right? But no, sometimes, when parsing attributes on macros,
+            // we already use the lowering logic and these are still there. So, when `omit_doc`
+            // is set we *also* want to ignore these.
+            if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) {
+                continue;
+            }
+
+            match &attr.kind {
+                ast::AttrKind::DocComment(comment_kind, symbol) => {
+                    if omit_doc == OmitDoc::Skip {
+                        continue;
+                    }
+
+                    attributes.push(Attribute::Parsed(AttributeKind::DocComment {
+                        style: attr.style,
+                        kind: *comment_kind,
+                        span: lower_span(attr.span),
+                        comment: *symbol,
+                    }))
+                }
+                // // FIXME: make doc attributes go through a proper attribute parser
+                // ast::AttrKind::Normal(n) if n.has_name(sym::doc) => {
+                //     let p = GenericMetaItemParser::from_attr(&n, self.dcx());
+                //
+                //     attributes.push(Attribute::Parsed(AttributeKind::DocComment {
+                //         style: attr.style,
+                //         kind: CommentKind::Line,
+                //         span: attr.span,
+                //         comment: p.args().name_value(),
+                //     }))
+                // }
+                ast::AttrKind::Normal(n) => {
+                    attr_paths.push(PathParser(Cow::Borrowed(&n.item.path)));
+
+                    let parts =
+                        n.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
+
+                    if let Some(accepts) = S::parsers().accepters.get(parts.as_slice()) {
+                        let Some(parser) = MetaItemParser::from_attr(
+                            n,
+                            &parts,
+                            &self.sess.psess,
+                            self.stage.should_emit(),
+                        ) else {
+                            continue;
+                        };
+                        let path = parser.path();
+                        let args = parser.args();
+                        for accept in accepts {
+                            let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
+                                shared: SharedContext {
+                                    cx: self,
+                                    target_span,
+                                    target_id,
+                                    emit_lint: &mut emit_lint,
+                                },
+                                attr_span: lower_span(attr.span),
+                                attr_style: attr.style,
+                                template: &accept.template,
+                                attr_path: path.get_attribute_path(),
+                            };
+
+                            (accept.accept_fn)(&mut cx, args);
+                            if !matches!(cx.stage.should_emit(), ShouldEmit::Nothing) {
+                                Self::check_target(&accept.allowed_targets, target, &mut cx);
+                            }
+                        }
+                    } else {
+                        // If we're here, we must be compiling a tool attribute... Or someone
+                        // forgot to parse their fancy new attribute. Let's warn them in any case.
+                        // If you are that person, and you really think your attribute should
+                        // remain unparsed, carefully read the documentation in this module and if
+                        // you still think so you can add an exception to this assertion.
+
+                        // FIXME(jdonszelmann): convert other attributes, and check with this that
+                        // we caught em all
+                        // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg];
+                        // assert!(
+                        //     self.tools.contains(&parts[0]) || true,
+                        //     // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]),
+                        //     "attribute {path} wasn't parsed and isn't a know tool attribute",
+                        // );
+
+                        attributes.push(Attribute::Unparsed(Box::new(AttrItem {
+                            path: AttrPath::from_ast(&n.item.path),
+                            args: self.lower_attr_args(&n.item.args, lower_span),
+                            id: HashIgnoredAttrId { attr_id: attr.id },
+                            style: attr.style,
+                            span: lower_span(attr.span),
+                        })));
+                    }
+                }
+            }
+        }
+
+        let mut parsed_attributes = Vec::new();
+        for f in &S::parsers().finalizers {
+            if let Some(attr) = f(&mut FinalizeContext {
+                shared: SharedContext {
+                    cx: self,
+                    target_span,
+                    target_id,
+                    emit_lint: &mut emit_lint,
+                },
+                all_attrs: &attr_paths,
+            }) {
+                parsed_attributes.push(Attribute::Parsed(attr));
+            }
+        }
+
+        attributes.extend(parsed_attributes);
+
+        attributes
+    }
+
+    /// Returns whether there is a parser for an attribute with this name
+    pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
+        Late::parsers().accepters.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,
+            ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()),
+            // This is an inert key-value attribute - it will never be visible to macros
+            // after it gets lowered to HIR. Therefore, we can extract literals to handle
+            // nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
+            ast::AttrArgs::Eq { eq_span, expr } => {
+                // In valid code the value always ends up as a single literal. Otherwise, a dummy
+                // literal suffices because the error is handled elsewhere.
+                let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
+                    && let Ok(lit) =
+                        ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span))
+                {
+                    lit
+                } else {
+                    let guar = self.dcx().span_delayed_bug(
+                        args.span().unwrap_or(DUMMY_SP),
+                        "expr in place where literal is expected (builtin attr parsing)",
+                    );
+                    ast::MetaItemLit {
+                        symbol: sym::dummy,
+                        suffix: None,
+                        kind: ast::LitKind::Err(guar),
+                        span: DUMMY_SP,
+                    }
+                };
+                AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs
index dc54cb6b840..f51cc8c4e8b 100644
--- a/compiler/rustc_attr_parsing/src/lib.rs
+++ b/compiler/rustc_attr_parsing/src/lib.rs
@@ -1,13 +1,13 @@
 //! Centralized logic for parsing and attributes.
 //!
 //! ## Architecture
-//! This crate is part of a series of crates that handle attribute processing.
-//! - [rustc_attr_data_structures](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_data_structures/index.html): Defines the data structures that store parsed attributes
+//! This crate is part of a series of crates and modules that handle attribute processing.
+//! - [rustc_hir::attrs](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/index.html): Defines the data structures that store parsed attributes
 //! - [rustc_attr_parsing](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_parsing/index.html): This crate, handles the parsing of attributes
-//! - (planned) rustc_attr_validation: Will handle attribute validation
+//! - (planned) rustc_attr_validation: Will handle attribute validation, logic currently handled in `rustc_passes`
 //!
 //! The separation between data structures and parsing follows the principle of separation of concerns.
-//! Data structures (`rustc_attr_data_structures`) define what attributes look like after parsing.
+//! Data structures (`rustc_hir::attrs`) define what attributes look like after parsing.
 //! This crate (`rustc_attr_parsing`) handles how to convert raw tokens into those structures.
 //! This split allows other parts of the compiler to use the data structures without needing
 //! the parsing logic, making the codebase more modular and maintainable.
@@ -62,7 +62,7 @@
 //! a "stability" of an item. So, the stability attribute has an
 //! [`AttributeParser`](attributes::AttributeParser) that recognizes both the `#[stable()]`
 //! and `#[unstable()]` syntactic attributes, and at the end produce a single
-//! [`AttributeKind::Stability`](rustc_attr_data_structures::AttributeKind::Stability).
+//! [`AttributeKind::Stability`](rustc_hir::attrs::AttributeKind::Stability).
 //!
 //! When multiple instances of the same attribute are allowed, they're combined into a single
 //! semantic attribute. For example:
@@ -79,23 +79,37 @@
 // tidy-alphabetical-start
 #![allow(internal_features)]
 #![doc(rust_logo)]
+#![feature(decl_macro)]
 #![feature(rustdoc_internals)]
 #![recursion_limit = "256"]
 // tidy-alphabetical-end
 
 #[macro_use]
+/// All the individual attribute parsers for each of rustc's built-in attributes.
 mod attributes;
+
+/// All the important types given to attribute parsers when parsing
 pub(crate) mod context;
-mod lints;
+
+/// Code that other crates interact with, to actually parse a list (or sometimes single)
+/// attribute.
+mod interface;
+
+/// Despite this entire module called attribute parsing and the term being a little overloaded,
+/// in this module the code lives that actually breaks up tokenstreams into semantic pieces of attributes,
+/// like lists or name-value pairs.
 pub mod parser;
+
+mod lints;
 mod session_diagnostics;
+mod target_checking;
+pub mod validate_attr;
 
 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, ShouldEmit};
+pub use attributes::util::{is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version};
+pub use context::{Early, Late, OmitDoc, ShouldEmit};
+pub use interface::AttributeParser;
 pub use lints::emit_attribute_lint;
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
diff --git a/compiler/rustc_attr_parsing/src/lints.rs b/compiler/rustc_attr_parsing/src/lints.rs
index e648ca4fdf8..b1a971eec32 100644
--- a/compiler/rustc_attr_parsing/src/lints.rs
+++ b/compiler/rustc_attr_parsing/src/lints.rs
@@ -1,10 +1,13 @@
-use rustc_attr_data_structures::lints::{AttributeLint, AttributeLintKind};
+use std::borrow::Cow;
+
 use rustc_errors::{DiagArgValue, LintEmitter};
-use rustc_hir::HirId;
+use rustc_hir::Target;
+use rustc_hir::lints::{AttributeLint, AttributeLintKind};
+use rustc_span::sym;
 
 use crate::session_diagnostics;
 
-pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emitter: L) {
+pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<L::Id>, lint_emitter: L) {
     let AttributeLint { id, span, kind } = lint;
 
     match kind {
@@ -34,5 +37,48 @@ pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emi
             *first_span,
             session_diagnostics::EmptyAttributeList { attr_span: *first_span },
         ),
+        AttributeLintKind::InvalidTarget { name, target, applied, only } => lint_emitter
+            .emit_node_span_lint(
+                // This check is here because `deprecated` had its own lint group and removing this would be a breaking change
+                if name.segments[0].name == sym::deprecated
+                    && ![
+                        Target::Closure,
+                        Target::Expression,
+                        Target::Statement,
+                        Target::Arm,
+                        Target::MacroCall,
+                    ]
+                    .contains(target)
+                {
+                    rustc_session::lint::builtin::USELESS_DEPRECATED
+                } else {
+                    rustc_session::lint::builtin::UNUSED_ATTRIBUTES
+                },
+                *id,
+                *span,
+                session_diagnostics::InvalidTargetLint {
+                    name: name.clone(),
+                    target: target.plural_name(),
+                    applied: DiagArgValue::StrListSepByAnd(
+                        applied.into_iter().map(|i| Cow::Owned(i.to_string())).collect(),
+                    ),
+                    only,
+                    attr_span: *span,
+                },
+            ),
+
+        &AttributeLintKind::InvalidStyle { ref name, is_used_as_inner, target, target_span } => {
+            lint_emitter.emit_node_span_lint(
+                rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
+                *id,
+                *span,
+                session_diagnostics::InvalidAttrStyle {
+                    name: name.clone(),
+                    is_used_as_inner,
+                    target_span: (!is_used_as_inner).then_some(target_span),
+                    target,
+                },
+            )
+        }
     }
 }
diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs
index aecaae947c9..4f903594225 100644
--- a/compiler/rustc_attr_parsing/src/parser.rs
+++ b/compiler/rustc_attr_parsing/src/parser.rs
@@ -3,45 +3,30 @@
 //!
 //! FIXME(jdonszelmann): delete `rustc_ast/attr/mod.rs`
 
+use std::borrow::Cow;
 use std::fmt::{Debug, Display};
-use std::iter::Peekable;
 
-use rustc_ast::token::{self, Delimiter, Token};
-use rustc_ast::tokenstream::{TokenStreamIter, TokenTree};
+use rustc_ast::token::{self, Delimiter, MetaVarKind};
+use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::{AttrArgs, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
 use rustc_ast_pretty::pprust;
-use rustc_errors::DiagCtxtHandle;
+use rustc_errors::{Diag, PResult};
 use rustc_hir::{self as hir, AttrPath};
-use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym};
-
-pub struct SegmentIterator<'a> {
-    offset: usize,
-    path: &'a PathParser<'a>,
-}
-
-impl<'a> Iterator for SegmentIterator<'a> {
-    type Item = &'a Ident;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.offset >= self.path.len() {
-            return None;
-        }
-
-        let res = match self.path {
-            PathParser::Ast(ast_path) => &ast_path.segments[self.offset].ident,
-            PathParser::Attr(attr_path) => &attr_path.segments[self.offset],
-        };
-
-        self.offset += 1;
-        Some(res)
-    }
-}
+use rustc_parse::exp;
+use rustc_parse::parser::{Parser, PathStyle, token_descr};
+use rustc_session::errors::{create_lit_error, report_lit_error};
+use rustc_session::parse::ParseSess;
+use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym};
+use thin_vec::ThinVec;
+
+use crate::ShouldEmit;
+use crate::session_diagnostics::{
+    InvalidMetaItem, InvalidMetaItemQuoteIdentSugg, InvalidMetaItemRemoveNegSugg, MetaBadDelim,
+    MetaBadDelimSugg, SuffixedLiteralInAttribute,
+};
 
 #[derive(Clone, Debug)]
-pub enum PathParser<'a> {
-    Ast(&'a Path),
-    Attr(AttrPath),
-}
+pub struct PathParser<'a>(pub Cow<'a, Path>);
 
 impl<'a> PathParser<'a> {
     pub fn get_attribute_path(&self) -> hir::AttrPath {
@@ -52,21 +37,15 @@ impl<'a> PathParser<'a> {
     }
 
     pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> {
-        SegmentIterator { offset: 0, path: self }
+        self.0.segments.iter().map(|seg| &seg.ident)
     }
 
     pub fn span(&self) -> Span {
-        match self {
-            PathParser::Ast(path) => path.span,
-            PathParser::Attr(attr_path) => attr_path.span,
-        }
+        self.0.span
     }
 
     pub fn len(&self) -> usize {
-        match self {
-            PathParser::Ast(path) => path.segments.len(),
-            PathParser::Attr(attr_path) => attr_path.segments.len(),
-        }
+        self.0.segments.len()
     }
 
     pub fn segments_is(&self, segments: &[Symbol]) -> bool {
@@ -99,10 +78,7 @@ impl<'a> PathParser<'a> {
 
 impl Display for PathParser<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            PathParser::Ast(path) => write!(f, "{}", pprust::path_to_string(path)),
-            PathParser::Attr(attr_path) => write!(f, "{attr_path}"),
-        }
+        write!(f, "{}", pprust::path_to_string(&self.0))
     }
 }
 
@@ -123,21 +99,39 @@ impl<'a> ArgParser<'a> {
         }
     }
 
-    pub fn from_attr_args<'sess>(value: &'a AttrArgs, dcx: DiagCtxtHandle<'sess>) -> Self {
-        match value {
+    pub fn from_attr_args<'sess>(
+        value: &'a AttrArgs,
+        parts: &[Symbol],
+        psess: &'sess ParseSess,
+        should_emit: ShouldEmit,
+    ) -> Option<Self> {
+        Some(match value {
             AttrArgs::Empty => Self::NoArgs,
-            AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => {
-                Self::List(MetaItemListParser::new(args, dcx))
-            }
             AttrArgs::Delimited(args) => {
-                Self::List(MetaItemListParser { sub_parsers: vec![], span: args.dspan.entire() })
+                // The arguments of rustc_dummy are not validated if the arguments are delimited
+                if parts == &[sym::rustc_dummy] {
+                    return Some(ArgParser::List(MetaItemListParser {
+                        sub_parsers: ThinVec::new(),
+                        span: args.dspan.entire(),
+                    }));
+                }
+
+                if args.delim != Delimiter::Parenthesis {
+                    psess.dcx().emit_err(MetaBadDelim {
+                        span: args.dspan.entire(),
+                        sugg: MetaBadDelimSugg { open: args.dspan.open, close: args.dspan.close },
+                    });
+                    return None;
+                }
+
+                Self::List(MetaItemListParser::new(args, psess, should_emit)?)
             }
             AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser {
                 eq_span: *eq_span,
-                value: expr_to_lit(dcx, &expr, *eq_span),
+                value: expr_to_lit(psess, &expr, expr.span, should_emit)?,
                 value_span: expr.span,
             }),
-        }
+        })
     }
 
     /// Asserts that this MetaItem is a list
@@ -249,11 +243,16 @@ impl<'a> Debug for MetaItemParser<'a> {
 impl<'a> MetaItemParser<'a> {
     /// Create a new parser from a [`NormalAttr`], which is stored inside of any
     /// [`ast::Attribute`](rustc_ast::Attribute)
-    pub fn from_attr<'sess>(attr: &'a NormalAttr, dcx: DiagCtxtHandle<'sess>) -> Self {
-        Self {
-            path: PathParser::Ast(&attr.item.path),
-            args: ArgParser::from_attr_args(&attr.item.args, dcx),
-        }
+    pub fn from_attr<'sess>(
+        attr: &'a NormalAttr,
+        parts: &[Symbol],
+        psess: &'sess ParseSess,
+        should_emit: ShouldEmit,
+    ) -> Option<Self> {
+        Some(Self {
+            path: PathParser(Cow::Borrowed(&attr.item.path)),
+            args: ArgParser::from_attr_args(&attr.item.args, parts, psess, should_emit)?,
+        })
     }
 }
 
@@ -318,215 +317,248 @@ impl NameValueParser {
     }
 }
 
-fn expr_to_lit(dcx: DiagCtxtHandle<'_>, expr: &Expr, span: Span) -> MetaItemLit {
-    // In valid code the value always ends up as a single literal. Otherwise, a dummy
-    // literal suffices because the error is handled elsewhere.
-    if let ExprKind::Lit(token_lit) = expr.kind
-        && let Ok(lit) = MetaItemLit::from_token_lit(token_lit, expr.span)
-    {
-        lit
+fn expr_to_lit(
+    psess: &ParseSess,
+    expr: &Expr,
+    span: Span,
+    should_emit: ShouldEmit,
+) -> Option<MetaItemLit> {
+    if let ExprKind::Lit(token_lit) = expr.kind {
+        let res = MetaItemLit::from_token_lit(token_lit, expr.span);
+        match res {
+            Ok(lit) => {
+                if token_lit.suffix.is_some() {
+                    should_emit.emit_err(
+                        psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
+                    );
+                    None
+                } else {
+                    if !lit.kind.is_unsuffixed() {
+                        // Emit error and continue, we can still parse the attribute as if the suffix isn't there
+                        should_emit.emit_err(
+                            psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
+                        );
+                    }
+
+                    Some(lit)
+                }
+            }
+            Err(err) => {
+                let guar = report_lit_error(psess, err, token_lit, expr.span);
+                let lit = MetaItemLit {
+                    symbol: token_lit.symbol,
+                    suffix: token_lit.suffix,
+                    kind: LitKind::Err(guar),
+                    span: expr.span,
+                };
+                Some(lit)
+            }
+        }
     } else {
-        let guar = dcx.span_delayed_bug(
-            span,
-            "expr in place where literal is expected (builtin attr parsing)",
-        );
-        MetaItemLit { symbol: sym::dummy, suffix: None, kind: LitKind::Err(guar), span }
+        if matches!(should_emit, ShouldEmit::Nothing) {
+            return None;
+        }
+
+        // Example cases:
+        // - `#[foo = 1+1]`: results in `ast::ExprKind::BinOp`.
+        // - `#[foo = include_str!("nonexistent-file.rs")]`:
+        //   results in `ast::ExprKind::Err`. In that case we delay
+        //   the error because an earlier error will have already
+        //   been reported.
+        let msg = "attribute value must be a literal";
+        let err = psess.dcx().struct_span_err(span, msg);
+        should_emit.emit_err(err);
+        None
     }
 }
 
 struct MetaItemListParserContext<'a, 'sess> {
-    // the tokens inside the delimiters, so `#[some::attr(a b c)]` would have `a b c` inside
-    inside_delimiters: Peekable<TokenStreamIter<'a>>,
-    dcx: DiagCtxtHandle<'sess>,
+    parser: &'a mut Parser<'sess>,
+    should_emit: ShouldEmit,
 }
 
 impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
-    fn done(&mut self) -> bool {
-        self.inside_delimiters.peek().is_none()
-    }
-
-    fn next_path(&mut self) -> Option<AttrPath> {
-        // FIXME: Share code with `parse_path`.
-        let tt = self.inside_delimiters.next().map(|tt| TokenTree::uninterpolate(tt));
-
-        match tt.as_deref()? {
-            &TokenTree::Token(
-                Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span },
-                _,
-            ) => {
-                // here we have either an ident or pathsep `::`.
-
-                let mut segments = if let &token::Ident(name, _) = kind {
-                    // when we lookahead another pathsep, more path's coming
-                    if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) =
-                        self.inside_delimiters.peek()
-                    {
-                        self.inside_delimiters.next();
-                        vec![Ident::new(name, span)]
-                    } else {
-                        // else we have a single identifier path, that's all
-                        return Some(AttrPath {
-                            segments: vec![Ident::new(name, span)].into_boxed_slice(),
-                            span,
-                        });
-                    }
-                } else {
-                    // if `::` is all we get, we just got a path root
-                    vec![Ident::new(kw::PathRoot, span)]
-                };
+    fn parse_unsuffixed_meta_item_lit(&mut self) -> PResult<'sess, MetaItemLit> {
+        let Some(token_lit) = self.parser.eat_token_lit() else { return Err(self.expected_lit()) };
+        self.unsuffixed_meta_item_from_lit(token_lit)
+    }
+
+    fn unsuffixed_meta_item_from_lit(
+        &mut self,
+        token_lit: token::Lit,
+    ) -> PResult<'sess, MetaItemLit> {
+        let lit = match MetaItemLit::from_token_lit(token_lit, self.parser.prev_token.span) {
+            Ok(lit) => lit,
+            Err(err) => {
+                return Err(create_lit_error(
+                    &self.parser.psess,
+                    err,
+                    token_lit,
+                    self.parser.prev_token_uninterpolated_span(),
+                ));
+            }
+        };
 
-                // one segment accepted. accept n more
-                loop {
-                    // another ident?
-                    if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) =
-                        self.inside_delimiters
-                            .next()
-                            .map(|tt| TokenTree::uninterpolate(tt))
-                            .as_deref()
-                    {
-                        segments.push(Ident::new(name, span));
-                    } else {
-                        return None;
-                    }
-                    // stop unless we see another `::`
-                    if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) =
-                        self.inside_delimiters.peek()
-                    {
-                        self.inside_delimiters.next();
+        if !lit.kind.is_unsuffixed() {
+            // Emit error and continue, we can still parse the attribute as if the suffix isn't there
+            self.should_emit.emit_err(
+                self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
+            );
+        }
+
+        Ok(lit)
+    }
+
+    fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser<'static>> {
+        if let Some(MetaVarKind::Meta { has_meta_form }) = self.parser.token.is_metavar_seq() {
+            return if has_meta_form {
+                let attr_item = self
+                    .parser
+                    .eat_metavar_seq(MetaVarKind::Meta { has_meta_form: true }, |this| {
+                        MetaItemListParserContext { parser: this, should_emit: self.should_emit }
+                            .parse_attr_item()
+                    })
+                    .unwrap();
+                Ok(attr_item)
+            } else {
+                self.parser.unexpected_any()
+            };
+        }
+
+        let path = self.parser.parse_path(PathStyle::Mod)?;
+
+        // Check style of arguments that this meta item has
+        let args = if self.parser.check(exp!(OpenParen)) {
+            let start = self.parser.token.span;
+            let (sub_parsers, _) = self.parser.parse_paren_comma_seq(|parser| {
+                MetaItemListParserContext { parser, should_emit: self.should_emit }
+                    .parse_meta_item_inner()
+            })?;
+            let end = self.parser.prev_token.span;
+            ArgParser::List(MetaItemListParser { sub_parsers, span: start.with_hi(end.hi()) })
+        } else if self.parser.eat(exp!(Eq)) {
+            let eq_span = self.parser.prev_token.span;
+            let value = self.parse_unsuffixed_meta_item_lit()?;
+
+            ArgParser::NameValue(NameValueParser { eq_span, value, value_span: value.span })
+        } else {
+            ArgParser::NoArgs
+        };
+
+        Ok(MetaItemParser { path: PathParser(Cow::Owned(path)), args })
+    }
+
+    fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> {
+        if let Some(token_lit) = self.parser.eat_token_lit() {
+            // If a literal token is parsed, we commit to parsing a MetaItemLit for better errors
+            Ok(MetaItemOrLitParser::Lit(self.unsuffixed_meta_item_from_lit(token_lit)?))
+        } else {
+            let prev_pros = self.parser.approx_token_stream_pos();
+            match self.parse_attr_item() {
+                Ok(item) => Ok(MetaItemOrLitParser::MetaItemParser(item)),
+                Err(err) => {
+                    // If `parse_attr_item` made any progress, it likely has a more precise error we should prefer
+                    // If it didn't make progress we use the `expected_lit` from below
+                    if self.parser.approx_token_stream_pos() != prev_pros {
+                        Err(err)
                     } else {
-                        break;
+                        err.cancel();
+                        Err(self.expected_lit())
                     }
                 }
-                let span = span.with_hi(segments.last().unwrap().span.hi());
-                Some(AttrPath { segments: segments.into_boxed_slice(), span })
-            }
-            TokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => None,
-            _ => {
-                // malformed attributes can get here. We can't crash, but somewhere else should've
-                // already warned for this.
-                None
             }
         }
     }
 
-    fn value(&mut self) -> Option<MetaItemLit> {
-        match self.inside_delimiters.next() {
-            Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => {
-                MetaItemListParserContext {
-                    inside_delimiters: inner_tokens.iter().peekable(),
-                    dcx: self.dcx,
-                }
-                .value()
+    fn expected_lit(&mut self) -> Diag<'sess> {
+        let mut err = InvalidMetaItem {
+            span: self.parser.token.span,
+            descr: token_descr(&self.parser.token),
+            quote_ident_sugg: None,
+            remove_neg_sugg: None,
+        };
+
+        // Suggest quoting idents, e.g. in `#[cfg(key = value)]`. We don't use `Token::ident` and
+        // don't `uninterpolate` the token to avoid suggesting anything butchered or questionable
+        // when macro metavariables are involved.
+        if self.parser.prev_token == token::Eq
+            && let token::Ident(..) = self.parser.token.kind
+        {
+            let before = self.parser.token.span.shrink_to_lo();
+            while let token::Ident(..) = self.parser.token.kind {
+                self.parser.bump();
             }
-            Some(TokenTree::Token(token, _)) => MetaItemLit::from_token(token),
-            _ => None,
+            err.quote_ident_sugg = Some(InvalidMetaItemQuoteIdentSugg {
+                before,
+                after: self.parser.prev_token.span.shrink_to_hi(),
+            });
         }
-    }
 
-    /// parses one element on the inside of a list attribute like `#[my_attr( <insides> )]`
-    ///
-    /// parses a path followed be either:
-    /// 1. nothing (a word attr)
-    /// 2. a parenthesized list
-    /// 3. an equals sign and a literal (name-value)
-    ///
-    /// Can also parse *just* a literal. This is for cases like as `#[my_attr("literal")]`
-    /// where no path is given before the literal
-    ///
-    /// Some exceptions too for interpolated attributes which are already pre-processed
-    fn next(&mut self) -> Option<MetaItemOrLitParser<'a>> {
-        // a list element is either a literal
-        if let Some(TokenTree::Token(token, _)) = self.inside_delimiters.peek()
-            && let Some(lit) = MetaItemLit::from_token(token)
-        {
-            self.inside_delimiters.next();
-            return Some(MetaItemOrLitParser::Lit(lit));
-        } else if let Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) =
-            self.inside_delimiters.peek()
+        if self.parser.token == token::Minus
+            && self
+                .parser
+                .look_ahead(1, |t| matches!(t.kind, rustc_ast::token::TokenKind::Literal { .. }))
         {
-            self.inside_delimiters.next();
-            return MetaItemListParserContext {
-                inside_delimiters: inner_tokens.iter().peekable(),
-                dcx: self.dcx,
-            }
-            .next();
+            err.remove_neg_sugg =
+                Some(InvalidMetaItemRemoveNegSugg { negative_sign: self.parser.token.span });
+            self.parser.bump();
+            self.parser.bump();
         }
 
-        // or a path.
-        let path = self.next_path()?;
-
-        // Paths can be followed by:
-        // - `(more meta items)` (another list)
-        // - `= lit` (a name-value)
-        // - nothing
-        Some(MetaItemOrLitParser::MetaItemParser(match self.inside_delimiters.peek() {
-            Some(TokenTree::Delimited(dspan, _, Delimiter::Parenthesis, inner_tokens)) => {
-                self.inside_delimiters.next();
-
-                MetaItemParser {
-                    path: PathParser::Attr(path),
-                    args: ArgParser::List(MetaItemListParser::new_tts(
-                        inner_tokens.iter(),
-                        dspan.entire(),
-                        self.dcx,
-                    )),
-                }
-            }
-            Some(TokenTree::Delimited(_, ..)) => {
-                self.inside_delimiters.next();
-                // self.dcx.span_delayed_bug(span.entire(), "wrong delimiters");
-                return None;
-            }
-            Some(TokenTree::Token(Token { kind: token::Eq, span }, _)) => {
-                self.inside_delimiters.next();
-                let value = self.value()?;
-                MetaItemParser {
-                    path: PathParser::Attr(path),
-                    args: ArgParser::NameValue(NameValueParser {
-                        eq_span: *span,
-                        value_span: value.span,
-                        value,
-                    }),
-                }
-            }
-            _ => MetaItemParser { path: PathParser::Attr(path), args: ArgParser::NoArgs },
-        }))
+        self.parser.dcx().create_err(err)
     }
 
-    fn parse(mut self, span: Span) -> MetaItemListParser<'a> {
-        let mut sub_parsers = Vec::new();
+    fn parse(
+        tokens: TokenStream,
+        psess: &'sess ParseSess,
+        span: Span,
+        should_emit: ShouldEmit,
+    ) -> PResult<'sess, MetaItemListParser<'static>> {
+        let mut parser = Parser::new(psess, tokens, None);
+        let mut this = MetaItemListParserContext { parser: &mut parser, should_emit };
 
-        while !self.done() {
-            let Some(n) = self.next() else {
-                continue;
-            };
-            sub_parsers.push(n);
+        // Presumably, the majority of the time there will only be one attr.
+        let mut sub_parsers = ThinVec::with_capacity(1);
+        while this.parser.token != token::Eof {
+            sub_parsers.push(this.parse_meta_item_inner()?);
 
-            match self.inside_delimiters.peek() {
-                None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => {
-                    self.inside_delimiters.next();
-                }
-                Some(_) => {}
+            if !this.parser.eat(exp!(Comma)) {
+                break;
             }
         }
 
-        MetaItemListParser { sub_parsers, span }
+        if parser.token != token::Eof {
+            parser.unexpected()?;
+        }
+
+        Ok(MetaItemListParser { sub_parsers, span })
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct MetaItemListParser<'a> {
-    sub_parsers: Vec<MetaItemOrLitParser<'a>>,
+    sub_parsers: ThinVec<MetaItemOrLitParser<'a>>,
     pub span: Span,
 }
 
 impl<'a> MetaItemListParser<'a> {
-    fn new<'sess>(delim: &'a DelimArgs, dcx: DiagCtxtHandle<'sess>) -> Self {
-        MetaItemListParser::new_tts(delim.tokens.iter(), delim.dspan.entire(), dcx)
-    }
-
-    fn new_tts<'sess>(tts: TokenStreamIter<'a>, span: Span, dcx: DiagCtxtHandle<'sess>) -> Self {
-        MetaItemListParserContext { inside_delimiters: tts.peekable(), dcx }.parse(span)
+    fn new<'sess>(
+        delim: &'a DelimArgs,
+        psess: &'sess ParseSess,
+        should_emit: ShouldEmit,
+    ) -> Option<Self> {
+        match MetaItemListParserContext::parse(
+            delim.tokens.clone(),
+            psess,
+            delim.dspan.entire(),
+            should_emit,
+        ) {
+            Ok(s) => Some(s),
+            Err(e) => {
+                should_emit.emit_err(e);
+                None
+            }
+        }
     }
 
     /// Lets you pick and choose as what you want to parse each element in the list
diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
index 5b0bf0e6662..2c2b14c8a68 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -1,12 +1,12 @@
 use std::num::IntErrorKind;
 
-use rustc_ast as ast;
+use rustc_ast::{self as ast, AttrStyle, Path};
 use rustc_errors::codes::*;
 use rustc_errors::{
     Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
 };
 use rustc_feature::AttributeTemplate;
-use rustc_hir::AttrPath;
+use rustc_hir::{AttrPath, Target};
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_span::{Span, Symbol};
 
@@ -438,7 +438,7 @@ pub(crate) struct IllFormedAttributeInput {
 
 #[derive(Diagnostic)]
 #[diag(attr_parsing_ill_formed_attribute_input)]
-pub(crate) struct MustUseIllFormedAttributeInput {
+pub(crate) struct IllFormedAttributeInputLint {
     #[primary_span]
     pub span: Span,
     pub num_suggestions: usize,
@@ -460,6 +460,34 @@ pub(crate) struct NullOnLinkSection {
 }
 
 #[derive(Diagnostic)]
+#[diag(attr_parsing_null_on_objc_class)]
+pub(crate) struct NullOnObjcClass {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_null_on_objc_selector)]
+pub(crate) struct NullOnObjcSelector {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_objc_class_expected_string_literal)]
+pub(crate) struct ObjcClassExpectedStringLiteral {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_objc_selector_expected_string_literal)]
+pub(crate) struct ObjcSelectorExpectedStringLiteral {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(attr_parsing_stability_outside_std, code = E0734)]
 pub(crate) struct StabilityOutsideStd {
     #[primary_span]
@@ -480,6 +508,32 @@ pub(crate) struct EmptyAttributeList {
     pub attr_span: Span,
 }
 
+#[derive(LintDiagnostic)]
+#[diag(attr_parsing_invalid_target_lint)]
+#[warning]
+#[help]
+pub(crate) struct InvalidTargetLint {
+    pub name: AttrPath,
+    pub target: &'static str,
+    pub applied: DiagArgValue,
+    pub only: &'static str,
+    #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")]
+    pub attr_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[help]
+#[diag(attr_parsing_invalid_target)]
+pub(crate) struct InvalidTarget {
+    #[primary_span]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")]
+    pub span: Span,
+    pub name: AttrPath,
+    pub target: &'static str,
+    pub applied: DiagArgValue,
+    pub only: &'static str,
+}
+
 #[derive(Diagnostic)]
 #[diag(attr_parsing_invalid_alignment_value, code = E0589)]
 pub(crate) struct InvalidAlignmentValue {
@@ -498,6 +552,7 @@ pub(crate) struct ReprIdent {
 #[derive(Diagnostic)]
 #[diag(attr_parsing_unrecognized_repr_hint, code = E0552)]
 #[help]
+#[note]
 pub(crate) struct UnrecognizedReprHint {
     #[primary_span]
     pub span: Span,
@@ -531,9 +586,11 @@ pub(crate) struct LinkOrdinalOutOfRange {
     pub ordinal: u128,
 }
 
-pub(crate) enum AttributeParseErrorReason {
+pub(crate) enum AttributeParseErrorReason<'a> {
     ExpectedNoArgs,
-    ExpectedStringLiteral { byte_string: Option<Span> },
+    ExpectedStringLiteral {
+        byte_string: Option<Span>,
+    },
     ExpectedIntegerLiteral,
     ExpectedAtLeastOneArgument,
     ExpectedSingleArgument,
@@ -541,18 +598,25 @@ pub(crate) enum AttributeParseErrorReason {
     UnexpectedLiteral,
     ExpectedNameValue(Option<Symbol>),
     DuplicateKey(Symbol),
-    ExpectedSpecificArgument { possibilities: Vec<&'static str>, strings: bool },
+    ExpectedSpecificArgument {
+        possibilities: &'a [Symbol],
+        strings: bool,
+        /// Should we tell the user to write a list when they didn't?
+        list: bool,
+    },
+    ExpectedIdentifier,
 }
 
-pub(crate) struct AttributeParseError {
+pub(crate) struct AttributeParseError<'a> {
     pub(crate) span: Span,
     pub(crate) attr_span: Span,
+    pub(crate) attr_style: AttrStyle,
     pub(crate) template: AttributeTemplate,
     pub(crate) attribute: AttrPath,
-    pub(crate) reason: AttributeParseErrorReason,
+    pub(crate) reason: AttributeParseErrorReason<'a>,
 }
 
-impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
+impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
     fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
         let name = self.attribute.to_string();
 
@@ -593,11 +657,11 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                 diag.code(E0538);
             }
             AttributeParseErrorReason::UnexpectedLiteral => {
-                diag.span_label(self.span, format!("didn't expect a literal here"));
+                diag.span_label(self.span, "didn't expect a literal here");
                 diag.code(E0565);
             }
             AttributeParseErrorReason::ExpectedNoArgs => {
-                diag.span_label(self.span, format!("didn't expect any arguments here"));
+                diag.span_label(self.span, "didn't expect any arguments here");
                 diag.code(E0565);
             }
             AttributeParseErrorReason::ExpectedNameValue(None) => {
@@ -615,9 +679,13 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                     format!("expected this to be of the form `{name} = \"...\"`"),
                 );
             }
-            AttributeParseErrorReason::ExpectedSpecificArgument { possibilities, strings } => {
+            AttributeParseErrorReason::ExpectedSpecificArgument {
+                possibilities,
+                strings,
+                list: false,
+            } => {
                 let quote = if strings { '"' } else { '`' };
-                match possibilities.as_slice() {
+                match possibilities {
                     &[] => {}
                     &[x] => {
                         diag.span_label(
@@ -641,9 +709,48 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                     }
                 }
             }
+            AttributeParseErrorReason::ExpectedSpecificArgument {
+                possibilities,
+                strings,
+                list: true,
+            } => {
+                let quote = if strings { '"' } else { '`' };
+                match possibilities {
+                    &[] => {}
+                    &[x] => {
+                        diag.span_label(
+                            self.span,
+                            format!(
+                                "this attribute is only valid with {quote}{x}{quote} as an argument"
+                            ),
+                        );
+                    }
+                    [first, second] => {
+                        diag.span_label(self.span, format!("this attribute is only valid with either {quote}{first}{quote} or {quote}{second}{quote} as an argument"));
+                    }
+                    [first @ .., second_to_last, last] => {
+                        let mut res = String::new();
+                        for i in first {
+                            res.push_str(&format!("{quote}{i}{quote}, "));
+                        }
+                        res.push_str(&format!(
+                            "{quote}{second_to_last}{quote} or {quote}{last}{quote}"
+                        ));
+
+                        diag.span_label(self.span, format!("this attribute is only valid with one of the following arguments: {res}"));
+                    }
+                }
+            }
+            AttributeParseErrorReason::ExpectedIdentifier => {
+                diag.span_label(self.span, "expected a valid identifier here");
+            }
         }
 
-        let suggestions = self.template.suggestions(false, &name);
+        if let Some(link) = self.template.docs {
+            diag.note(format!("for more information, visit <{link}>"));
+        }
+        let suggestions = self.template.suggestions(self.attr_style, &name);
+
         diag.span_suggestions(
             self.attr_span,
             if suggestions.len() == 1 {
@@ -658,3 +765,206 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
         diag
     }
 }
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_invalid_attr_unsafe)]
+#[note]
+pub(crate) struct InvalidAttrUnsafe {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub name: Path,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_unsafe_attr_outside_unsafe)]
+pub(crate) struct UnsafeAttrOutsideUnsafe {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    #[subdiagnostic]
+    pub suggestion: UnsafeAttrOutsideUnsafeSuggestion,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    attr_parsing_unsafe_attr_outside_unsafe_suggestion,
+    applicability = "machine-applicable"
+)]
+pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion {
+    #[suggestion_part(code = "unsafe(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_meta_bad_delim)]
+pub(crate) struct MetaBadDelim {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sugg: MetaBadDelimSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    attr_parsing_meta_bad_delim_suggestion,
+    applicability = "machine-applicable"
+)]
+pub(crate) struct MetaBadDelimSugg {
+    #[suggestion_part(code = "(")]
+    pub open: Span,
+    #[suggestion_part(code = ")")]
+    pub close: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_invalid_meta_item)]
+pub(crate) struct InvalidMetaItem {
+    #[primary_span]
+    pub span: Span,
+    pub descr: String,
+    #[subdiagnostic]
+    pub quote_ident_sugg: Option<InvalidMetaItemQuoteIdentSugg>,
+    #[subdiagnostic]
+    pub remove_neg_sugg: Option<InvalidMetaItemRemoveNegSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(attr_parsing_quote_ident_sugg, applicability = "machine-applicable")]
+pub(crate) struct InvalidMetaItemQuoteIdentSugg {
+    #[suggestion_part(code = "\"")]
+    pub before: Span,
+    #[suggestion_part(code = "\"")]
+    pub after: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(attr_parsing_remove_neg_sugg, applicability = "machine-applicable")]
+pub(crate) struct InvalidMetaItemRemoveNegSugg {
+    #[suggestion_part(code = "")]
+    pub negative_sign: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_suffixed_literal_in_attribute)]
+#[help]
+pub(crate) struct SuffixedLiteralInAttribute {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(attr_parsing_invalid_style)]
+pub(crate) struct InvalidAttrStyle {
+    pub name: AttrPath,
+    pub is_used_as_inner: bool,
+    #[note]
+    pub target_span: Option<Span>,
+    pub target: Target,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_empty_link_name, code = E0454)]
+pub(crate) struct EmptyLinkName {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_link_framework_apple, code = E0455)]
+pub(crate) struct LinkFrameworkApple {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_incompatible_wasm_link)]
+pub(crate) struct IncompatibleWasmLink {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_link_requires_name, code = E0459)]
+pub(crate) struct LinkRequiresName {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_raw_dylib_no_nul)]
+pub(crate) struct RawDylibNoNul {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_raw_dylib_only_windows, code = E0455)]
+pub(crate) struct RawDylibOnlyWindows {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_invalid_link_modifier)]
+pub(crate) struct InvalidLinkModifier {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_multiple_modifiers)]
+pub(crate) struct MultipleModifiers {
+    #[primary_span]
+    pub span: Span,
+    pub modifier: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_import_name_type_x86)]
+pub(crate) struct ImportNameTypeX86 {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_bundle_needs_static)]
+pub(crate) struct BundleNeedsStatic {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_whole_archive_needs_static)]
+pub(crate) struct WholeArchiveNeedsStatic {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_as_needed_compatibility)]
+pub(crate) struct AsNeededCompatibility {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_import_name_type_raw)]
+pub(crate) struct ImportNameTypeRaw {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_limit_invalid)]
+pub(crate) struct LimitInvalid<'a> {
+    #[primary_span]
+    pub span: Span,
+    #[label]
+    pub value_span: Span,
+    pub error_str: &'a str,
+}
diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs
new file mode 100644
index 00000000000..c52253699b5
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/target_checking.rs
@@ -0,0 +1,278 @@
+use std::borrow::Cow;
+
+use rustc_ast::AttrStyle;
+use rustc_errors::DiagArgValue;
+use rustc_feature::Features;
+use rustc_hir::lints::AttributeLintKind;
+use rustc_hir::{MethodKind, Target};
+
+use crate::AttributeParser;
+use crate::context::{AcceptContext, Stage};
+use crate::session_diagnostics::InvalidTarget;
+
+#[derive(Debug)]
+pub(crate) enum AllowedTargets {
+    AllowList(&'static [Policy]),
+    AllowListWarnRest(&'static [Policy]),
+    /// Special, and not the same as `AllowList(&[Allow(Target::Crate)])`.
+    /// For crate-level attributes we emit a specific set of lints to warn
+    /// people about accidentally not using them on the crate.
+    /// Only use this for attributes that are *exclusively* valid at the crate level.
+    CrateLevel,
+}
+
+pub(crate) enum AllowedResult {
+    Allowed,
+    Warn,
+    Error,
+}
+
+impl AllowedTargets {
+    pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult {
+        match self {
+            AllowedTargets::AllowList(list) => {
+                if list.contains(&Policy::Allow(target)) {
+                    AllowedResult::Allowed
+                } else if list.contains(&Policy::Warn(target)) {
+                    AllowedResult::Warn
+                } else {
+                    AllowedResult::Error
+                }
+            }
+            AllowedTargets::AllowListWarnRest(list) => {
+                if list.contains(&Policy::Allow(target)) {
+                    AllowedResult::Allowed
+                } else if list.contains(&Policy::Error(target)) {
+                    AllowedResult::Error
+                } else {
+                    AllowedResult::Warn
+                }
+            }
+            AllowedTargets::CrateLevel => AllowedResult::Allowed,
+        }
+    }
+
+    pub(crate) fn allowed_targets(&self) -> Vec<Target> {
+        match self {
+            AllowedTargets::AllowList(list) => list,
+            AllowedTargets::AllowListWarnRest(list) => list,
+            AllowedTargets::CrateLevel => ALL_TARGETS,
+        }
+        .iter()
+        .filter_map(|target| match target {
+            Policy::Allow(target) => Some(*target),
+            Policy::Warn(_) => None,
+            Policy::Error(_) => None,
+        })
+        .collect()
+    }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub(crate) enum Policy {
+    Allow(Target),
+    Warn(Target),
+    Error(Target),
+}
+
+impl<'sess, S: Stage> AttributeParser<'sess, S> {
+    pub(crate) fn check_target(
+        allowed_targets: &AllowedTargets,
+        target: Target,
+        cx: &mut AcceptContext<'_, 'sess, S>,
+    ) {
+        Self::check_type(matches!(allowed_targets, AllowedTargets::CrateLevel), target, cx);
+
+        match allowed_targets.is_allowed(target) {
+            AllowedResult::Allowed => {}
+            AllowedResult::Warn => {
+                let allowed_targets = allowed_targets.allowed_targets();
+                let (applied, only) = allowed_targets_applied(allowed_targets, target, cx.features);
+                let name = cx.attr_path.clone();
+                let attr_span = cx.attr_span;
+                cx.emit_lint(
+                    AttributeLintKind::InvalidTarget {
+                        name,
+                        target,
+                        only: if only { "only " } else { "" },
+                        applied,
+                    },
+                    attr_span,
+                );
+            }
+            AllowedResult::Error => {
+                let allowed_targets = allowed_targets.allowed_targets();
+                let (applied, only) = allowed_targets_applied(allowed_targets, target, cx.features);
+                let name = cx.attr_path.clone();
+                cx.dcx().emit_err(InvalidTarget {
+                    span: cx.attr_span.clone(),
+                    name,
+                    target: target.plural_name(),
+                    only: if only { "only " } else { "" },
+                    applied: DiagArgValue::StrListSepByAnd(
+                        applied.into_iter().map(Cow::Owned).collect(),
+                    ),
+                });
+            }
+        }
+    }
+
+    pub(crate) fn check_type(
+        crate_level: bool,
+        target: Target,
+        cx: &mut AcceptContext<'_, 'sess, S>,
+    ) {
+        let is_crate_root = S::id_is_crate_root(cx.target_id);
+
+        if is_crate_root {
+            return;
+        }
+
+        if !crate_level {
+            return;
+        }
+
+        let lint = AttributeLintKind::InvalidStyle {
+            name: cx.attr_path.clone(),
+            is_used_as_inner: cx.attr_style == AttrStyle::Inner,
+            target,
+            target_span: cx.target_span,
+        };
+        let attr_span = cx.attr_span;
+
+        cx.emit_lint(lint, attr_span);
+    }
+}
+
+/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
+/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
+pub(crate) fn allowed_targets_applied(
+    mut allowed_targets: Vec<Target>,
+    target: Target,
+    features: Option<&Features>,
+) -> (Vec<String>, bool) {
+    // Remove unstable targets from `allowed_targets` if their features are not enabled
+    if let Some(features) = features {
+        if !features.fn_delegation() {
+            allowed_targets.retain(|t| !matches!(t, Target::Delegation { .. }));
+        }
+        if !features.stmt_expr_attributes() {
+            allowed_targets.retain(|t| !matches!(t, Target::Expression | Target::Statement));
+        }
+        if !features.extern_types() {
+            allowed_targets.retain(|t| !matches!(t, Target::ForeignTy));
+        }
+    }
+
+    // We define groups of "similar" targets.
+    // If at least two of the targets are allowed, and the `target` is not in the group,
+    // we collapse the entire group to a single entry to simplify the target list
+    const FUNCTION_LIKE: &[Target] = &[
+        Target::Fn,
+        Target::Closure,
+        Target::ForeignFn,
+        Target::Method(MethodKind::Inherent),
+        Target::Method(MethodKind::Trait { body: false }),
+        Target::Method(MethodKind::Trait { body: true }),
+        Target::Method(MethodKind::TraitImpl),
+    ];
+    const METHOD_LIKE: &[Target] = &[
+        Target::Method(MethodKind::Inherent),
+        Target::Method(MethodKind::Trait { body: false }),
+        Target::Method(MethodKind::Trait { body: true }),
+        Target::Method(MethodKind::TraitImpl),
+    ];
+    const IMPL_LIKE: &[Target] =
+        &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }];
+    const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum];
+
+    let mut added_fake_targets = Vec::new();
+    filter_targets(
+        &mut allowed_targets,
+        FUNCTION_LIKE,
+        "functions",
+        target,
+        &mut added_fake_targets,
+    );
+    filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets);
+    filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets);
+    filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets);
+
+    // If there is now only 1 target left, show that as the only possible target
+    (
+        added_fake_targets
+            .iter()
+            .copied()
+            .chain(allowed_targets.iter().map(|t| t.plural_name()))
+            .map(|i| i.to_string())
+            .collect(),
+        allowed_targets.len() + added_fake_targets.len() == 1,
+    )
+}
+
+fn filter_targets(
+    allowed_targets: &mut Vec<Target>,
+    target_group: &'static [Target],
+    target_group_name: &'static str,
+    target: Target,
+    added_fake_targets: &mut Vec<&'static str>,
+) {
+    if target_group.contains(&target) {
+        return;
+    }
+    if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 {
+        return;
+    }
+    allowed_targets.retain(|t| !target_group.contains(t));
+    added_fake_targets.push(target_group_name);
+}
+
+/// This is the list of all targets to which a attribute can be applied
+/// This is used for:
+/// - `rustc_dummy`, which can be applied to all targets
+/// - Attributes that are not parted to the new target system yet can use this list as a placeholder
+pub(crate) const ALL_TARGETS: &'static [Policy] = {
+    use Policy::Allow;
+    &[
+        Allow(Target::ExternCrate),
+        Allow(Target::Use),
+        Allow(Target::Static),
+        Allow(Target::Const),
+        Allow(Target::Fn),
+        Allow(Target::Closure),
+        Allow(Target::Mod),
+        Allow(Target::ForeignMod),
+        Allow(Target::GlobalAsm),
+        Allow(Target::TyAlias),
+        Allow(Target::Enum),
+        Allow(Target::Variant),
+        Allow(Target::Struct),
+        Allow(Target::Field),
+        Allow(Target::Union),
+        Allow(Target::Trait),
+        Allow(Target::TraitAlias),
+        Allow(Target::Impl { of_trait: false }),
+        Allow(Target::Impl { of_trait: true }),
+        Allow(Target::Expression),
+        Allow(Target::Statement),
+        Allow(Target::Arm),
+        Allow(Target::AssocConst),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: false })),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+        Allow(Target::AssocTy),
+        Allow(Target::ForeignFn),
+        Allow(Target::ForeignStatic),
+        Allow(Target::ForeignTy),
+        Allow(Target::MacroDef),
+        Allow(Target::Param),
+        Allow(Target::PatField),
+        Allow(Target::ExprField),
+        Allow(Target::WherePredicate),
+        Allow(Target::MacroCall),
+        Allow(Target::Crate),
+        Allow(Target::Delegation { mac: false }),
+        Allow(Target::Delegation { mac: true }),
+    ]
+};
diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs
new file mode 100644
index 00000000000..7a7624893bd
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/validate_attr.rs
@@ -0,0 +1,340 @@
+//! Meta-syntax validation logic of attributes for post-expansion.
+
+use std::slice;
+
+use rustc_ast::token::Delimiter;
+use rustc_ast::tokenstream::DelimSpan;
+use rustc_ast::{
+    self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, NodeId,
+    Path, Safety,
+};
+use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult};
+use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
+use rustc_parse::parse_in;
+use rustc_session::errors::report_lit_error;
+use rustc_session::lint::BuiltinLintDiag;
+use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE};
+use rustc_session::parse::ParseSess;
+use rustc_span::{Span, Symbol, sym};
+
+use crate::{AttributeParser, Late, session_diagnostics as errors};
+
+pub fn check_attr(psess: &ParseSess, attr: &Attribute, id: NodeId) {
+    if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace)
+    {
+        return;
+    }
+
+    let builtin_attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
+
+    let builtin_attr_safety = builtin_attr_info.map(|x| x.safety);
+    check_attribute_safety(psess, builtin_attr_safety, attr, id);
+
+    // Check input tokens for built-in and key-value attributes.
+    match builtin_attr_info {
+        // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
+        Some(BuiltinAttribute { name, template, .. }) => {
+            if AttributeParser::<Late>::is_parsed_attribute(slice::from_ref(&name)) {
+                return;
+            }
+            match parse_meta(psess, attr) {
+                // Don't check safety again, we just did that
+                Ok(meta) => {
+                    check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false)
+                }
+                Err(err) => {
+                    err.emit();
+                }
+            }
+        }
+        _ => {
+            let attr_item = attr.get_normal_item();
+            if let AttrArgs::Eq { .. } = attr_item.args {
+                // All key-value attributes are restricted to meta-item syntax.
+                match parse_meta(psess, attr) {
+                    Ok(_) => {}
+                    Err(err) => {
+                        err.emit();
+                    }
+                }
+            }
+        }
+    }
+}
+
+pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
+    let item = attr.get_normal_item();
+    Ok(MetaItem {
+        unsafety: item.unsafety,
+        span: attr.span,
+        path: item.path.clone(),
+        kind: match &item.args {
+            AttrArgs::Empty => MetaItemKind::Word,
+            AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
+                check_meta_bad_delim(psess, *dspan, *delim);
+                let nmis =
+                    parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
+                MetaItemKind::List(nmis)
+            }
+            AttrArgs::Eq { expr, .. } => {
+                if let ast::ExprKind::Lit(token_lit) = expr.kind {
+                    let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
+                    let res = match res {
+                        Ok(lit) => {
+                            if token_lit.suffix.is_some() {
+                                let mut err = psess.dcx().struct_span_err(
+                                    expr.span,
+                                    "suffixed literals are not allowed in attributes",
+                                );
+                                err.help(
+                                    "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
+                                    use an unsuffixed version (`1`, `1.0`, etc.)",
+                                );
+                                return Err(err);
+                            } else {
+                                MetaItemKind::NameValue(lit)
+                            }
+                        }
+                        Err(err) => {
+                            let guar = report_lit_error(psess, err, token_lit, expr.span);
+                            let lit = ast::MetaItemLit {
+                                symbol: token_lit.symbol,
+                                suffix: token_lit.suffix,
+                                kind: ast::LitKind::Err(guar),
+                                span: expr.span,
+                            };
+                            MetaItemKind::NameValue(lit)
+                        }
+                    };
+                    res
+                } else {
+                    // Example cases:
+                    // - `#[foo = 1+1]`: results in `ast::ExprKind::Binary`.
+                    // - `#[foo = include_str!("nonexistent-file.rs")]`:
+                    //   results in `ast::ExprKind::Err`. In that case we delay
+                    //   the error because an earlier error will have already
+                    //   been reported.
+                    let msg = "attribute value must be a literal";
+                    let mut err = psess.dcx().struct_span_err(expr.span, msg);
+                    if let ast::ExprKind::Err(_) = expr.kind {
+                        err.downgrade_to_delayed_bug();
+                    }
+                    return Err(err);
+                }
+            }
+        },
+    })
+}
+
+fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
+    if let Delimiter::Parenthesis = delim {
+        return;
+    }
+    psess.dcx().emit_err(errors::MetaBadDelim {
+        span: span.entire(),
+        sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
+    });
+}
+
+/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
+fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
+    let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
+        [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
+        _ => false,
+    };
+    match meta {
+        MetaItemKind::Word => template.word,
+        MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
+        MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
+        MetaItemKind::NameValue(..) => false,
+    }
+}
+
+pub fn check_attribute_safety(
+    psess: &ParseSess,
+    builtin_attr_safety: Option<AttributeSafety>,
+    attr: &Attribute,
+    id: NodeId,
+) {
+    let attr_item = attr.get_normal_item();
+    match (builtin_attr_safety, attr_item.unsafety) {
+        // - Unsafe builtin attribute
+        // - User wrote `#[unsafe(..)]`, which is permitted on any edition
+        (Some(AttributeSafety::Unsafe { .. }), Safety::Unsafe(..)) => {
+            // OK
+        }
+
+        // - Unsafe builtin attribute
+        // - User did not write `#[unsafe(..)]`
+        (Some(AttributeSafety::Unsafe { unsafe_since }), Safety::Default) => {
+            let path_span = attr_item.path.span;
+
+            // If the `attr_item`'s span is not from a macro, then just suggest
+            // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the
+            // `unsafe(`, `)` right after and right before the opening and closing
+            // square bracket respectively.
+            let diag_span = attr_item.span();
+
+            // Attributes can be safe in earlier editions, and become unsafe in later ones.
+            //
+            // Use the span of the attribute's name to determine the edition: the span of the
+            // attribute as a whole may be inaccurate if it was emitted by a macro.
+            //
+            // See https://github.com/rust-lang/rust/issues/142182.
+            let emit_error = match unsafe_since {
+                None => true,
+                Some(unsafe_since) => path_span.edition() >= unsafe_since,
+            };
+
+            if emit_error {
+                psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe {
+                    span: path_span,
+                    suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion {
+                        left: diag_span.shrink_to_lo(),
+                        right: diag_span.shrink_to_hi(),
+                    },
+                });
+            } else {
+                psess.buffer_lint(
+                    UNSAFE_ATTR_OUTSIDE_UNSAFE,
+                    path_span,
+                    id,
+                    BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
+                        attribute_name_span: path_span,
+                        sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()),
+                    },
+                );
+            }
+        }
+
+        // - Normal builtin attribute, or any non-builtin attribute
+        // - All non-builtin attributes are currently considered safe; writing `#[unsafe(..)]` is
+        //   not permitted on non-builtin attributes or normal builtin attributes
+        (Some(AttributeSafety::Normal) | None, Safety::Unsafe(unsafe_span)) => {
+            psess.dcx().emit_err(errors::InvalidAttrUnsafe {
+                span: unsafe_span,
+                name: attr_item.path.clone(),
+            });
+        }
+
+        // - Normal builtin attribute
+        // - No explicit `#[unsafe(..)]` written.
+        (Some(AttributeSafety::Normal), Safety::Default) => {
+            // OK
+        }
+
+        // - Non-builtin attribute
+        // - No explicit `#[unsafe(..)]` written.
+        (None, Safety::Default) => {
+            // OK
+        }
+
+        (
+            Some(AttributeSafety::Unsafe { .. } | AttributeSafety::Normal) | None,
+            Safety::Safe(..),
+        ) => {
+            psess.dcx().span_delayed_bug(
+                attr_item.span(),
+                "`check_attribute_safety` does not expect `Safety::Safe` on attributes",
+            );
+        }
+    }
+}
+
+// Called by `check_builtin_meta_item` and code that manually denies
+// `unsafe(...)` in `cfg`
+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) = unsafety {
+        diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() });
+    }
+}
+
+pub fn check_builtin_meta_item(
+    psess: &ParseSess,
+    meta: &MetaItem,
+    style: ast::AttrStyle,
+    name: Symbol,
+    template: AttributeTemplate,
+    deny_unsafety: bool,
+) {
+    if !is_attr_template_compatible(&template, &meta.kind) {
+        // attrs with new parsers are locally validated so excluded here
+        emit_malformed_attribute(psess, style, meta.span, name, template);
+    }
+
+    if deny_unsafety {
+        deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
+    }
+}
+
+fn emit_malformed_attribute(
+    psess: &ParseSess,
+    style: ast::AttrStyle,
+    span: Span,
+    name: Symbol,
+    template: AttributeTemplate,
+) {
+    // Some of previously accepted forms were used in practice,
+    // report them as warnings for now.
+    let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench);
+
+    let error_msg = format!("malformed `{name}` attribute input");
+    let mut suggestions = vec![];
+    let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
+    if template.word {
+        suggestions.push(format!("#{inner}[{name}]"));
+    }
+    if let Some(descr) = template.list {
+        for descr in descr {
+            suggestions.push(format!("#{inner}[{name}({descr})]"));
+        }
+    }
+    suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
+    if let Some(descr) = template.name_value_str {
+        for descr in descr {
+            suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
+        }
+    }
+    if should_warn(name) {
+        psess.buffer_lint(
+            ILL_FORMED_ATTRIBUTE_INPUT,
+            span,
+            ast::CRATE_NODE_ID,
+            BuiltinLintDiag::IllFormedAttributeInput {
+                suggestions: suggestions.clone(),
+                docs: template.docs,
+            },
+        );
+    } else {
+        suggestions.sort();
+        let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions(
+            span,
+            if suggestions.len() == 1 {
+                "must be of the form"
+            } else {
+                "the following are the possible correct uses"
+            },
+            suggestions,
+            Applicability::HasPlaceholders,
+        );
+        if let Some(link) = template.docs {
+            err.note(format!("for more information, visit <{link}>"));
+        }
+        err.emit();
+    }
+}
+
+pub fn emit_fatal_malformed_builtin_attribute(
+    psess: &ParseSess,
+    attr: &Attribute,
+    name: Symbol,
+) -> ! {
+    let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
+    emit_malformed_attribute(psess, attr.style, attr.span, name, template);
+    // This is fatal, otherwise it will likely cause a cascade of other errors
+    // (and an error here is expected to be very rare).
+    FatalError.raise()
+}