about summary refs log tree commit diff
path: root/compiler/rustc_attr_parsing
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_attr_parsing')
-rw-r--r--compiler/rustc_attr_parsing/Cargo.toml2
-rw-r--r--compiler/rustc_attr_parsing/messages.ftl21
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs10
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/body.rs8
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs250
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/confusables.rs15
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/crate_level.rs33
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/deprecation.rs23
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/dummy.rs4
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/inline.rs14
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/link_attrs.rs43
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs7
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/loop_match.rs7
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs16
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs11
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/must_use.rs28
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs7
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs7
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/path.rs9
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/prelude.rs20
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs26
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/prototype.rs11
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/repr.rs16
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs12
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/semantics.rs5
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs10
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/test_attrs.rs12
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/traits.rs16
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/transparency.rs12
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/util.rs39
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs636
-rw-r--r--compiler/rustc_attr_parsing/src/interface.rs370
-rw-r--r--compiler/rustc_attr_parsing/src/lib.rs23
-rw-r--r--compiler/rustc_attr_parsing/src/lints.rs27
-rw-r--r--compiler/rustc_attr_parsing/src/parser.rs483
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs116
-rw-r--r--compiler/rustc_attr_parsing/src/target_checking.rs247
-rw-r--r--compiler/rustc_attr_parsing/src/validate_attr.rs340
38 files changed, 1818 insertions, 1118 deletions
diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml
index bac89373b67..fd8f7ffb2ed 100644
--- a/compiler/rustc_attr_parsing/Cargo.toml
+++ b/compiler/rustc_attr_parsing/Cargo.toml
@@ -5,7 +5,6 @@ edition = "2024"
 
 [dependencies]
 # tidy-alphabetical-start
-itertools = "0.12"
 rustc_abi = { path = "../rustc_abi" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_ast_pretty = { path = "../rustc_ast_pretty" }
@@ -15,6 +14,7 @@ rustc_fluent_macro = { path = "../rustc_fluent_macro" }
 rustc_hir = { path = "../rustc_hir" }
 rustc_lexer = { path = "../rustc_lexer" }
 rustc_macros = { path = "../rustc_macros" }
+rustc_parse = { path = "../rustc_parse" }
 rustc_session = { path = "../rustc_session" }
 rustc_span = { path = "../rustc_span" }
 thin-vec = "0.2.12"
diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl
index 4fb66a81652..40e9d597530 100644
--- a/compiler/rustc_attr_parsing/messages.ftl
+++ b/compiler/rustc_attr_parsing/messages.ftl
@@ -12,9 +12,11 @@ attr_parsing_empty_attribute =
 
 attr_parsing_invalid_target = `#[{$name}]` attribute cannot be used on {$target}
     .help = `#[{$name}]` can {$only}be applied to {$applied}
+    .suggestion = remove the attribute
 attr_parsing_invalid_target_lint = `#[{$name}]` attribute cannot be used on {$target}
     .warn = {-attr_parsing_previously_accepted}
     .help = `#[{$name}]` can {$only}be applied to {$applied}
+    .suggestion = remove the attribute
 
 attr_parsing_empty_confusables =
     expected at least one confusable name
@@ -168,3 +170,22 @@ attr_parsing_unused_multiple =
 
 -attr_parsing_previously_accepted =
     this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+
+attr_parsing_meta_bad_delim = wrong meta list delimiters
+attr_parsing_meta_bad_delim_suggestion = the delimiters should be `(` and `)`
+
+attr_parsing_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
+    .label = usage of unsafe attribute
+attr_parsing_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
+
+attr_parsing_invalid_attr_unsafe = `{$name}` is not an unsafe attribute
+    .label = this is not an unsafe attribute
+    .suggestion = remove the `unsafe(...)`
+    .note = extraneous unsafe is not allowed in attributes
+
+attr_parsing_invalid_meta_item = expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found {$descr}
+    .remove_neg_sugg = negative numbers are not literals, try removing the `-` sign
+    .quote_ident_sugg = surround the identifier with quotation marks to make it into a string literal
+
+attr_parsing_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes
+    .help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
index 4d995027814..088fa73d742 100644
--- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
@@ -1,14 +1,6 @@
 use std::iter;
 
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::AttributeKind;
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{Span, Symbol, sym};
-
-use super::{CombineAttributeParser, ConvertFn};
-use crate::context::MaybeWarn::{Allow, Warn};
-use crate::context::{AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
+use super::prelude::*;
 use crate::session_diagnostics;
 
 pub(crate) struct AllowInternalUnstableParser;
diff --git a/compiler/rustc_attr_parsing/src/attributes/body.rs b/compiler/rustc_attr_parsing/src/attributes/body.rs
index 88540384621..a1492d76194 100644
--- a/compiler/rustc_attr_parsing/src/attributes/body.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/body.rs
@@ -1,12 +1,6 @@
 //! Attributes that can be found in function body.
 
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Symbol, sym};
-
-use super::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AllowedTargets, Stage};
+use super::prelude::*;
 
 pub(crate) struct CoroutineParser;
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
index 6ea073896c2..ffdacff7152 100644
--- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -1,16 +1,7 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::{AttributeKind, CoverageAttrKind, OptimizeAttr, UsedBy};
-use rustc_hir::{MethodKind, Target};
+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 crate::context::MaybeWarn::{Allow, Warn};
-use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
-use crate::parser::ArgParser;
+
+use super::prelude::*;
 use crate::session_diagnostics::{NakedFunctionIncompatibleAttribute, NullOnExport};
 
 pub(crate) struct OptimizeParser;
@@ -44,7 +35,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
             }
         };
@@ -91,7 +82,7 @@ impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
 
     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, vec!["on", "off"]);
+            cx.expected_specific_argument_and_list(cx.attr_span, &[sym::on, sym::off]);
             return None;
         };
 
@@ -100,7 +91,8 @@ impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
             return None;
         };
 
-        let fail_incorrect_argument = |span| cx.expected_specific_argument(span, vec!["on", "off"]);
+        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);
@@ -135,6 +127,7 @@ impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
         Warn(Target::Field),
         Warn(Target::Arm),
         Warn(Target::MacroDef),
+        Warn(Target::MacroCall),
     ]);
     const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
 
@@ -182,6 +175,7 @@ impl<S: Stage> AttributeParser<S> for NakedParser {
         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> {
@@ -286,6 +280,7 @@ impl<S: Stage> NoArgsAttributeParser<S> for TrackCallerParser {
         Warn(Target::MacroDef),
         Warn(Target::Arm),
         Warn(Target::Field),
+        Warn(Target::MacroCall),
     ]);
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller;
 }
@@ -352,7 +347,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;
                         }
                     }
@@ -373,7 +368,8 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
             }
         },
     )];
-    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Static)]);
+    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`
@@ -385,57 +381,68 @@ 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 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 {
-            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
+        parse_tf_attribute(cx, args)
     }
 
     const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
@@ -447,5 +454,134 @@ impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser {
         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 None;
+        };
+
+        let mut on_set = SanitizerSet::empty();
+        let mut off_set = SanitizerSet::empty();
+
+        for item in list.mixed() {
+            let Some(item) = item.meta_item() else {
+                cx.expected_name_value(item.span(), None);
+                continue;
+            };
+
+            let path = item.path().word_sym();
+            let Some(value) = item.args().name_value() else {
+                cx.expected_name_value(item.span(), path);
+                continue;
+            };
+
+            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;
+                }
+            };
+
+            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;
+                }
+            }
+        }
+
+        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 00f949c82c5..97e78dfb136 100644
--- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs
@@ -1,13 +1,6 @@
-use rustc_feature::template;
-use rustc_hir::attrs::AttributeKind;
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{Span, Symbol, sym};
-use thin_vec::ThinVec;
-
-use super::{AcceptMapping, AttributeParser};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AllowedTargets, FinalizeContext, Stage};
-use crate::session_diagnostics;
+use super::prelude::*;
+use crate::session_diagnostics::EmptyConfusables;
+
 #[derive(Default)]
 pub(crate) struct ConfusablesParser {
     confusables: ThinVec<Symbol>,
@@ -25,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() {
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..9175d7479e1
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs
@@ -0,0 +1,33 @@
+use super::prelude::*;
+
+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");
+
+    // FIXME: crate name is allowed on all targets and ignored,
+    //        even though it should only be valid on crates of course
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
+
+    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,
+        })
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
index d3a61f3a653..31c698228ef 100644
--- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
@@ -1,14 +1,11 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation};
-use rustc_hir::{MethodKind, Target};
-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::MaybeWarn::{Allow, Error};
-use crate::context::{AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
-use crate::session_diagnostics;
+use crate::session_diagnostics::{
+    DeprecatedItemSuggestion, InvalidSince, MissingNote, MissingSince,
+};
+
 pub(crate) struct DeprecationParser;
 
 fn get<S: Stage>(
@@ -102,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: (),
@@ -144,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 85842b1b5c5..7293cee842c 100644
--- a/compiler/rustc_attr_parsing/src/attributes/dummy.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs
@@ -3,8 +3,10 @@ use rustc_hir::attrs::AttributeKind;
 use rustc_span::{Symbol, sym};
 
 use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::{ALL_TARGETS, AcceptContext, AllowedTargets, Stage};
+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];
diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs
index 33c21bad240..a73430c9d00 100644
--- a/compiler/rustc_attr_parsing/src/attributes/inline.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs
@@ -2,17 +2,10 @@
 //                      note: need to model better how duplicate attr errors work when not using
 //                      SingleAttributeParser which is what we have two of here.
 
-use rustc_feature::{AttributeTemplate, template};
 use rustc_hir::attrs::{AttributeKind, InlineAttr};
-use rustc_hir::lints::AttributeLintKind;
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{Symbol, sym};
 
-use super::{AcceptContext, AttributeOrder, OnDuplicate};
-use crate::attributes::SingleAttributeParser;
-use crate::context::MaybeWarn::{Allow, Warn};
-use crate::context::{AllowedTargets, Stage};
-use crate::parser::ArgParser;
+use super::prelude::*;
+
 pub(crate) struct InlineParser;
 
 impl<S: Stage> SingleAttributeParser<S> for InlineParser {
@@ -32,6 +25,7 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
         Warn(Target::MacroDef),
         Warn(Target::Arm),
         Warn(Target::AssocConst),
+        Warn(Target::MacroCall),
     ]);
     const TEMPLATE: AttributeTemplate = template!(
         Word,
@@ -56,7 +50,7 @@ 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;
                     }
                 }
diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
index 552b9dfabc2..5e4551ccd79 100644
--- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
@@ -1,16 +1,10 @@
-use rustc_feature::{AttributeTemplate, template};
 use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection};
-use rustc_hir::attrs::{AttributeKind, Linkage};
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{Span, Symbol, sym};
-
-use crate::attributes::{
-    AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
-};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{ALL_TARGETS, AcceptContext, AllowedTargets, Stage, parse_single_integer};
-use crate::parser::ArgParser;
+use rustc_hir::attrs::Linkage;
+
+use super::prelude::*;
+use super::util::parse_single_integer;
 use crate::session_diagnostics::{LinkOrdinalOutOfRange, NullOnLinkSection};
+
 pub(crate) struct LinkNameParser;
 
 impl<S: Stage> SingleAttributeParser<S> for LinkNameParser {
@@ -116,8 +110,11 @@ 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 ALLOWED_TARGETS: AllowedTargets =
-        AllowedTargets::AllowList(&[Allow(Target::ForeignFn), Allow(Target::ForeignStatic)]);
+    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"
@@ -212,16 +209,16 @@ impl<S: Stage> SingleAttributeParser<S> for LinkageParser {
             _ => {
                 cx.expected_specific_argument(
                     name_value.value_span,
-                    vec![
-                        "available_externally",
-                        "common",
-                        "extern_weak",
-                        "external",
-                        "internal",
-                        "linkonce",
-                        "linkonce_odr",
-                        "weak",
-                        "weak_odr",
+                    &[
+                        sym::available_externally,
+                        sym::common,
+                        sym::extern_weak,
+                        sym::external,
+                        sym::internal,
+                        sym::linkonce,
+                        sym::linkonce_odr,
+                        sym::weak,
+                        sym::weak_odr,
                     ],
                 );
                 return None;
diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
index 2b586d4003c..63b0809d0d8 100644
--- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
@@ -1,10 +1,5 @@
-use rustc_hir::attrs::AttributeKind;
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{Span, Symbol, sym};
+use super::prelude::*;
 
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::MaybeWarn::{Allow, Error};
-use crate::context::{AllowedTargets, Stage};
 pub(crate) struct AsPtrParser;
 impl<S: Stage> NoArgsAttributeParser<S> for AsPtrParser {
     const PATH: &[Symbol] = &[sym::rustc_as_ptr];
diff --git a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs
index 242e2f2c1bc..528090b8673 100644
--- a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs
@@ -1,10 +1,5 @@
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Span, Symbol, sym};
+use super::prelude::*;
 
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AllowedTargets, Stage};
 pub(crate) struct LoopMatchParser;
 impl<S: Stage> NoArgsAttributeParser<S> for LoopMatchParser {
     const PATH: &[Symbol] = &[sym::loop_match];
diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs
index 8928129c201..180130c7be4 100644
--- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs
@@ -1,15 +1,9 @@
 use rustc_errors::DiagArgValue;
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::Target;
-use rustc_hir::attrs::{AttributeKind, MacroUseArgs};
-use rustc_span::{Span, Symbol, sym};
-use thin_vec::ThinVec;
+use rustc_hir::attrs::MacroUseArgs;
+
+use super::prelude::*;
+use crate::session_diagnostics::IllFormedAttributeInputLint;
 
-use crate::attributes::{AcceptMapping, AttributeParser, NoArgsAttributeParser, OnDuplicate};
-use crate::context::MaybeWarn::{Allow, Error, Warn};
-use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
-use crate::parser::ArgParser;
-use crate::session_diagnostics;
 pub(crate) struct MacroEscapeParser;
 impl<S: Stage> NoArgsAttributeParser<S> for MacroEscapeParser {
     const PATH: &[Symbol] = &[sym::macro_escape];
@@ -108,7 +102,7 @@ impl<S: Stage> AttributeParser<S> for MacroUseParser {
                 }
                 ArgParser::NameValue(_) => {
                     let suggestions = MACRO_USE_TEMPLATE.suggestions(cx.attr_style, sym::macro_use);
-                    cx.emit_err(session_diagnostics::IllFormedAttributeInputLint {
+                    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/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index 3d6e26a24b8..9dad9c893f0 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -7,9 +7,9 @@
 //! 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.
@@ -21,9 +21,13 @@ use rustc_hir::attrs::AttributeKind;
 use rustc_span::{Span, Symbol};
 use thin_vec::ThinVec;
 
-use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
+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;
@@ -31,6 +35,7 @@ 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;
diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs
index b0ee3d1ba6e..e6a5141d783 100644
--- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs
@@ -1,19 +1,29 @@
 use rustc_errors::DiagArgValue;
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Symbol, sym};
 
-use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::{ALL_TARGETS, AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
-use crate::session_diagnostics;
+use super::prelude::*;
+use crate::session_diagnostics::IllFormedAttributeInputLint;
+
 pub(crate) struct MustUseParser;
 
 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 ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
+    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"
@@ -37,7 +47,7 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
                 ArgParser::List(_) => {
                     let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
                         .suggestions(cx.attr_style, "must_use");
-                    cx.emit_err(session_diagnostics::IllFormedAttributeInputLint {
+                    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 589faf38f73..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,10 +1,5 @@
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Span, sym};
+use super::prelude::*;
 
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AllowedTargets, Stage};
 pub(crate) struct NoImplicitPreludeParser;
 
 impl<S: Stage> NoArgsAttributeParser<S> for NoImplicitPreludeParser {
diff --git a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs
index 41e9ca4de41..fc41c073fd2 100644
--- a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs
@@ -3,8 +3,10 @@ use rustc_hir::attrs::AttributeKind;
 use rustc_span::{Span, Symbol, sym};
 
 use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::MaybeWarn::{Allow, Warn};
-use crate::context::{AllowedTargets, Stage};
+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 {
@@ -17,6 +19,7 @@ impl<S: Stage> NoArgsAttributeParser<S> for NonExhaustiveParser {
         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 f9191d1abed..e4cb806bb42 100644
--- a/compiler/rustc_attr_parsing/src/attributes/path.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/path.rs
@@ -1,12 +1,5 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Symbol, sym};
+use super::prelude::*;
 
-use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::MaybeWarn::{Allow, Error};
-use crate::context::{AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
 pub(crate) struct PathParser;
 
 impl<S: Stage> SingleAttributeParser<S> for PathParser {
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..2bcdee55c75
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs
@@ -0,0 +1,20 @@
+// parsing
+// templates
+pub(super) use rustc_feature::{AttributeTemplate, template};
+// data structures
+pub(super) use rustc_hir::attrs::AttributeKind;
+pub(super) use rustc_hir::lints::AttributeLintKind;
+pub(super) use rustc_hir::{MethodKind, Target};
+pub(super) use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
+pub(super) use thin_vec::ThinVec;
+
+pub(super) use crate::attributes::{
+    AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn,
+    NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
+};
+// contexts
+pub(super) use crate::context::{AcceptContext, FinalizeContext, Stage};
+pub(super) use crate::parser::*;
+// target checking
+pub(super) use crate::target_checking::Policy::{Allow, Error, Warn};
+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
index 4624fa36287..b9929d6f1f8 100644
--- a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs
@@ -1,21 +1,13 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Span, Symbol, sym};
-use thin_vec::ThinVec;
+use super::prelude::*;
+
+const PROC_MACRO_ALLOWED_TARGETS: AllowedTargets =
+    AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate), Warn(Target::MacroCall)]);
 
-use crate::attributes::{
-    AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
-};
-use crate::context::MaybeWarn::{Allow, Warn};
-use crate::context::{AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
 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 =
-        AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate)]);
+    const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS;
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::ProcMacro;
 }
 
@@ -23,8 +15,7 @@ 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 =
-        AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate)]);
+    const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS;
     const CREATE: fn(Span) -> AttributeKind = AttributeKind::ProcMacroAttribute;
 }
 
@@ -33,8 +24,7 @@ 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 =
-        AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate)]);
+    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"
@@ -110,7 +100,7 @@ fn parse_derive_like<S: Stage>(
             return None;
         };
         if !attr_list.path().word_is(sym::attributes) {
-            cx.expected_specific_argument(attrs.span(), vec!["attributes"]);
+            cx.expected_specific_argument(attrs.span(), &[sym::attributes]);
             return None;
         }
         let Some(attr_list) = attr_list.args().list() else {
diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs
index fb1e47298b4..80fe82bf542 100644
--- a/compiler/rustc_attr_parsing/src/attributes/prototype.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs
@@ -7,8 +7,10 @@ use rustc_span::{Span, Symbol, sym};
 
 use super::{AttributeOrder, OnDuplicate};
 use crate::attributes::SingleAttributeParser;
-use crate::context::{AcceptContext, AllowedTargets, MaybeWarn, Stage};
+use crate::context::{AcceptContext, Stage};
 use crate::parser::ArgParser;
+use crate::target_checking::AllowedTargets;
+use crate::target_checking::Policy::Allow;
 
 pub(crate) struct CustomMirParser;
 
@@ -19,8 +21,7 @@ impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
 
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
 
-    const ALLOWED_TARGETS: AllowedTargets =
-        AllowedTargets::AllowList(&[MaybeWarn::Allow(Target::Fn)]);
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]);
 
     const TEMPLATE: AttributeTemplate = template!(List: &[r#"dialect = "...", phase = "...""#]);
 
@@ -108,7 +109,7 @@ fn parse_dialect<S: Stage>(
         sym::runtime => MirDialect::Runtime,
 
         _ => {
-            cx.expected_specific_argument(span, vec!["analysis", "built", "runtime"]);
+            cx.expected_specific_argument(span, &[sym::analysis, sym::built, sym::runtime]);
             *failed = true;
             return None;
         }
@@ -130,7 +131,7 @@ fn parse_phase<S: Stage>(
         sym::optimized => MirPhase::Optimized,
 
         _ => {
-            cx.expected_specific_argument(span, vec!["initial", "post-cleanup", "optimized"]);
+            cx.expected_specific_argument(span, &[sym::initial, sym::post_cleanup, sym::optimized]);
             *failed = true;
             return None;
         }
diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs
index 7ab58ed9347..23aabd15597 100644
--- a/compiler/rustc_attr_parsing/src/attributes/repr.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs
@@ -1,16 +1,10 @@
 use rustc_abi::Align;
 use rustc_ast::{IntTy, LitIntType, LitKind, UintTy};
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::{AttributeKind, IntType, ReprAttr};
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{DUMMY_SP, Span, Symbol, sym};
-
-use super::{AcceptMapping, AttributeParser, CombineAttributeParser, ConvertFn, FinalizeContext};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{ALL_TARGETS, AcceptContext, AllowedTargets, Stage};
-use crate::parser::{ArgParser, MetaItemListParser, MetaItemParser};
-use crate::session_diagnostics;
-use crate::session_diagnostics::IncorrectReprFormatGenericCause;
+use rustc_hir::attrs::{IntType, ReprAttr};
+
+use super::prelude::*;
+use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
+
 /// Parse #[repr(...)] forms.
 ///
 /// Valid repr contents: any of the primitive integral type names (see
diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
index efd7b650e44..a995549fc7c 100644
--- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
@@ -1,12 +1,6 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Symbol, sym};
-
-use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AcceptContext, AllowedTargets, Stage, parse_single_integer};
-use crate::parser::ArgParser;
+use super::prelude::*;
+use super::util::parse_single_integer;
+
 pub(crate) struct RustcLayoutScalarValidRangeStart;
 
 impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeStart {
diff --git a/compiler/rustc_attr_parsing/src/attributes/semantics.rs b/compiler/rustc_attr_parsing/src/attributes/semantics.rs
index d4ad861a3a2..d7f62483297 100644
--- a/compiler/rustc_attr_parsing/src/attributes/semantics.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/semantics.rs
@@ -1,8 +1,5 @@
-use rustc_hir::attrs::AttributeKind;
-use rustc_span::{Span, Symbol, sym};
+use super::prelude::*;
 
-use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
-use crate::context::{ALL_TARGETS, AllowedTargets, Stage};
 pub(crate) struct MayDangleParser;
 impl<S: Stage> NoArgsAttributeParser<S> for MayDangleParser {
     const PATH: &[Symbol] = &[sym::may_dangle];
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 5a26178f84b..b94e23477ff 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -1,20 +1,13 @@
 use std::num::NonZero;
 
 use rustc_errors::ErrorGuaranteed;
-use rustc_feature::template;
-use rustc_hir::attrs::AttributeKind;
 use rustc_hir::{
     DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel,
     StableSince, Target, UnstableReason, VERSION_PLACEHOLDER,
 };
-use rustc_span::{Ident, Span, Symbol, sym};
 
+use super::prelude::*;
 use super::util::parse_version;
-use super::{AcceptMapping, AttributeParser, OnDuplicate};
-use crate::attributes::NoArgsAttributeParser;
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
-use crate::parser::{ArgParser, MetaItemParser};
 use crate::session_diagnostics::{self, UnsupportedLiteralReason};
 
 macro_rules! reject_outside_std {
@@ -54,6 +47,7 @@ const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
     Allow(Target::Static),
     Allow(Target::ForeignFn),
     Allow(Target::ForeignStatic),
+    Allow(Target::ExternCrate),
 ]);
 
 #[derive(Default)]
diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
index 164c680b8a8..510ff1ded49 100644
--- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
@@ -1,13 +1,5 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
-use rustc_hir::lints::AttributeLintKind;
-use rustc_span::{Symbol, sym};
+use super::prelude::*;
 
-use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::MaybeWarn::{Allow, Error};
-use crate::context::{AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
 pub(crate) struct IgnoreParser;
 
 impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
@@ -89,7 +81,7 @@ impl<S: Stage> SingleAttributeParser<S> for ShouldPanicParser {
                         return None;
                     };
                     if !single.path().word_is(sym::expected) {
-                        cx.expected_specific_argument_strings(list.span, vec!["expected"]);
+                        cx.expected_specific_argument_strings(list.span, &[sym::expected]);
                         return None;
                     }
                     let Some(nv) = single.args().name_value() else {
diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs
index ee9d7ba99cd..89ac1b07d16 100644
--- a/compiler/rustc_attr_parsing/src/attributes/traits.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs
@@ -1,16 +1,14 @@
-use core::mem;
-
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::attrs::AttributeKind;
-use rustc_hir::{MethodKind, Target};
-use rustc_span::{Span, Symbol, sym};
+use std::mem;
 
+use super::prelude::*;
 use crate::attributes::{
     AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser,
 };
-use crate::context::MaybeWarn::{Allow, Warn};
-use crate::context::{ALL_TARGETS, AcceptContext, AllowedTargets, Stage};
+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];
@@ -44,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;
                 }
             };
diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
index 0ffcf434b52..ea1f5549c4e 100644
--- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
@@ -1,13 +1,7 @@
-use rustc_feature::{AttributeTemplate, template};
-use rustc_hir::Target;
-use rustc_hir::attrs::AttributeKind;
 use rustc_span::hygiene::Transparency;
-use rustc_span::{Symbol, sym};
 
-use super::{AttributeOrder, OnDuplicate, SingleAttributeParser};
-use crate::context::MaybeWarn::Allow;
-use crate::context::{AcceptContext, AllowedTargets, Stage};
-use crate::parser::ArgParser;
+use super::prelude::*;
+
 pub(crate) struct TransparencyParser;
 
 // FIXME(jdonszelmann): make these proper diagnostics
@@ -35,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 10134915b27..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_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 c0d3bc99ba9..d4b9cfe00ad 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -3,28 +3,28 @@ use std::collections::BTreeMap;
 use std::ops::{Deref, DerefMut};
 use std::sync::LazyLock;
 
-use itertools::Itertools;
 use private::Sealed;
-use rustc_ast::{self as ast, AttrStyle, LitKind, MetaItemLit, NodeId};
-use rustc_errors::{DiagCtxtHandle, Diagnostic};
-use rustc_feature::{AttributeTemplate, Features};
+use rustc_ast::{AttrStyle, 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::{
-    AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, HirId, MethodKind, Target,
-};
+use rustc_hir::{AttrPath, 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, CoverageParser, ExportNameParser, NakedParser, NoMangleParser, OptimizeParser,
-    TargetFeatureParser, TrackCallerParser, UsedParser,
+    ColdParser, CoverageParser, ExportNameParser, ForceTargetFeatureParser, NakedParser,
+    NoMangleParser, OptimizeParser, SanitizeParser, TargetFeatureParser, TrackCallerParser,
+    UsedParser,
 };
 use crate::attributes::confusables::ConfusablesParser;
+use crate::attributes::crate_level::CrateNameParser;
 use crate::attributes::deprecation::DeprecationParser;
 use crate::attributes::dummy::DummyParser;
 use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
@@ -65,23 +65,21 @@ use crate::attributes::traits::{
 };
 use crate::attributes::transparency::TransparencyParser;
 use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
-use crate::context::MaybeWarn::{Allow, Error, Warn};
-use crate::parser::{ArgParser, MetaItemParser, PathParser};
-use crate::session_diagnostics::{
-    AttributeParseError, AttributeParseErrorReason, InvalidTarget, UnknownMetaItem,
-};
+use crate::parser::{ArgParser, PathParser};
+use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem};
+use crate::target_checking::AllowedTargets;
 
 type GroupType<S> = LazyLock<GroupTypeInner<S>>;
 
-struct GroupTypeInner<S: Stage> {
-    accepters: BTreeMap<&'static [Symbol], Vec<GroupTypeInnerAccept<S>>>,
-    finalizers: Vec<FinalizeFn<S>>,
+pub(super) struct GroupTypeInner<S: Stage> {
+    pub(super) accepters: BTreeMap<&'static [Symbol], Vec<GroupTypeInnerAccept<S>>>,
+    pub(super) finalizers: Vec<FinalizeFn<S>>,
 }
 
-struct GroupTypeInnerAccept<S: Stage> {
-    template: AttributeTemplate,
-    accept_fn: AcceptFn<S>,
-    allowed_targets: AllowedTargets,
+pub(super) struct GroupTypeInnerAccept<S: Stage> {
+    pub(super) template: AttributeTemplate,
+    pub(super) accept_fn: AcceptFn<S>,
+    pub(super) allowed_targets: AllowedTargets,
 }
 
 type AcceptFn<S> =
@@ -161,6 +159,7 @@ attribute_parsers!(
         // tidy-alphabetical-start
         Combine<AllowConstFnUnstableParser>,
         Combine<AllowInternalUnstableParser>,
+        Combine<ForceTargetFeatureParser>,
         Combine<ReprParser>,
         Combine<TargetFeatureParser>,
         Combine<UnstableFeatureBoundParser>,
@@ -168,6 +167,7 @@ attribute_parsers!(
 
         // tidy-alphabetical-start
         Single<CoverageParser>,
+        Single<CrateNameParser>,
         Single<CustomMirParser>,
         Single<DeprecationParser>,
         Single<DummyParser>,
@@ -187,6 +187,7 @@ attribute_parsers!(
         Single<RustcLayoutScalarValidRangeEnd>,
         Single<RustcLayoutScalarValidRangeStart>,
         Single<RustcObjectLifetimeDefaultParser>,
+        Single<SanitizeParser>,
         Single<ShouldPanicParser>,
         Single<SkipDuringMethodDispatchParser>,
         Single<TransparencyParser>,
@@ -264,11 +265,7 @@ 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 {
@@ -315,7 +312,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.
@@ -334,7 +333,7 @@ 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 !self.stage.should_emit().should_emit() {
+        if matches!(self.stage.should_emit(), ShouldEmit::Nothing) {
             return;
         }
         let id = self.target_id;
@@ -506,10 +505,11 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
         })
     }
 
+    /// 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,
@@ -525,10 +525,12 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
         })
     }
 
+    /// 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: Vec<&'static str>,
+        possibilities: &[Symbol],
     ) -> ErrorGuaranteed {
         self.emit_err(AttributeParseError {
             span,
@@ -544,10 +546,11 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
         })
     }
 
+    /// 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,
@@ -595,7 +598,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.
@@ -648,8 +651,13 @@ 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,
     /// The operation will emit errors and lints.
     /// This is usually what you need.
     ErrorsAndLints,
@@ -659,564 +667,12 @@ pub enum ShouldEmit {
 }
 
 impl ShouldEmit {
-    pub fn should_emit(&self) -> bool {
-        match self {
-            ShouldEmit::ErrorsAndLints => true,
-            ShouldEmit::Nothing => false,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub(crate) enum AllowedTargets {
-    AllowList(&'static [MaybeWarn]),
-    AllowListWarnRest(&'static [MaybeWarn]),
-}
-
-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(&Allow(target)) {
-                    AllowedResult::Allowed
-                } else if list.contains(&Warn(target)) {
-                    AllowedResult::Warn
-                } else {
-                    AllowedResult::Error
-                }
-            }
-            AllowedTargets::AllowListWarnRest(list) => {
-                if list.contains(&Allow(target)) {
-                    AllowedResult::Allowed
-                } else if list.contains(&Error(target)) {
-                    AllowedResult::Error
-                } else {
-                    AllowedResult::Warn
-                }
-            }
-        }
-    }
-
-    pub(crate) fn allowed_targets(&self) -> Vec<Target> {
+    pub(crate) fn emit_err(&self, diag: Diag<'_>) -> ErrorGuaranteed {
         match self {
-            AllowedTargets::AllowList(list) => list,
-            AllowedTargets::AllowListWarnRest(list) => list,
+            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(),
         }
-        .iter()
-        .filter_map(|target| match target {
-            Allow(target) => Some(*target),
-            Warn(_) => None,
-            Error(_) => None,
-        })
-        .collect()
-    }
-}
-
-#[derive(Debug, Eq, PartialEq)]
-pub(crate) enum MaybeWarn {
-    Allow(Target),
-    Warn(Target),
-    Error(Target),
-}
-
-/// 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,
-            Target::Crate, // Does not matter, we're not going to emit errors anyways
-            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,
-            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::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().accepters.get(parts.as_slice()) {
-                        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 self.stage.should_emit().should_emit() {
-                                match accept.allowed_targets.is_allowed(target) {
-                                    AllowedResult::Allowed => {}
-                                    AllowedResult::Warn => {
-                                        let allowed_targets =
-                                            accept.allowed_targets.allowed_targets();
-                                        let (applied, only) = allowed_targets_applied(
-                                            allowed_targets,
-                                            target,
-                                            self.features,
-                                        );
-                                        emit_lint(AttributeLint {
-                                            id: target_id,
-                                            span: attr.span,
-                                            kind: AttributeLintKind::InvalidTarget {
-                                                name: parts[0],
-                                                target,
-                                                only: if only { "only " } else { "" },
-                                                applied,
-                                            },
-                                        });
-                                    }
-                                    AllowedResult::Error => {
-                                        let allowed_targets =
-                                            accept.allowed_targets.allowed_targets();
-                                        let (applied, only) = allowed_targets_applied(
-                                            allowed_targets,
-                                            target,
-                                            self.features,
-                                        );
-                                        self.dcx().emit_err(InvalidTarget {
-                                            span: attr.span,
-                                            name: parts[0],
-                                            target: target.plural_name(),
-                                            only: if only { "only " } else { "" },
-                                            applied,
-                                        });
-                                    }
-                                }
-                            }
-                        }
-                    } 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 }
-            }
-        }
-    }
-}
-
-/// 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>,
-) -> (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()))
-            .join(", "),
-        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 [MaybeWarn] = &[
-    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 }),
-];
-
-/// 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..60523c2877c
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/interface.rs
@@ -0,0 +1,370 @@
+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!(self.stage.should_emit(), ShouldEmit::Nothing) {
+                                self.check_target(
+                                    path.get_attribute_path(),
+                                    attr.span,
+                                    &accept.allowed_targets,
+                                    target,
+                                    target_id,
+                                    &mut emit_lint,
+                                );
+                            }
+                        }
+                    } 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 fc1377e5314..4dd908cdc40 100644
--- a/compiler/rustc_attr_parsing/src/lib.rs
+++ b/compiler/rustc_attr_parsing/src/lib.rs
@@ -84,18 +84,31 @@
 // 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 733225bab59..84ae19c4fc6 100644
--- a/compiler/rustc_attr_parsing/src/lints.rs
+++ b/compiler/rustc_attr_parsing/src/lints.rs
@@ -1,11 +1,13 @@
+use std::borrow::Cow;
+
 use rustc_errors::{DiagArgValue, LintEmitter};
+use rustc_hir::Target;
 use rustc_hir::lints::{AttributeLint, AttributeLintKind};
-use rustc_hir::{HirId, Target};
 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 {
@@ -35,12 +37,18 @@ 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, ref applied, only } => lint_emitter
+        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 == sym::deprecated
-                    && ![Target::Closure, Target::Expression, Target::Statement, Target::Arm]
-                        .contains(&target)
+                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 {
@@ -49,10 +57,13 @@ pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emi
                 *id,
                 *span,
                 session_diagnostics::InvalidTargetLint {
-                    name,
+                    name: name.clone(),
                     target: target.plural_name(),
-                    applied: applied.clone(),
+                    applied: DiagArgValue::StrListSepByAnd(
+                        applied.into_iter().map(|i| Cow::Owned(i.to_string())).collect(),
+                    ),
                     only,
+                    attr_span: *span,
                 },
             ),
     }
diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs
index aecaae947c9..6d3cf684296 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::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::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,235 @@ 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 uninterpolated_span = self.parser.token_uninterpolated_span();
+        let Some(token_lit) = self.parser.eat_token_lit() else {
+            return self.parser.handle_missing_lit(Parser::mk_meta_item_lit_char);
+        };
 
-                // 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();
-                    } else {
-                        break;
-                    }
-                }
-                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
+        let lit = match MetaItemLit::from_token_lit(token_lit, self.parser.prev_token.span) {
+            Ok(lit) => lit,
+            Err(err) => {
+                let guar =
+                    report_lit_error(&self.parser.psess, err, token_lit, uninterpolated_span);
+                // Pack possible quotes and prefixes from the original literal into
+                // the error literal's symbol so they can be pretty-printed faithfully.
+                let suffixless_lit = token::Lit::new(token_lit.kind, token_lit.symbol, None);
+                let symbol = Symbol::intern(&suffixless_lit.to_string());
+                let token_lit = token::Lit::new(token::Err(guar), symbol, token_lit.suffix);
+                MetaItemLit::from_token_lit(token_lit, uninterpolated_span).unwrap()
             }
+        };
+
+        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 }),
+            );
         }
-    }
 
-    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()
-            }
-            Some(TokenTree::Token(token, _)) => MetaItemLit::from_token(token),
-            _ => None,
+        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 })
     }
 
-    /// 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()
+    fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> {
+        match self.parse_unsuffixed_meta_item_lit() {
+            Ok(lit) => return Ok(MetaItemOrLitParser::Lit(lit)),
+            Err(err) => err.cancel(), // we provide a better error below
+        }
+
+        match self.parse_attr_item() {
+            Ok(mi) => return Ok(MetaItemOrLitParser::MetaItemParser(mi)),
+            Err(err) => err.cancel(), // we provide a better error below
+        }
+
+        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
         {
-            self.inside_delimiters.next();
-            return MetaItemListParserContext {
-                inside_delimiters: inner_tokens.iter().peekable(),
-                dcx: self.dcx,
+            let before = self.parser.token.span.shrink_to_lo();
+            while let token::Ident(..) = self.parser.token.kind {
+                self.parser.bump();
             }
-            .next();
+            err.quote_ident_sugg = Some(InvalidMetaItemQuoteIdentSugg {
+                before,
+                after: self.parser.prev_token.span.shrink_to_hi(),
+            });
         }
 
-        // 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 },
-        }))
+        if self.parser.token == token::Minus
+            && self
+                .parser
+                .look_ahead(1, |t| matches!(t.kind, rustc_ast::token::TokenKind::Literal { .. }))
+        {
+            err.remove_neg_sugg =
+                Some(InvalidMetaItemRemoveNegSugg { negative_sign: self.parser.token.span });
+            self.parser.bump();
+            self.parser.bump();
+        }
+
+        Err(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 c65937b35b3..72bee0ddfbf 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -1,6 +1,6 @@
 use std::num::IntErrorKind;
 
-use rustc_ast::{self as ast, AttrStyle};
+use rustc_ast::{self as ast, AttrStyle, Path};
 use rustc_errors::codes::*;
 use rustc_errors::{
     Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
@@ -485,10 +485,12 @@ pub(crate) struct EmptyAttributeList {
 #[warning]
 #[help]
 pub(crate) struct InvalidTargetLint {
-    pub name: Symbol,
+    pub name: AttrPath,
     pub target: &'static str,
-    pub applied: String,
+    pub applied: DiagArgValue,
     pub only: &'static str,
+    #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")]
+    pub attr_span: Span,
 }
 
 #[derive(Diagnostic)]
@@ -496,10 +498,11 @@ pub(crate) struct InvalidTargetLint {
 #[diag(attr_parsing_invalid_target)]
 pub(crate) struct InvalidTarget {
     #[primary_span]
+    #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")]
     pub span: Span,
-    pub name: Symbol,
+    pub name: AttrPath,
     pub target: &'static str,
-    pub applied: String,
+    pub applied: DiagArgValue,
     pub only: &'static str,
 }
 
@@ -555,7 +558,7 @@ pub(crate) struct LinkOrdinalOutOfRange {
     pub ordinal: u128,
 }
 
-pub(crate) enum AttributeParseErrorReason {
+pub(crate) enum AttributeParseErrorReason<'a> {
     ExpectedNoArgs,
     ExpectedStringLiteral {
         byte_string: Option<Span>,
@@ -568,7 +571,7 @@ pub(crate) enum AttributeParseErrorReason {
     ExpectedNameValue(Option<Symbol>),
     DuplicateKey(Symbol),
     ExpectedSpecificArgument {
-        possibilities: Vec<&'static str>,
+        possibilities: &'a [Symbol],
         strings: bool,
         /// Should we tell the user to write a list when they didn't?
         list: bool,
@@ -576,16 +579,16 @@ pub(crate) enum AttributeParseErrorReason {
     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();
 
@@ -654,7 +657,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                 list: false,
             } => {
                 let quote = if strings { '"' } else { '`' };
-                match possibilities.as_slice() {
+                match possibilities {
                     &[] => {}
                     &[x] => {
                         diag.span_label(
@@ -684,7 +687,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                 list: true,
             } => {
                 let quote = if strings { '"' } else { '`' };
-                match possibilities.as_slice() {
+                match possibilities {
                     &[] => {}
                     &[x] => {
                         diag.span_label(
@@ -734,3 +737,92 @@ 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,
+}
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..9568b791b3f
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/target_checking.rs
@@ -0,0 +1,247 @@
+use std::borrow::Cow;
+
+use rustc_errors::DiagArgValue;
+use rustc_feature::Features;
+use rustc_hir::lints::{AttributeLint, AttributeLintKind};
+use rustc_hir::{AttrPath, MethodKind, Target};
+use rustc_span::Span;
+
+use crate::AttributeParser;
+use crate::context::Stage;
+use crate::session_diagnostics::InvalidTarget;
+
+#[derive(Debug)]
+pub(crate) enum AllowedTargets {
+    AllowList(&'static [Policy]),
+    AllowListWarnRest(&'static [Policy]),
+}
+
+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
+                }
+            }
+        }
+    }
+
+    pub(crate) fn allowed_targets(&self) -> Vec<Target> {
+        match self {
+            AllowedTargets::AllowList(list) => list,
+            AllowedTargets::AllowListWarnRest(list) => list,
+        }
+        .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<S: Stage> AttributeParser<'_, S> {
+    pub(crate) fn check_target(
+        &self,
+        attr_name: AttrPath,
+        attr_span: Span,
+        allowed_targets: &AllowedTargets,
+        target: Target,
+        target_id: S::Id,
+        mut emit_lint: impl FnMut(AttributeLint<S::Id>),
+    ) {
+        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, self.features);
+                emit_lint(AttributeLint {
+                    id: target_id,
+                    span: attr_span,
+                    kind: AttributeLintKind::InvalidTarget {
+                        name: attr_name,
+                        target,
+                        only: if only { "only " } else { "" },
+                        applied,
+                    },
+                });
+            }
+            AllowedResult::Error => {
+                let allowed_targets = allowed_targets.allowed_targets();
+                let (applied, only) =
+                    allowed_targets_applied(allowed_targets, target, self.features);
+                self.dcx().emit_err(InvalidTarget {
+                    span: attr_span,
+                    name: attr_name,
+                    target: target.plural_name(),
+                    only: if only { "only " } else { "" },
+                    applied: DiagArgValue::StrListSepByAnd(
+                        applied.into_iter().map(Cow::Owned).collect(),
+                    ),
+                });
+            }
+        }
+    }
+}
+
+/// 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()
+}