diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 556f50a85af7d..55b79b63c9ee2 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1164,6 +1164,22 @@ impl AttrPath {
             span: path.span,
         }
     }
+
+    /// For a single-segment attribute (i.e., `#[attr]` and not `#[path::atrr]`),
+    /// return the name of the attribute; otherwise, returns `None`.
+    pub fn name(&self) -> Option<Symbol> {
+        self.ident().map(|ident| ident.name)
+    }
+
+    /// For a single-segment attribute, returns its ident; otherwise, returns `None`.
+    pub fn ident(&self) -> Option<Ident> {
+        if let [ident] = self.segments.as_ref() { Some(*ident) } else { None }
+    }
+
+    #[inline]
+    pub fn has_name(&self, name: Symbol) -> bool {
+        self.name() == Some(name)
+    }
 }
 
 impl fmt::Display for AttrPath {
@@ -1206,27 +1222,48 @@ pub enum Attribute {
     Unparsed(Box<AttrItem>),
 }
 
+pub fn find_attr_items_by_name(
+    attrs: &[Attribute],
+    name: Symbol,
+) -> impl Iterator<Item = &AttrItem> {
+    attrs.iter().filter_map(move |attr| match attr {
+        Attribute::Unparsed(attr_item) if attr_item.has_name(name) => Some(&**attr_item),
+        _ => None,
+    })
+}
+
 impl Attribute {
-    pub fn get_normal_item(&self) -> &AttrItem {
+    pub fn attr_item(self) -> Option<AttrItem> {
+        match self {
+            Attribute::Parsed(_) => None,
+            Attribute::Unparsed(attr_item) => Some(*attr_item),
+        }
+    }
+    pub fn attr_item_ref(&self) -> Option<&AttrItem> {
+        match self {
+            Attribute::Parsed(_) => None,
+            Attribute::Unparsed(attr_item) => Some(attr_item),
+        }
+    }
+
+    pub fn unwrap_attr_item_ref(&self) -> &AttrItem {
         match &self {
             Attribute::Unparsed(normal) => &normal,
             _ => panic!("unexpected parsed attribute"),
         }
     }
 
-    pub fn unwrap_normal_item(self) -> AttrItem {
+    pub fn unwrap_attr_item(self) -> AttrItem {
         match self {
             Attribute::Unparsed(normal) => *normal,
             _ => panic!("unexpected parsed attribute"),
         }
     }
-
+}
+impl AttrItem {
     pub fn value_lit(&self) -> Option<&MetaItemLit> {
         match &self {
-            Attribute::Unparsed(n) => match n.as_ref() {
-                AttrItem { args: AttrArgs::Eq { eq_span: _, expr }, .. } => Some(expr),
-                _ => None,
-            },
+            AttrItem { args: AttrArgs::Eq { eq_span: _, expr }, .. } => Some(expr),
             _ => None,
         }
     }
@@ -1256,12 +1293,18 @@ impl AttributeExt for Attribute {
 
     #[inline]
     fn value_str(&self) -> Option<Symbol> {
-        self.value_lit().and_then(|x| x.value_str())
+        match self {
+            Attribute::Parsed(_) => None,
+            Attribute::Unparsed(attr_item) => attr_item.value_lit().and_then(|x| x.value_str()),
+        }
     }
 
     #[inline]
     fn value_span(&self) -> Option<Span> {
-        self.value_lit().map(|i| i.span)
+        match self {
+            Attribute::Parsed(_) => None,
+            Attribute::Unparsed(attr_item) => attr_item.value_lit().map(|i| i.span),
+        }
     }
 
     /// For a single-segment attribute, returns its name; otherwise, returns `None`.
@@ -1355,6 +1398,239 @@ impl AttributeExt for Attribute {
     }
 }
 
+impl AttributeExt for AttrItem {
+    #[inline]
+    fn id(&self) -> AttrId {
+        self.id.attr_id
+    }
+
+    #[inline]
+    fn meta_item_list(&self) -> Option<ThinVec<ast::MetaItemInner>> {
+        match &self {
+            AttrItem { args: AttrArgs::Delimited(d), .. } => {
+                ast::MetaItemKind::list_from_tokens(d.tokens.clone())
+            }
+            _ => None,
+        }
+    }
+
+    #[inline]
+    fn value_str(&self) -> Option<Symbol> {
+        self.value_lit().and_then(|x| x.value_str())
+    }
+
+    #[inline]
+    fn value_span(&self) -> Option<Span> {
+        self.value_lit().map(|i| i.span)
+    }
+
+    /// For a single-segment attribute, returns its name; otherwise, returns `None`.
+    #[inline]
+    fn ident(&self) -> Option<Ident> {
+        self.path.ident()
+    }
+
+    #[inline]
+    fn path_matches(&self, name: &[Symbol]) -> bool {
+        self.path.segments.len() == name.len()
+            && self.path.segments.iter().zip(name).all(|(s, n)| s.name == *n)
+    }
+
+    #[inline]
+    fn is_doc_comment(&self) -> bool {
+        false
+    }
+
+    #[inline]
+    fn span(&self) -> Span {
+        self.span
+    }
+
+    #[inline]
+    fn is_word(&self) -> bool {
+        matches!(self.args, AttrArgs::Empty)
+    }
+
+    #[inline]
+    fn ident_path(&self) -> Option<SmallVec<[Ident; 1]>> {
+        Some(self.path.segments.iter().copied().collect())
+    }
+
+    #[inline]
+    fn doc_str(&self) -> Option<Symbol> {
+        if self.has_name(sym::doc) { self.value_str() } else { None }
+    }
+    #[inline]
+    fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
+        if self.has_name(sym::doc) {
+            self.value_str().map(|s| (s, CommentKind::Line))
+        } else {
+            None
+        }
+    }
+
+    #[inline]
+    fn style(&self) -> AttrStyle {
+        self.style
+    }
+}
+
+// FIXME(fn_delegation): use function delegation instead of manually forwarding
+impl AttributeExt for &'_ AttrItem {
+    #[inline]
+    fn id(&self) -> AttrId {
+        <AttrItem as AttributeExt>::id(self)
+    }
+
+    #[inline]
+    fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>> {
+        <AttrItem as AttributeExt>::meta_item_list(self)
+    }
+
+    #[inline]
+    fn value_str(&self) -> Option<Symbol> {
+        <AttrItem as AttributeExt>::value_str(self)
+    }
+
+    #[inline]
+    fn value_span(&self) -> Option<Span> {
+        <AttrItem as AttributeExt>::value_span(self)
+    }
+
+    #[inline]
+    fn ident(&self) -> Option<Ident> {
+        <AttrItem as AttributeExt>::ident(self)
+    }
+
+    #[inline]
+    fn path_matches(&self, name: &[Symbol]) -> bool {
+        <AttrItem as AttributeExt>::path_matches(self, name)
+    }
+
+    #[inline]
+    fn is_doc_comment(&self) -> bool {
+        <AttrItem as AttributeExt>::is_doc_comment(self)
+    }
+
+    #[inline]
+    fn span(&self) -> Span {
+        <AttrItem as AttributeExt>::span(self)
+    }
+
+    #[inline]
+    fn is_word(&self) -> bool {
+        <AttrItem as AttributeExt>::is_word(self)
+    }
+
+    #[inline]
+    fn ident_path(&self) -> Option<SmallVec<[Ident; 1]>> {
+        <AttrItem as AttributeExt>::ident_path(self)
+    }
+
+    #[inline]
+    fn doc_str(&self) -> Option<Symbol> {
+        <AttrItem as AttributeExt>::doc_str(self)
+    }
+
+    #[inline]
+    fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
+        <AttrItem as AttributeExt>::doc_str_and_comment_kind(self)
+    }
+
+    #[inline]
+    fn style(&self) -> AttrStyle {
+        <AttrItem as AttributeExt>::style(self)
+    }
+}
+
+// FIXME(fn_delegation): use function delegation instead of manually forwarding
+impl AttrItem {
+    #[inline]
+    pub fn id(&self) -> AttrId {
+        <AttrItem as AttributeExt>::id(self)
+    }
+
+    #[inline]
+    pub fn path(&self) -> SmallVec<[Symbol; 1]> {
+        <AttrItem as AttributeExt>::path(self)
+    }
+
+    #[inline]
+    pub fn name(&self) -> Option<Symbol> {
+        <AttrItem as AttributeExt>::name(self)
+    }
+
+    #[inline]
+    pub fn has_name(&self, name: Symbol) -> bool {
+        <AttrItem as AttributeExt>::has_name(self, name)
+    }
+
+    #[inline]
+    pub fn has_any_name(&self, names: &[Symbol]) -> bool {
+        <AttrItem as AttributeExt>::has_any_name(self, names)
+    }
+
+    #[inline]
+    pub fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>> {
+        <AttrItem as AttributeExt>::meta_item_list(self)
+    }
+
+    #[inline]
+    pub fn value_str(&self) -> Option<Symbol> {
+        <AttrItem as AttributeExt>::value_str(self)
+    }
+
+    #[inline]
+    pub fn value_span(&self) -> Option<Span> {
+        <AttrItem as AttributeExt>::value_span(self)
+    }
+
+    #[inline]
+    pub fn ident(&self) -> Option<Ident> {
+        <AttrItem as AttributeExt>::ident(self)
+    }
+
+    #[inline]
+    pub fn path_matches(&self, name: &[Symbol]) -> bool {
+        <AttrItem as AttributeExt>::path_matches(self, name)
+    }
+
+    #[inline]
+    pub fn is_doc_comment(&self) -> bool {
+        <AttrItem as AttributeExt>::is_doc_comment(self)
+    }
+
+    #[inline]
+    pub fn span(&self) -> Span {
+        <AttrItem as AttributeExt>::span(self)
+    }
+
+    #[inline]
+    pub fn is_word(&self) -> bool {
+        <AttrItem as AttributeExt>::is_word(self)
+    }
+
+    #[inline]
+    pub fn ident_path(&self) -> Option<SmallVec<[Ident; 1]>> {
+        <AttrItem as AttributeExt>::ident_path(self)
+    }
+
+    #[inline]
+    pub fn doc_str(&self) -> Option<Symbol> {
+        <AttrItem as AttributeExt>::doc_str(self)
+    }
+
+    #[inline]
+    pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
+        <AttrItem as AttributeExt>::doc_str_and_comment_kind(self)
+    }
+
+    #[inline]
+    pub fn style(&self) -> AttrStyle {
+        <AttrItem as AttributeExt>::style(self)
+    }
+}
+
 // FIXME(fn_delegation): use function delegation instead of manually forwarding
 impl Attribute {
     #[inline]
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index fc507285860ed..0c7c28d14c9a9 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -101,32 +101,36 @@ impl<'a> State<'a> {
         }
 
         for attr in attrs {
-            self.print_attribute_inline(attr, style);
+            match &attr {
+                hir::Attribute::Unparsed(attr) => self.print_attr_item_inline(&**attr, style),
+                hir::Attribute::Parsed(attr) => self.print_parsed_attribute_inline(attr, style),
+            }
         }
         self.hardbreak_if_not_bol();
     }
 
-    fn print_attribute_inline(&mut self, attr: &hir::Attribute, style: AttrStyle) {
-        match &attr {
-            hir::Attribute::Unparsed(unparsed) => {
-                self.maybe_print_comment(unparsed.span.lo());
-                match style {
-                    ast::AttrStyle::Inner => self.word("#!["),
-                    ast::AttrStyle::Outer => self.word("#["),
-                }
-                self.print_attr_item(&unparsed, unparsed.span);
-                self.word("]");
-                self.hardbreak()
-            }
-            hir::Attribute::Parsed(AttributeKind::DocComment { style, kind, comment, .. }) => {
+    fn print_attr_item_inline(&mut self, unparsed: &hir::AttrItem, style: AttrStyle) {
+        self.maybe_print_comment(unparsed.span.lo());
+        match style {
+            ast::AttrStyle::Inner => self.word("#!["),
+            ast::AttrStyle::Outer => self.word("#["),
+        }
+        self.print_attr_item(&unparsed, unparsed.span);
+        self.word("]");
+        self.hardbreak()
+    }
+
+    fn print_parsed_attribute_inline(&mut self, attr: &AttributeKind, _style: AttrStyle) {
+        match attr {
+            AttributeKind::DocComment { style, kind, comment, .. } => {
                 self.word(rustc_ast_pretty::pprust::state::doc_comment_to_string(
                     *kind, *style, *comment,
                 ));
                 self.hardbreak()
             }
-            hir::Attribute::Parsed(pa) => {
+            _ => {
                 self.word("#[attr = ");
-                pa.print_attribute(self);
+                attr.print_attribute(self);
                 self.word("]");
                 self.hardbreak()
             }
@@ -298,8 +302,18 @@ where
     printer.s.eof()
 }
 
+pub fn parsed_attribute_to_string(ann: &dyn PpAnn, attr: &AttributeKind) -> String {
+    to_string(ann, |s| s.print_parsed_attribute_inline(attr, AttrStyle::Outer))
+}
+pub fn attr_item_to_string(ann: &dyn PpAnn, attr: &hir::AttrItem) -> String {
+    to_string(ann, |s| s.print_attr_item_inline(attr, AttrStyle::Outer))
+}
+
 pub fn attribute_to_string(ann: &dyn PpAnn, attr: &hir::Attribute) -> String {
-    to_string(ann, |s| s.print_attribute_inline(attr, AttrStyle::Outer))
+    match attr {
+        hir::Attribute::Parsed(attr) => parsed_attribute_to_string(ann, attr),
+        hir::Attribute::Unparsed(attr) => attr_item_to_string(ann, attr),
+    }
 }
 
 pub fn ty_to_string(ann: &dyn PpAnn, ty: &hir::Ty<'_>) -> String {
diff --git a/compiler/rustc_incremental/src/assert_dep_graph.rs b/compiler/rustc_incremental/src/assert_dep_graph.rs
index 0e04a2a784ec8..9f432b67320e2 100644
--- a/compiler/rustc_incremental/src/assert_dep_graph.rs
+++ b/compiler/rustc_incremental/src/assert_dep_graph.rs
@@ -107,7 +107,7 @@ struct IfThisChanged<'tcx> {
 impl<'tcx> IfThisChanged<'tcx> {
     fn argument(&self, attr: &hir::Attribute) -> Option<Symbol> {
         let mut value = None;
-        for list_item in attr.meta_item_list().unwrap_or_default() {
+        for list_item in attr.attr_item_ref().and_then(|a| a.meta_item_list()).unwrap_or_default() {
             match list_item.ident() {
                 Some(ident) if list_item.is_word() && value.is_none() => value = Some(ident.name),
                 _ =>
@@ -125,47 +125,57 @@ impl<'tcx> IfThisChanged<'tcx> {
         let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
         let attrs = self.tcx.hir_attrs(hir_id);
         for attr in attrs {
-            if attr.has_name(sym::rustc_if_this_changed) {
-                let dep_node_interned = self.argument(attr);
-                let dep_node = match dep_node_interned {
-                    None => DepNode::from_def_path_hash(
-                        self.tcx,
-                        def_path_hash,
-                        dep_kinds::opt_hir_owner_nodes,
-                    ),
-                    Some(n) => {
-                        match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
-                            Ok(n) => n,
-                            Err(()) => self.tcx.dcx().emit_fatal(errors::UnrecognizedDepNode {
-                                span: attr.span(),
-                                name: n,
-                            }),
+            match attr {
+                hir::Attribute::Unparsed(attr_item)
+                    if attr_item.has_name(sym::rustc_if_this_changed) =>
+                {
+                    let dep_node_interned = self.argument(attr);
+                    let dep_node = match dep_node_interned {
+                        None => DepNode::from_def_path_hash(
+                            self.tcx,
+                            def_path_hash,
+                            dep_kinds::opt_hir_owner_nodes,
+                        ),
+                        Some(n) => {
+                            match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
+                                Ok(n) => n,
+                                Err(()) => self.tcx.dcx().emit_fatal(errors::UnrecognizedDepNode {
+                                    span: attr_item.span(),
+                                    name: n,
+                                }),
+                            }
                         }
-                    }
-                };
-                self.if_this_changed.push((attr.span(), def_id.to_def_id(), dep_node));
-            } else if attr.has_name(sym::rustc_then_this_would_need) {
-                let dep_node_interned = self.argument(attr);
-                let dep_node = match dep_node_interned {
-                    Some(n) => {
-                        match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
-                            Ok(n) => n,
-                            Err(()) => self.tcx.dcx().emit_fatal(errors::UnrecognizedDepNode {
-                                span: attr.span(),
-                                name: n,
-                            }),
+                    };
+                    self.if_this_changed.push((attr_item.span(), def_id.to_def_id(), dep_node));
+                }
+                hir::Attribute::Unparsed(attr_item)
+                    if attr_item.has_name(sym::rustc_then_this_would_need) =>
+                {
+                    let dep_node_interned = self.argument(attr);
+                    let dep_node = match dep_node_interned {
+                        Some(n) => {
+                            match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
+                                Ok(n) => n,
+                                Err(()) => self.tcx.dcx().emit_fatal(errors::UnrecognizedDepNode {
+                                    span: attr_item.span(),
+                                    name: n,
+                                }),
+                            }
                         }
-                    }
-                    None => {
-                        self.tcx.dcx().emit_fatal(errors::MissingDepNode { span: attr.span() });
-                    }
-                };
-                self.then_this_would_need.push((
-                    attr.span(),
-                    dep_node_interned.unwrap(),
-                    hir_id,
-                    dep_node,
-                ));
+                        None => {
+                            self.tcx
+                                .dcx()
+                                .emit_fatal(errors::MissingDepNode { span: attr_item.span() });
+                        }
+                    };
+                    self.then_this_would_need.push((
+                        attr_item.span(),
+                        dep_node_interned.unwrap(),
+                        hir_id,
+                        dep_node,
+                    ));
+                }
+                _ => {}
             }
         }
     }
diff --git a/compiler/rustc_incremental/src/persist/dirty_clean.rs b/compiler/rustc_incremental/src/persist/dirty_clean.rs
index 64166255fa485..c95fb81c638bf 100644
--- a/compiler/rustc_incremental/src/persist/dirty_clean.rs
+++ b/compiler/rustc_incremental/src/persist/dirty_clean.rs
@@ -19,12 +19,14 @@
 //! Errors are reported if we are in the suitable configuration but
 //! the required condition is not met.
 
+use rustc_ast::attr::AttributeExt;
 use rustc_ast::{self as ast, MetaItemInner};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::unord::UnordSet;
 use rustc_hir::def_id::LocalDefId;
 use rustc_hir::{
-    Attribute, ImplItemKind, ItemKind as HirItem, Node as HirNode, TraitItemKind, intravisit,
+    AttrItem, Attribute, ImplItemKind, ItemKind as HirItem, Node as HirNode, TraitItemKind,
+    intravisit,
 };
 use rustc_middle::dep_graph::{DepNode, DepNodeExt, label_strs};
 use rustc_middle::hir::nested_filter;
@@ -182,7 +184,7 @@ struct DirtyCleanVisitor<'tcx> {
 
 impl<'tcx> DirtyCleanVisitor<'tcx> {
     /// Possibly "deserialize" the attribute into a clean/dirty assertion
-    fn assertion_maybe(&mut self, item_id: LocalDefId, attr: &Attribute) -> Option<Assertion> {
+    fn assertion_maybe(&mut self, item_id: LocalDefId, attr: &AttrItem) -> Option<Assertion> {
         assert!(attr.has_name(sym::rustc_clean));
         if !check_config(self.tcx, attr) {
             // skip: not the correct `cfg=`
@@ -193,7 +195,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
     }
 
     /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
-    fn assertion_auto(&mut self, item_id: LocalDefId, attr: &Attribute) -> Assertion {
+    fn assertion_auto(&mut self, item_id: LocalDefId, attr: &AttrItem) -> Assertion {
         let (name, mut auto) = self.auto_labels(item_id, attr);
         let except = self.except(attr);
         let loaded_from_disk = self.loaded_from_disk(attr);
@@ -206,7 +208,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
     }
 
     /// `loaded_from_disk=` attribute value
-    fn loaded_from_disk(&self, attr: &Attribute) -> Labels {
+    fn loaded_from_disk(&self, attr: &AttrItem) -> Labels {
         for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) {
             if item.has_name(LOADED_FROM_DISK) {
                 let value = expect_associated_value(self.tcx, &item);
@@ -218,7 +220,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
     }
 
     /// `except=` attribute value
-    fn except(&self, attr: &Attribute) -> Labels {
+    fn except(&self, attr: &AttrItem) -> Labels {
         for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) {
             if item.has_name(EXCEPT) {
                 let value = expect_associated_value(self.tcx, &item);
@@ -231,7 +233,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
 
     /// Return all DepNode labels that should be asserted for this item.
     /// index=0 is the "name" used for error messages
-    fn auto_labels(&mut self, item_id: LocalDefId, attr: &Attribute) -> (&'static str, Labels) {
+    fn auto_labels(&mut self, item_id: LocalDefId, attr: &AttrItem) -> (&'static str, Labels) {
         let node = self.tcx.hir_node_by_def_id(item_id);
         let (name, labels) = match node {
             HirNode::Item(item) => {
@@ -394,7 +396,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
 
 /// Given a `#[rustc_clean]` attribute, scan for a `cfg="foo"` attribute and check whether we have
 /// a cfg flag called `foo`.
-fn check_config(tcx: TyCtxt<'_>, attr: &Attribute) -> bool {
+fn check_config(tcx: TyCtxt<'_>, attr: &AttrItem) -> bool {
     debug!("check_config(attr={:?})", attr);
     let config = &tcx.sess.psess.config;
     debug!("check_config: config={:?}", config);
@@ -430,11 +432,11 @@ fn expect_associated_value(tcx: TyCtxt<'_>, item: &MetaItemInner) -> Symbol {
 /// nodes.
 struct FindAllAttrs<'tcx> {
     tcx: TyCtxt<'tcx>,
-    found_attrs: Vec<&'tcx Attribute>,
+    found_attrs: Vec<&'tcx AttrItem>,
 }
 
 impl<'tcx> FindAllAttrs<'tcx> {
-    fn is_active_attr(&mut self, attr: &Attribute) -> bool {
+    fn is_active_attr(&mut self, attr: &AttrItem) -> bool {
         if attr.has_name(sym::rustc_clean) && check_config(self.tcx, attr) {
             return true;
         }
@@ -460,8 +462,9 @@ impl<'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'tcx> {
     }
 
     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
-        if self.is_active_attr(attr) {
-            self.found_attrs.push(attr);
+        match attr {
+            Attribute::Unparsed(attr) if self.is_active_attr(attr) => self.found_attrs.push(attr),
+            _ => {}
         }
     }
 }
diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs
index 68adfb3cdb388..50423373bf296 100644
--- a/compiler/rustc_middle/src/ty/instance.rs
+++ b/compiler/rustc_middle/src/ty/instance.rs
@@ -291,9 +291,9 @@ impl<'tcx> InstanceKind<'tcx> {
     pub fn get_attrs(
         &self,
         tcx: TyCtxt<'tcx>,
-        attr: Symbol,
-    ) -> impl Iterator<Item = &'tcx hir::Attribute> {
-        tcx.get_attrs(self.def_id(), attr)
+        name: Symbol,
+    ) -> impl Iterator<Item = &'tcx hir::AttrItem> {
+        tcx.get_attrs(self.def_id(), name)
     }
 
     /// Returns `true` if the LLVM version of this instance is unconditionally
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 0402d09882254..e7ec41498d8f5 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -1795,9 +1795,14 @@ impl<'tcx> TyCtxt<'tcx> {
     pub fn get_attrs(
         self,
         did: impl Into<DefId>,
-        attr: Symbol,
-    ) -> impl Iterator<Item = &'tcx hir::Attribute> {
-        self.get_all_attrs(did).filter(move |a: &&hir::Attribute| a.has_name(attr))
+        name: Symbol,
+    ) -> impl Iterator<Item = &'tcx hir::AttrItem> {
+        self.get_all_attrs(did).filter_map(move |a| match a {
+            hir::Attribute::Unparsed(attr_item) if attr_item.path.has_name(name) => {
+                Some(&**attr_item)
+            }
+            _ => None,
+        })
     }
 
     /// Gets all attributes.
@@ -1827,7 +1832,7 @@ impl<'tcx> TyCtxt<'tcx> {
         self,
         did: impl Into<DefId>,
         attr: Symbol,
-    ) -> Option<&'tcx hir::Attribute> {
+    ) -> Option<&'tcx hir::AttrItem> {
         let did: DefId = did.into();
         if did.as_local().is_some() {
             // it's a crate local item, we need to check feature flags
@@ -1840,9 +1845,12 @@ impl<'tcx> TyCtxt<'tcx> {
             // we filter out unstable diagnostic attributes before
             // encoding attributes
             debug_assert!(rustc_feature::encode_cross_crate(attr));
-            self.attrs_for_def(did)
-                .iter()
-                .find(|a| matches!(a.path().as_ref(), [sym::diagnostic, a] if *a == attr))
+            self.attrs_for_def(did).iter().find_map(|a| match a {
+                hir::Attribute::Unparsed(ai) if  matches!(ai.path().as_ref(), [sym::diagnostic, a] if *a == attr) => {
+                    Some(&**ai)
+                }
+                _ => None
+            })
         }
     }
 
@@ -1850,16 +1858,23 @@ impl<'tcx> TyCtxt<'tcx> {
         self,
         did: DefId,
         attr: &[Symbol],
-    ) -> impl Iterator<Item = &'tcx hir::Attribute> {
-        let filter_fn = move |a: &&hir::Attribute| a.path_matches(attr);
+    ) -> impl Iterator<Item = &'tcx hir::AttrItem> {
+        let filter_fn = move |a: &'tcx hir::Attribute| -> Option<&'tcx hir::AttrItem> {
+            match a {
+                hir::Attribute::Unparsed(attr_item) if attr_item.path_matches(attr) => {
+                    Some(&**attr_item)
+                }
+                _ => None,
+            }
+        };
         if let Some(did) = did.as_local() {
-            self.hir_attrs(self.local_def_id_to_hir_id(did)).iter().filter(filter_fn)
+            self.hir_attrs(self.local_def_id_to_hir_id(did)).iter().filter_map(filter_fn)
         } else {
-            self.attrs_for_def(did).iter().filter(filter_fn)
+            self.attrs_for_def(did).iter().filter_map(filter_fn)
         }
     }
 
-    pub fn get_attr(self, did: impl Into<DefId>, attr: Symbol) -> Option<&'tcx hir::Attribute> {
+    pub fn get_attr(self, did: impl Into<DefId>, attr: Symbol) -> Option<&'tcx hir::AttrItem> {
         if cfg!(debug_assertions) && !rustc_feature::is_valid_for_get_attr(attr) {
             let did: DefId = did.into();
             bug!("get_attr: unexpected called with DefId `{:?}`, attr `{:?}`", did, attr);
diff --git a/compiler/rustc_passes/src/abi_test.rs b/compiler/rustc_passes/src/abi_test.rs
index b139ed6a66c38..5b7d158bfbcb7 100644
--- a/compiler/rustc_passes/src/abi_test.rs
+++ b/compiler/rustc_passes/src/abi_test.rs
@@ -1,4 +1,4 @@
-use rustc_hir::Attribute;
+use rustc_hir::AttrItem;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalDefId;
 use rustc_middle::span_bug;
@@ -49,7 +49,7 @@ fn unwrap_fn_abi<'tcx>(
     }
 }
 
-fn dump_abi_of_fn_item(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) {
+fn dump_abi_of_fn_item(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &AttrItem) {
     let typing_env = ty::TypingEnv::post_analysis(tcx, item_def_id);
     let args = GenericArgs::identity_for_item(tcx, item_def_id);
     let instance = match Instance::try_resolve(tcx, typing_env, item_def_id.into(), args) {
@@ -109,7 +109,7 @@ fn test_abi_eq<'tcx>(abi1: &'tcx FnAbi<'tcx, Ty<'tcx>>, abi2: &'tcx FnAbi<'tcx,
         && abi1.args.iter().zip(abi2.args.iter()).all(|(arg1, arg2)| arg1.eq_abi(arg2))
 }
 
-fn dump_abi_of_fn_type(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) {
+fn dump_abi_of_fn_type(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &AttrItem) {
     let typing_env = ty::TypingEnv::post_analysis(tcx, item_def_id);
     let ty = tcx.type_of(item_def_id).instantiate_identity();
     let span = tcx.def_span(item_def_id);
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index dddbf65db72ea..c27b707496572 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -18,8 +18,8 @@ use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalModDefId;
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{
-    self as hir, self, AssocItemKind, Attribute, CRATE_HIR_ID, CRATE_OWNER_ID, FnSig, ForeignItem,
-    HirId, Item, ItemKind, MethodKind, Safety, Target, TraitItem,
+    self as hir, self, AssocItemKind, AttrItem, Attribute, CRATE_HIR_ID, CRATE_OWNER_ID, FnSig,
+    ForeignItem, HirId, Item, ItemKind, MethodKind, Safety, Target, TraitItem,
 };
 use rustc_macros::LintDiagnostic;
 use rustc_middle::hir::nested_filter;
@@ -107,7 +107,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_attributes(
         &self,
         hir_id: HirId,
-        span: Span,
+        item_span: Span,
         target: Target,
         item: Option<ItemLike<'_>>,
     ) {
@@ -128,15 +128,46 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                     .check_allow_internal_unstable(
                         hir_id,
                         syms.first().unwrap().1,
-                        span,
+                        item_span,
                         target,
                         attrs,
                     ),
-                Attribute::Parsed(AttributeKind::AllowConstFnUnstable { .. }) => {
-                    self.check_rustc_allow_const_fn_unstable(hir_id, attr, span, target)
+                Attribute::Parsed(AttributeKind::AllowConstFnUnstable(_)) => {
+                    // FIXME: this is incorrect
+                    let attr_span = item_span;
+                    match target {
+                        Target::Fn | Target::Method(_)
+                            if self.tcx.is_const_fn(hir_id.expect_owner().to_def_id()) => {}
+                        // FIXME(#80564): We permit struct fields and match arms to have an
+                        // `#[allow_internal_unstable]` attribute with just a lint, because we previously
+                        // erroneously allowed it and some crates used it accidentally, to be compatible
+                        // with crates depending on them, we can't throw an error here.
+                        Target::Field | Target::Arm | Target::MacroDef => self
+                            .inline_attr_str_error_with_macro_def(
+                                hir_id,
+                                attr_span,
+                                "allow_internal_unstable",
+                            ),
+                        _ => {
+                            self.tcx.dcx().emit_err(errors::RustcAllowConstFnUnstable {
+                                attr_span,
+                                span: item_span,
+                            });
+                        }
+                    }
                 }
-                Attribute::Parsed(AttributeKind::Deprecation { .. }) => {
-                    self.check_deprecated(hir_id, attr, span, target)
+                Attribute::Parsed(AttributeKind::Deprecation { span, deprecation: _ }) => {
+                    match target {
+                        Target::Closure | Target::Expression | Target::Statement | Target::Arm => {
+                            self.tcx.emit_node_span_lint(
+                                UNUSED_ATTRIBUTES,
+                                hir_id,
+                                *span,
+                                errors::Deprecated,
+                            );
+                        }
+                        _ => {}
+                    }
                 }
                 Attribute::Parsed(AttributeKind::DocComment { .. }) => { /* `#[doc]` is actually a lot more than just doc comments, so is checked below*/
                 }
@@ -148,9 +179,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                     | AttributeKind::MacroTransparency(_),
                 ) => { /* do nothing  */ }
                 Attribute::Parsed(AttributeKind::AsPtr(attr_span)) => {
-                    self.check_applied_to_fn_or_method(hir_id, *attr_span, span, target)
+                    self.check_applied_to_fn_or_method(hir_id, *attr_span, item_span, target)
                 }
-                Attribute::Unparsed(_) => {
+                Attribute::Unparsed(attr) => {
+                    let attr: &AttrItem = attr;
+                    let span = item_span;
                     match attr.path().as_slice() {
                         [sym::diagnostic, sym::do_not_recommend, ..] => {
                             self.check_do_not_recommend(attr.span(), hir_id, target, attr, item)
@@ -329,49 +362,50 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         }
                         [] => unreachable!(),
                     }
-                }
-            }
 
-            let builtin = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
+                    let builtin =
+                        attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
 
-            if hir_id != CRATE_HIR_ID {
-                if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) =
-                    attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name))
-                {
-                    match attr.style() {
-                        ast::AttrStyle::Outer => self.tcx.emit_node_span_lint(
-                            UNUSED_ATTRIBUTES,
-                            hir_id,
-                            attr.span(),
-                            errors::OuterCrateLevelAttr,
-                        ),
-                        ast::AttrStyle::Inner => self.tcx.emit_node_span_lint(
-                            UNUSED_ATTRIBUTES,
-                            hir_id,
-                            attr.span(),
-                            errors::InnerCrateLevelAttr,
-                        ),
+                    if hir_id != CRATE_HIR_ID {
+                        if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) =
+                            attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name))
+                        {
+                            match attr.style() {
+                                ast::AttrStyle::Outer => self.tcx.emit_node_span_lint(
+                                    UNUSED_ATTRIBUTES,
+                                    hir_id,
+                                    attr.span(),
+                                    errors::OuterCrateLevelAttr,
+                                ),
+                                ast::AttrStyle::Inner => self.tcx.emit_node_span_lint(
+                                    UNUSED_ATTRIBUTES,
+                                    hir_id,
+                                    attr.span(),
+                                    errors::InnerCrateLevelAttr,
+                                ),
+                            }
+                        }
                     }
-                }
-            }
 
-            if let Some(BuiltinAttribute { duplicates, .. }) = builtin {
-                check_duplicates(self.tcx, attr, hir_id, *duplicates, &mut seen);
+                    if let Some(BuiltinAttribute { duplicates, .. }) = builtin {
+                        check_duplicates(self.tcx, attr, hir_id, *duplicates, &mut seen);
+                    }
+                }
             }
 
             self.check_unused_attribute(hir_id, attr)
         }
 
-        self.check_repr(attrs, span, target, item, hir_id);
-        self.check_used(attrs, target, span);
-        self.check_rustc_force_inline(hir_id, attrs, span, target);
+        self.check_repr(attrs, item_span, target, item, hir_id);
+        self.check_used(attrs, target, item_span);
+        self.check_rustc_force_inline(hir_id, attrs, item_span, target);
     }
 
-    fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) {
+    fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr_span: Span, sym: &str) {
         self.tcx.emit_node_span_lint(
             UNUSED_ATTRIBUTES,
             hir_id,
-            attr.span(),
+            attr_span,
             errors::IgnoredAttrWithMacro { sym },
         );
     }
@@ -391,7 +425,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         attr_span: Span,
         hir_id: HirId,
         target: Target,
-        attr: &Attribute,
+        attr: &AttrItem,
         item: Option<ItemLike<'_>>,
     ) {
         if !matches!(target, Target::Impl)
@@ -431,7 +465,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if an `#[inline]` is applied to a function or a closure.
-    fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_inline(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Fn
             | Target::Closure
@@ -456,7 +490,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             ),
             // FIXME(#80564): Same for fields, arms, and macro defs
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "inline")
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "inline")
             }
             _ => {
                 self.dcx().emit_err(errors::InlineNotFnOrClosure {
@@ -486,7 +520,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
     /// Checks that `#[coverage(..)]` is applied to a function/closure/method,
     /// or to an impl block or module.
-    fn check_coverage(&self, attr: &Attribute, target_span: Span, target: Target) {
+    fn check_coverage(&self, attr: &AttrItem, target_span: Span, target: Target) {
         let mut not_fn_impl_mod = None;
         let mut no_body = None;
 
@@ -518,7 +552,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
     /// Checks that `#[optimize(..)]` is applied to a function/closure/method,
     /// or to an impl block or module.
-    fn check_optimize(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_optimize(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         let is_valid = matches!(
             target,
             Target::Fn
@@ -534,7 +568,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_no_sanitize(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_no_sanitize(&self, attr: &AttrItem, span: Span, target: Target) {
         if let Some(list) = attr.meta_item_list() {
             for item in list.iter() {
                 let sym = item.name();
@@ -573,7 +607,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_generic_attr(
         &self,
         hir_id: HirId,
-        attr: &Attribute,
+        attr: &AttrItem,
         target: Target,
         allowed_target: Target,
     ) {
@@ -597,7 +631,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_naked(
         &self,
         hir_id: HirId,
-        attr: &Attribute,
+        attr: &AttrItem,
         span: Span,
         target: Target,
         attrs: &[Attribute],
@@ -662,52 +696,51 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 }
 
                 for other_attr in attrs {
-                    // this covers "sugared doc comments" of the form `/// ...`
-                    // it does not cover `#[doc = "..."]`, which is handled below
-                    if other_attr.is_doc_comment() {
-                        continue;
-                    }
-
-                    // FIXME(jdonszelmann): once naked uses new-style parsing,
-                    // this check can be part of the parser and be removed here
                     match other_attr {
+                        // this covers "sugared doc comments" of the form `/// ...`
+                        // it does not cover `#[doc = "..."]`, which is handled below
+                        Attribute::Parsed(AttributeKind::DocComment { .. }) => {
+                            continue;
+                        }
+                        // FIXME(jdonszelmann): once naked uses new-style parsing,
+                        // this check can be part of the parser and be removed here
                         Attribute::Parsed(
                             AttributeKind::Deprecation { .. } | AttributeKind::Repr { .. },
                         ) => {
                             continue;
                         }
-                        _ => {}
-                    }
-
-                    if other_attr.has_name(sym::target_feature) {
-                        if !self.tcx.features().naked_functions_target_feature() {
-                            feature_err(
+                        Attribute::Parsed(_) => {}
+                        Attribute::Unparsed(other_attr) => {
+                            if other_attr.has_name(sym::target_feature) {
+                                if !self.tcx.features().naked_functions_target_feature() {
+                                    feature_err(
                                 &self.tcx.sess,
                                 sym::naked_functions_target_feature,
                                 other_attr.span(),
                                 "`#[target_feature(/* ... */)]` is currently unstable on `#[naked]` functions",
                             ).emit();
+                                    return;
+                                } else {
+                                    continue;
+                                }
+                            }
 
-                            return;
-                        } else {
-                            continue;
-                        }
-                    }
+                            if !other_attr.has_any_name(ALLOW_LIST)
+                                && !matches!(other_attr.path().as_slice(), [sym::rustfmt, ..])
+                            {
+                                let path = other_attr.path();
+                                let path: Vec<_> = path.iter().map(|s| s.as_str()).collect();
+                                let other_attr_name = path.join("::");
 
-                    if !other_attr.has_any_name(ALLOW_LIST)
-                        && !matches!(other_attr.path().as_slice(), [sym::rustfmt, ..])
-                    {
-                        let path = other_attr.path();
-                        let path: Vec<_> = path.iter().map(|s| s.as_str()).collect();
-                        let other_attr_name = path.join("::");
-
-                        self.dcx().emit_err(errors::NakedFunctionIncompatibleAttribute {
-                            span: other_attr.span(),
-                            naked_span: attr.span(),
-                            attr: other_attr_name,
-                        });
+                                self.dcx().emit_err(errors::NakedFunctionIncompatibleAttribute {
+                                    span: other_attr.span(),
+                                    naked_span: attr.span(),
+                                    attr: other_attr_name,
+                                });
 
-                        return;
+                                return;
+                            }
+                        }
                     }
                 }
             }
@@ -742,7 +775,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[collapse_debuginfo]` is applied to a macro.
-    fn check_collapse_debuginfo(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_collapse_debuginfo(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::MacroDef => {}
             _ => {
@@ -787,7 +820,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
                 for attr in attrs {
-                    self.inline_attr_str_error_with_macro_def(hir_id, attr, "track_caller");
+                    self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "track_caller");
                 }
             }
             _ => {
@@ -804,7 +837,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_non_exhaustive(
         &self,
         hir_id: HirId,
-        attr: &Attribute,
+        attr: &AttrItem,
         span: Span,
         target: Target,
         item: Option<ItemLike<'_>>,
@@ -830,7 +863,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "non_exhaustive");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "non_exhaustive");
             }
             _ => {
                 self.dcx().emit_err(errors::NonExhaustiveWrongLocation {
@@ -842,7 +875,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if the `#[marker]` attribute on an `item` is valid.
-    fn check_marker(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_marker(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Trait => {}
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -850,7 +883,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "marker");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "marker");
             }
             _ => {
                 self.dcx().emit_err(errors::AttrShouldBeAppliedToTrait {
@@ -865,7 +898,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_target_feature(
         &self,
         hir_id: HirId,
-        attr: &Attribute,
+        attr: &AttrItem,
         span: Span,
         target: Target,
         attrs: &[Attribute],
@@ -904,7 +937,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "target_feature");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "target_feature");
             }
             _ => {
                 self.dcx().emit_err(errors::AttrShouldBeAppliedToFn {
@@ -917,7 +950,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if the `#[thread_local]` attribute on `item` is valid.
-    fn check_thread_local(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_thread_local(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::ForeignStatic | Target::Static => {}
             _ => {
@@ -1163,7 +1196,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     /// the first `inline`/`no_inline` attribute.
     fn check_doc_inline(
         &self,
-        attr: &Attribute,
+        attr: &AttrItem,
         meta: &MetaItemInner,
         hir_id: HirId,
         target: Target,
@@ -1203,7 +1236,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
     fn check_doc_masked(
         &self,
-        attr: &Attribute,
+        attr: &AttrItem,
         meta: &MetaItemInner,
         hir_id: HirId,
         target: Target,
@@ -1251,12 +1284,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks that an attribute is used at the crate level. Returns `true` if valid.
-    fn check_attr_crate_level(
-        &self,
-        attr: &Attribute,
-        meta: &MetaItemInner,
-        hir_id: HirId,
-    ) -> bool {
+    fn check_attr_crate_level(&self, attr: &AttrItem, meta: &MetaItemInner, hir_id: HirId) -> bool {
         if hir_id != CRATE_HIR_ID {
             // insert a bang between `#` and `[...`
             let bang_span = attr.span().lo() + BytePos(1);
@@ -1277,7 +1305,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
-    fn check_test_attr(&self, attr: &Attribute, meta: &MetaItemInner, hir_id: HirId) {
+    fn check_test_attr(&self, attr: &AttrItem, meta: &MetaItemInner, hir_id: HirId) {
         if let Some(metas) = meta.meta_item_list() {
             for i_meta in metas {
                 match (i_meta.name(), i_meta.meta_item()) {
@@ -1338,7 +1366,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     /// [`check_doc_inline`]: Self::check_doc_inline
     fn check_doc_attrs(
         &self,
-        attr: &Attribute,
+        attr: &AttrItem,
         hir_id: HirId,
         target: Target,
         specified_inline: &mut Option<(bool, Span)>,
@@ -1487,7 +1515,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Warns against some misuses of `#[pass_by_value]`
-    fn check_pass_by_value(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_pass_by_value(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Struct | Target::Enum | Target::TyAlias => {}
             _ => {
@@ -1496,7 +1524,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_allow_incoherent_impl(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_allow_incoherent_impl(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Method(MethodKind::Inherent) => {}
             _ => {
@@ -1505,7 +1533,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_has_incoherent_inherent_impls(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_has_incoherent_inherent_impls(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Trait | Target::Struct | Target::Enum | Target::Union | Target::ForeignTy => {}
             _ => {
@@ -1521,7 +1549,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             self.dcx().emit_err(errors::FfiPureInvalidTarget { attr_span });
             return;
         }
-        if attrs.iter().any(|a| a.has_name(sym::ffi_const)) {
+        if hir::find_attr_items_by_name(attrs, sym::ffi_const).next().is_some() {
             // `#[ffi_const]` functions cannot be `#[ffi_pure]`
             self.dcx().emit_err(errors::BothFfiConstAndPure { attr_span });
         }
@@ -1534,7 +1562,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Warns against some misuses of `#[must_use]`
-    fn check_must_use(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+    fn check_must_use(&self, hir_id: HirId, attr: &AttrItem, target: Target) {
         if matches!(
             target,
             Target::Fn
@@ -1580,7 +1608,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[must_not_suspend]` is applied to a struct, enum, union, or trait.
-    fn check_must_not_suspend(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_must_not_suspend(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Struct | Target::Enum | Target::Union | Target::Trait => {}
             _ => {
@@ -1590,7 +1618,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[may_dangle]` is applied to a lifetime or type generic parameter in `Drop` impl.
-    fn check_may_dangle(&self, hir_id: HirId, attr: &Attribute) {
+    fn check_may_dangle(&self, hir_id: HirId, attr: &AttrItem) {
         if let hir::Node::GenericParam(param) = self.tcx.hir_node(hir_id)
             && matches!(
                 param.kind,
@@ -1611,7 +1639,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[cold]` is applied to a non-function.
-    fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_cold(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {}
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -1619,7 +1647,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "cold");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "cold");
             }
             _ => {
                 // FIXME: #[cold] was previously allowed on non-functions and some crates used
@@ -1635,7 +1663,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[link]` is applied to an item other than a foreign module.
-    fn check_link(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_link(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         if target == Target::ForeignMod
             && let hir::Node::Item(item) = self.tcx.hir_node(hir_id)
             && let Item { kind: ItemKind::ForeignMod { abi, .. }, .. } = item
@@ -1653,7 +1681,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[link_name]` is applied to an item other than a foreign function or static.
-    fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_link_name(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::ForeignFn | Target::ForeignStatic => {}
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -1661,7 +1689,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_name");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "link_name");
             }
             _ => {
                 // FIXME: #[cold] was previously allowed on non-functions/statics and some crates
@@ -1687,7 +1715,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[no_link]` is applied to an `extern crate`.
-    fn check_no_link(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_no_link(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::ExternCrate => {}
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -1695,7 +1723,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_link");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "no_link");
             }
             _ => {
                 self.dcx().emit_err(errors::NoLink { attr_span: attr.span(), span });
@@ -1708,7 +1736,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[export_name]` is applied to a function or static.
-    fn check_export_name(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_export_name(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Static | Target::Fn => {}
             Target::Method(..) if self.is_impl_item(hir_id) => {}
@@ -1717,7 +1745,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "export_name");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "export_name");
             }
             _ => {
                 self.dcx().emit_err(errors::ExportName { attr_span: attr.span(), span });
@@ -1725,7 +1753,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_rustc_layout_scalar_valid_range(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_rustc_layout_scalar_valid_range(&self, attr: &AttrItem, span: Span, target: Target) {
         if target != Target::Struct {
             self.dcx().emit_err(errors::RustcLayoutScalarValidRangeNotStruct {
                 attr_span: attr.span(),
@@ -1749,7 +1777,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_rustc_legacy_const_generics(
         &self,
         hir_id: HirId,
-        attr: &Attribute,
+        attr: &AttrItem,
         span: Span,
         target: Target,
         item: Option<ItemLike<'_>>,
@@ -1840,7 +1868,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks that the `#[rustc_lint_opt_ty]` attribute is only applied to a struct.
-    fn check_rustc_lint_opt_ty(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_rustc_lint_opt_ty(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Struct => {}
             _ => {
@@ -1850,7 +1878,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks that the `#[rustc_lint_opt_deny_field_access]` attribute is only applied to a field.
-    fn check_rustc_lint_opt_deny_field_access(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_rustc_lint_opt_deny_field_access(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Field => {}
             _ => {
@@ -1863,14 +1891,14 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
     /// Checks that the dep-graph debugging attributes are only present when the query-dep-graph
     /// option is passed to the compiler.
-    fn check_rustc_dirty_clean(&self, attr: &Attribute) {
+    fn check_rustc_dirty_clean(&self, attr: &AttrItem) {
         if !self.tcx.sess.opts.unstable_opts.query_dep_graph {
             self.dcx().emit_err(errors::RustcDirtyClean { span: attr.span() });
         }
     }
 
     /// Checks if the attribute is applied to a trait.
-    fn check_must_be_applied_to_trait(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_must_be_applied_to_trait(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Trait => {}
             _ => {
@@ -1883,7 +1911,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[link_section]` is applied to a function or static.
-    fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_link_section(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Static | Target::Fn | Target::Method(..) => {}
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -1891,7 +1919,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_section");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "link_section");
             }
             _ => {
                 // FIXME: #[link_section] was previously allowed on non-functions/statics and some
@@ -1907,7 +1935,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[no_mangle]` is applied to a function or static.
-    fn check_no_mangle(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_no_mangle(&self, hir_id: HirId, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Static | Target::Fn => {}
             Target::Method(..) if self.is_impl_item(hir_id) => {}
@@ -1916,7 +1944,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_mangle");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "no_mangle");
             }
             // FIXME: #[no_mangle] was previously allowed on non-functions/statics, this should be an error
             // The error should specify that the item that is wrong is specifically a *foreign* fn/static
@@ -2153,7 +2181,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     fn check_used(&self, attrs: &[Attribute], target: Target, target_span: Span) {
         let mut used_linker_span = None;
         let mut used_compiler_span = None;
-        for attr in attrs.iter().filter(|attr| attr.has_name(sym::used)) {
+        for attr in hir::find_attr_items_by_name(attrs, sym::used) {
             if target != Target::Static {
                 self.dcx().emit_err(errors::UsedStatic {
                     attr_span: attr.span(),
@@ -2234,7 +2262,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if the items on the `#[debugger_visualizer]` attribute are valid.
-    fn check_debugger_visualizer(&self, attr: &Attribute, target: Target) {
+    fn check_debugger_visualizer(&self, attr: &AttrItem, target: Target) {
         // Here we only check that the #[debugger_visualizer] attribute is attached
         // to nothing other than a module. All other checks are done in the
         // `debugger_visualizer` query where they need to be done for decoding
@@ -2247,34 +2275,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros.
-    /// (Allows proc_macro functions)
-    fn check_rustc_allow_const_fn_unstable(
-        &self,
-        hir_id: HirId,
-        attr: &Attribute,
-        span: Span,
-        target: Target,
-    ) {
-        match target {
-            Target::Fn | Target::Method(_)
-                if self.tcx.is_const_fn(hir_id.expect_owner().to_def_id()) => {}
-            // FIXME(#80564): We permit struct fields and match arms to have an
-            // `#[allow_internal_unstable]` attribute with just a lint, because we previously
-            // erroneously allowed it and some crates used it accidentally, to be compatible
-            // with crates depending on them, we can't throw an error here.
-            Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "allow_internal_unstable")
-            }
-            _ => {
-                self.tcx
-                    .dcx()
-                    .emit_err(errors::RustcAllowConstFnUnstable { attr_span: attr.span(), span });
-            }
-        }
-    }
-
-    fn check_rustc_std_internal_symbol(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_rustc_std_internal_symbol(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Fn | Target::Static | Target::ForeignFn | Target::ForeignStatic => {}
             _ => {
@@ -2294,7 +2295,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_link_ordinal(&self, attr: &Attribute, _span: Span, target: Target) {
+    fn check_link_ordinal(&self, attr: &AttrItem, _span: Span, target: Target) {
         match target {
             Target::ForeignFn | Target::ForeignStatic => {}
             _ => {
@@ -2309,21 +2310,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) {
-        match target {
-            Target::Closure | Target::Expression | Target::Statement | Target::Arm => {
-                self.tcx.emit_node_span_lint(
-                    UNUSED_ATTRIBUTES,
-                    hir_id,
-                    attr.span(),
-                    errors::Deprecated,
-                );
-            }
-            _ => {}
-        }
-    }
-
-    fn check_macro_use(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+    fn check_macro_use(&self, hir_id: HirId, attr: &AttrItem, target: Target) {
         let Some(name) = attr.name() else {
             return;
         };
@@ -2340,7 +2327,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_macro_export(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+    fn check_macro_export(&self, hir_id: HirId, attr: &AttrItem, target: Target) {
         if target != Target::MacroDef {
             self.tcx.emit_node_span_lint(
                 UNUSED_ATTRIBUTES,
@@ -2383,97 +2370,111 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute) {
-        // FIXME(jdonszelmann): deduplicate these checks after more attrs are parsed. This is very
-        // ugly now but can 100% be removed later.
-        if let Attribute::Parsed(p) = attr {
-            match p {
-                AttributeKind::Repr(reprs) => {
-                    for (r, span) in reprs {
-                        if let ReprAttr::ReprEmpty = r {
-                            self.tcx.emit_node_span_lint(
-                                UNUSED_ATTRIBUTES,
-                                hir_id,
-                                *span,
-                                errors::Unused {
-                                    attr_span: *span,
-                                    note: errors::UnusedNote::EmptyList { name: sym::repr },
-                                },
-                            );
+        match attr {
+            Attribute::Parsed(p) =>
+            // FIXME(jdonszelmann): deduplicate these checks after more attrs are parsed. This is very
+            // ugly now but can 100% be removed later.
+            {
+                match p {
+                    AttributeKind::Repr(reprs) => {
+                        for (r, span) in reprs {
+                            if let ReprAttr::ReprEmpty = r {
+                                self.tcx.emit_node_span_lint(
+                                    UNUSED_ATTRIBUTES,
+                                    hir_id,
+                                    *span,
+                                    errors::Unused {
+                                        attr_span: *span,
+                                        note: errors::UnusedNote::EmptyList { name: sym::repr },
+                                    },
+                                );
+                            }
                         }
+                        return;
                     }
-                    return;
+                    _ => {}
                 }
-                _ => {}
             }
-        }
-
-        // Warn on useless empty attributes.
-        let note = if attr.has_any_name(&[
-            sym::macro_use,
-            sym::allow,
-            sym::expect,
-            sym::warn,
-            sym::deny,
-            sym::forbid,
-            sym::feature,
-            sym::target_feature,
-        ]) && attr.meta_item_list().is_some_and(|list| list.is_empty())
-        {
-            errors::UnusedNote::EmptyList { name: attr.name().unwrap() }
-        } else if attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect])
-            && let Some(meta) = attr.meta_item_list()
-            && let [meta] = meta.as_slice()
-            && let Some(item) = meta.meta_item()
-            && let MetaItemKind::NameValue(_) = &item.kind
-            && item.path == sym::reason
-        {
-            errors::UnusedNote::NoLints { name: attr.name().unwrap() }
-        } else if attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect])
-            && let Some(meta) = attr.meta_item_list()
-            && meta.iter().any(|meta| {
-                meta.meta_item().map_or(false, |item| item.path == sym::linker_messages)
-            })
-        {
-            if hir_id != CRATE_HIR_ID {
-                match attr.style() {
-                    ast::AttrStyle::Outer => self.tcx.emit_node_span_lint(
-                        UNUSED_ATTRIBUTES,
-                        hir_id,
-                        attr.span(),
-                        errors::OuterCrateLevelAttr,
-                    ),
-                    ast::AttrStyle::Inner => self.tcx.emit_node_span_lint(
-                        UNUSED_ATTRIBUTES,
-                        hir_id,
-                        attr.span(),
-                        errors::InnerCrateLevelAttr,
-                    ),
-                };
-                return;
-            } else {
-                let never_needs_link = self
-                    .tcx
-                    .crate_types()
-                    .iter()
-                    .all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib));
-                if never_needs_link {
-                    errors::UnusedNote::LinkerMessagesBinaryCrateOnly
+            Attribute::Unparsed(attr) => {
+                // Warn on useless empty attributes.
+                let note = if attr.has_any_name(&[
+                    sym::macro_use,
+                    sym::allow,
+                    sym::expect,
+                    sym::warn,
+                    sym::deny,
+                    sym::forbid,
+                    sym::feature,
+                    sym::target_feature,
+                ]) && attr.meta_item_list().is_some_and(|list| list.is_empty())
+                {
+                    errors::UnusedNote::EmptyList { name: attr.name().unwrap() }
+                } else if attr.has_any_name(&[
+                    sym::allow,
+                    sym::warn,
+                    sym::deny,
+                    sym::forbid,
+                    sym::expect,
+                ]) && let Some(meta) = attr.meta_item_list()
+                    && let [meta] = meta.as_slice()
+                    && let Some(item) = meta.meta_item()
+                    && let MetaItemKind::NameValue(_) = &item.kind
+                    && item.path == sym::reason
+                {
+                    errors::UnusedNote::NoLints { name: attr.name().unwrap() }
+                } else if attr.has_any_name(&[
+                    sym::allow,
+                    sym::warn,
+                    sym::deny,
+                    sym::forbid,
+                    sym::expect,
+                ]) && let Some(meta) = attr.meta_item_list()
+                    && meta.iter().any(|meta| {
+                        meta.meta_item().map_or(false, |item| item.path == sym::linker_messages)
+                    })
+                {
+                    if hir_id != CRATE_HIR_ID {
+                        match attr.style() {
+                            ast::AttrStyle::Outer => self.tcx.emit_node_span_lint(
+                                UNUSED_ATTRIBUTES,
+                                hir_id,
+                                attr.span(),
+                                errors::OuterCrateLevelAttr,
+                            ),
+                            ast::AttrStyle::Inner => self.tcx.emit_node_span_lint(
+                                UNUSED_ATTRIBUTES,
+                                hir_id,
+                                attr.span(),
+                                errors::InnerCrateLevelAttr,
+                            ),
+                        };
+                        return;
+                    } else {
+                        let never_needs_link = self
+                            .tcx
+                            .crate_types()
+                            .iter()
+                            .all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib));
+                        if never_needs_link {
+                            errors::UnusedNote::LinkerMessagesBinaryCrateOnly
+                        } else {
+                            return;
+                        }
+                    }
+                } else if attr.has_name(sym::default_method_body_is_const) {
+                    errors::UnusedNote::DefaultMethodBodyConst
                 } else {
                     return;
-                }
+                };
+
+                self.tcx.emit_node_span_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span(),
+                    errors::Unused { attr_span: attr.span(), note },
+                )
             }
-        } else if attr.has_name(sym::default_method_body_is_const) {
-            errors::UnusedNote::DefaultMethodBodyConst
-        } else {
-            return;
         };
-
-        self.tcx.emit_node_span_lint(
-            UNUSED_ATTRIBUTES,
-            hir_id,
-            attr.span(),
-            errors::Unused { attr_span: attr.span(), note },
-        );
     }
 
     /// A best effort attempt to create an error for a mismatching proc macro signature.
@@ -2584,7 +2585,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_coroutine(&self, attr: &Attribute, target: Target) {
+    fn check_coroutine(&self, attr: &AttrItem, target: Target) {
         match target {
             Target::Closure => return,
             _ => {
@@ -2593,7 +2594,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_type_const(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+    fn check_type_const(&self, hir_id: HirId, attr: &AttrItem, target: Target) {
         let tcx = self.tcx;
         if target == Target::AssocConst
             && let parent = tcx.parent(hir_id.expect_owner().to_def_id())
@@ -2610,7 +2611,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_linkage(&self, attr: &Attribute, span: Span, target: Target) {
+    fn check_linkage(&self, attr: &AttrItem, span: Span, target: Target) {
         match target {
             Target::Fn
             | Target::Method(..)
@@ -2671,7 +2672,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[autodiff]` is applied to an item other than a function item.
-    fn check_autodiff(&self, _hir_id: HirId, _attr: &Attribute, span: Span, target: Target) {
+    fn check_autodiff(&self, _hir_id: HirId, _attr: &AttrItem, span: Span, target: Target) {
         debug!("check_autodiff");
         match target {
             Target::Fn => {}
@@ -2910,7 +2911,7 @@ pub(crate) fn provide(providers: &mut Providers) {
 
 fn check_duplicates(
     tcx: TyCtxt<'_>,
-    attr: &Attribute,
+    attr: &AttrItem,
     hir_id: HirId,
     duplicates: AttributeDuplicates,
     seen: &mut FxHashMap<Symbol, Span>,
diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs
index a19faf0fa8367..006772bc26115 100644
--- a/compiler/rustc_passes/src/layout_test.rs
+++ b/compiler/rustc_passes/src/layout_test.rs
@@ -1,5 +1,5 @@
 use rustc_abi::{HasDataLayout, TargetDataLayout};
-use rustc_hir::Attribute;
+use rustc_hir::AttrItem;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalDefId;
 use rustc_middle::span_bug;
@@ -66,7 +66,7 @@ pub fn ensure_wf<'tcx>(
     }
 }
 
-fn dump_layout_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) {
+fn dump_layout_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &AttrItem) {
     let typing_env = ty::TypingEnv::post_analysis(tcx, item_def_id);
     let ty = tcx.type_of(item_def_id).instantiate_identity();
     let span = tcx.def_span(item_def_id.to_def_id());
diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs
index 93d164e7d01f8..dc42043281eb1 100644
--- a/compiler/rustc_passes/src/weak_lang_items.rs
+++ b/compiler/rustc_passes/src/weak_lang_items.rs
@@ -45,7 +45,7 @@ struct WeakLangItemVisitor<'a, 'tcx> {
 
 impl<'ast> visit::Visitor<'ast> for WeakLangItemVisitor<'_, '_> {
     fn visit_foreign_item(&mut self, i: &'ast ast::ForeignItem) {
-        if let Some((lang_item, _)) = lang_items::extract(&i.attrs) {
+        if let Some((lang_item, _)) = lang_items::extract(i.attrs.as_slice()) {
             if let Some(item) = LangItem::from_name(lang_item)
                 && item.is_weak()
             {
diff --git a/compiler/rustc_smir/src/rustc_smir/context.rs b/compiler/rustc_smir/src/rustc_smir/context.rs
index baa4c0681e806..54e25b3aa63ef 100644
--- a/compiler/rustc_smir/src/rustc_smir/context.rs
+++ b/compiler/rustc_smir/src/rustc_smir/context.rs
@@ -275,15 +275,11 @@ impl<'tcx> SmirCtxt<'tcx> {
         let attr_name: Vec<_> = attr.iter().map(|seg| rustc_span::Symbol::intern(&seg)).collect();
         tcx.get_attrs_by_path(did, &attr_name)
             .filter_map(|attribute| {
-                if let Attribute::Unparsed(u) = attribute {
-                    let attr_str = rustc_hir_pretty::attribute_to_string(&tcx, attribute);
-                    Some(stable_mir::crate_def::Attribute::new(
-                        attr_str,
-                        u.span.stable(&mut *tables),
-                    ))
-                } else {
-                    None
-                }
+                let attr_str = rustc_hir_pretty::attr_item_to_string(&tcx, attribute);
+                Some(stable_mir::crate_def::Attribute::new(
+                    attr_str,
+                    attribute.span.stable(&mut *tables),
+                ))
             })
             .collect()
     }
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
index 89dab90dc681c..78551c6f45ca8 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
@@ -4,9 +4,8 @@ use std::path::PathBuf;
 use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_errors::codes::*;
 use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
-use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::{AttrArgs, Attribute};
+use rustc_hir::{self as hir, AttrArgs, AttrItem};
 use rustc_macros::LintDiagnostic;
 use rustc_middle::bug;
 use rustc_middle::ty::print::PrintTraitRefExt;
@@ -645,20 +644,13 @@ impl<'tcx> OnUnimplementedDirective {
     }
 
     fn parse_attribute(
-        attr: &Attribute,
+        attr: &AttrItem,
         is_diagnostic_namespace_variant: bool,
         tcx: TyCtxt<'tcx>,
         item_def_id: DefId,
     ) -> Result<Option<Self>, ErrorGuaranteed> {
         let result = if let Some(items) = attr.meta_item_list() {
-            Self::parse(
-                tcx,
-                item_def_id,
-                &items,
-                attr.span(),
-                true,
-                is_diagnostic_namespace_variant,
-            )
+            Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
         } else if let Some(value) = attr.value_str() {
             if !is_diagnostic_namespace_variant {
                 Ok(Some(OnUnimplementedDirective {
@@ -680,9 +672,8 @@ impl<'tcx> OnUnimplementedDirective {
                     append_const_msg: None,
                 }))
             } else {
-                let item = attr.get_normal_item();
-                let report_span = match &item.args {
-                    AttrArgs::Empty => item.path.span,
+                let report_span = match &attr.args {
+                    AttrArgs::Empty => attr.path.span,
                     AttrArgs::Delimited(args) => args.dspan.entire(),
                     AttrArgs::Eq { eq_span, expr } => eq_span.to(expr.span),
                 };
@@ -698,29 +689,23 @@ impl<'tcx> OnUnimplementedDirective {
                 Ok(None)
             }
         } else if is_diagnostic_namespace_variant {
-            match attr {
-                Attribute::Unparsed(p) if !matches!(p.args, AttrArgs::Empty) => {
-                    if let Some(item_def_id) = item_def_id.as_local() {
-                        tcx.emit_node_span_lint(
-                            UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
-                            tcx.local_def_id_to_hir_id(item_def_id),
-                            attr.span(),
-                            MalformedOnUnimplementedAttrLint::new(attr.span()),
-                        );
-                    }
-                }
-                _ => {
-                    if let Some(item_def_id) = item_def_id.as_local() {
-                        tcx.emit_node_span_lint(
-                            UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
-                            tcx.local_def_id_to_hir_id(item_def_id),
-                            attr.span(),
-                            MissingOptionsForOnUnimplementedAttr,
-                        )
-                    }
+            if !matches!(attr.args, AttrArgs::Empty) {
+                if let Some(item_def_id) = item_def_id.as_local() {
+                    tcx.emit_node_span_lint(
+                        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                        tcx.local_def_id_to_hir_id(item_def_id),
+                        attr.span(),
+                        MalformedOnUnimplementedAttrLint::new(attr.span()),
+                    );
                 }
+            } else if let Some(item_def_id) = item_def_id.as_local() {
+                tcx.emit_node_span_lint(
+                    UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                    tcx.local_def_id_to_hir_id(item_def_id),
+                    attr.span(),
+                    MissingOptionsForOnUnimplementedAttr,
+                )
             };
-
             Ok(None)
         } else {
             let reported = tcx.dcx().delayed_bug("of_item: neither meta_item_list nor value_str");
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index c58b07a5b6731..866e046097fd2 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -579,7 +579,7 @@ pub(crate) fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool {
 }
 
 pub(crate) fn attrs_have_doc_flag<'a>(
-    mut attrs: impl Iterator<Item = &'a hir::Attribute>,
+    mut attrs: impl Iterator<Item = &'a hir::AttrItem>,
     flag: Symbol,
 ) -> bool {
     attrs.any(|attr| attr.meta_item_list().is_some_and(|l| ast::attr::list_contains_name(&l, flag)))
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index f4e4cd924f7ff..1a7dd424b7db1 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -66,9 +66,11 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
             let mut parent = Some(tcx.parent(impl_def_id));
             while let Some(did) = parent {
                 attr_buf.extend(
-                    tcx.get_attrs(did, sym::doc)
+                    tcx.get_all_attrs(did)
                         .filter(|attr| {
-                            if let Some([attr]) = attr.meta_item_list().as_deref() {
+                            if !attr.has_name(sym::doc) {
+                                false
+                            } else if let Some([attr]) = attr.meta_item_list().as_deref() {
                                 attr.has_name(sym::cfg)
                             } else {
                                 false
diff --git a/src/tools/clippy/clippy_lints/src/functions/must_use.rs b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
index 70655838b6af0..0e23676212d8b 100644
--- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
@@ -2,7 +2,7 @@ use hir::FnSig;
 use rustc_errors::Applicability;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::DefIdSet;
-use rustc_hir::{self as hir, Attribute, QPath};
+use rustc_hir::{self as hir, AttrItem, Attribute, QPath};
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_lint::{LateContext, LintContext};
 use rustc_middle::ty::{self, Ty};
@@ -103,7 +103,7 @@ fn check_needless_must_use(
     item_id: hir::OwnerId,
     item_span: Span,
     fn_header_span: Span,
-    attr: &Attribute,
+    attr: &AttrItem,
     attrs: &[Attribute],
     sig: &FnSig<'_>,
 ) {