about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir/src/hir.rs1
-rw-r--r--src/librustdoc/clean/mod.rs12
-rw-r--r--src/librustdoc/clean/types.rs2
-rw-r--r--src/librustdoc/doctest/rust.rs2
-rw-r--r--src/rustdoc-json-types/lib.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs/inline_always.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs42
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs/utils.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/default_union_representation.rs17
-rw-r--r--src/tools/clippy/clippy_lints/src/disallowed_macros.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/doc/include_in_doc_without_cfg.rs9
-rw-r--r--src/tools/clippy/clippy_lints/src/doc/mod.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/doc/too_long_first_doc_paragraph.rs9
-rw-r--r--src/tools/clippy/clippy_lints/src/four_forward_slashes.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/must_use.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/large_include_file.rs59
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/macro_use.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs3
-rw-r--r--src/tools/clippy/tests/ui-toml/large_include_file/large_include_file.stderr18
-rw-r--r--src/tools/clippy/tests/ui/must_use_unit.fixed4
-rw-r--r--src/tools/clippy/tests/ui/must_use_unit.stderr6
-rw-r--r--tests/rustdoc-json/enums/discriminant/struct.rs2
-rw-r--r--tests/rustdoc-json/enums/discriminant/tuple.rs2
31 files changed, 134 insertions, 121 deletions
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 06c49366659..0ef4e6b3fe3 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1145,6 +1145,7 @@ impl AttributeExt for Attribute {
     }
 
     fn is_doc_comment(&self) -> bool {
+        // FIXME(jdonszelmann): make the 2nd check unnecessary here
         matches!(self, Attribute::Parsed(AttributeKind::DocComment { .. }))
     }
 
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index dcc5fd12d81..ceffe5e5ce0 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -2737,13 +2737,13 @@ fn add_without_unwanted_attributes<'hir>(
     import_parent: Option<DefId>,
 ) {
     for attr in new_attrs {
-        if matches!(attr.kind, hir::AttrKind::DocComment(..)) {
+        if attr.is_doc_comment() {
             attrs.push((Cow::Borrowed(attr), import_parent));
             continue;
         }
         let mut attr = attr.clone();
-        match attr.kind {
-            hir::AttrKind::Normal(ref mut normal) => {
+        match attr {
+            hir::Attribute::Unparsed(ref mut normal) => {
                 if let [ident] = &*normal.path.segments {
                     let ident = ident.name;
                     if ident == sym::doc {
@@ -2755,7 +2755,11 @@ fn add_without_unwanted_attributes<'hir>(
                     }
                 }
             }
-            _ => unreachable!(),
+            hir::Attribute::Parsed(..) => {
+                if is_inline {
+                    attrs.push((Cow::Owned(attr), import_parent));
+                }
+            }
         }
     }
 }
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index fc7c4b42047..178b6a60b41 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -265,7 +265,7 @@ impl ExternalCrate {
                     let attr_value = attr.value_str().expect("syntax should already be validated");
                     let Some(prim) = PrimitiveType::from_symbol(attr_value) else {
                         span_bug!(
-                            attr.span,
+                            attr.span(),
                             "primitive `{attr_value}` is not a member of `PrimitiveType`"
                         );
                     };
diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs
index 1ac3c040b59..3ac7abd0aa5 100644
--- a/src/librustdoc/doctest/rust.rs
+++ b/src/librustdoc/doctest/rust.rs
@@ -123,7 +123,7 @@ impl HirCollector<'_> {
                     .iter()
                     .find(|attr| attr.doc_str().is_some())
                     .map(|attr| {
-                        attr.span.ctxt().outer_expn().expansion_cause().unwrap_or(attr.span)
+                        attr.span().ctxt().outer_expn().expansion_cause().unwrap_or(attr.span())
                     })
                     .unwrap_or(DUMMY_SP)
             };
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index a92f3ded774..8f6496e9626 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -30,7 +30,7 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
 /// This integer is incremented with every breaking change to the API,
 /// and is returned along with the JSON blob as [`Crate::format_version`].
 /// Consuming code should assert that this value matches the format version(s) that it supports.
-pub const FORMAT_VERSION: u32 = 39;
+pub const FORMAT_VERSION: u32 = 40;
 
 /// The root of the emitted JSON blob.
 ///
@@ -120,7 +120,9 @@ pub struct Item {
     pub docs: Option<String>,
     /// This mapping resolves [intra-doc links](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md) from the docstring to their IDs
     pub links: HashMap<String, Id>,
-    /// Stringified versions of the attributes on this item (e.g. `"#[inline]"`)
+    /// Stringified versions of parsed attributes on this item.
+    /// Essentially debug printed (e.g. `#[inline]` becomes something similar to `#[attr="Inline(Hint)"]`).
+    /// Equivalent to the hir pretty-printing of attributes.
     pub attrs: Vec<String>,
     /// Information about the item’s deprecation, if present.
     pub deprecation: Option<Deprecation>,
diff --git a/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs b/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs
index 2325f914b0b..cb63fadb4e2 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs
@@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Att
                 span_lint(
                     cx,
                     INLINE_ALWAYS,
-                    attr.span,
+                    attr.span(),
                     format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
                 );
             }
diff --git a/src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs b/src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs
index 6d1ab46aa0c..6cc47596bbb 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/repr_attributes.rs
@@ -1,6 +1,7 @@
+use rustc_attr_parsing::{find_attr, AttributeKind, ReprAttr};
 use rustc_hir::Attribute;
 use rustc_lint::LateContext;
-use rustc_span::{Span, sym};
+use rustc_span::Span;
 
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::msrvs;
@@ -14,30 +15,21 @@ pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute],
 }
 
 fn check_packed(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute]) {
-    if let Some(items) = attrs.iter().find_map(|attr| {
-        if attr.ident().is_some_and(|ident| matches!(ident.name, sym::repr)) {
-            attr.meta_item_list()
-        } else {
-            None
+    if let Some(reprs) = find_attr!(attrs, AttributeKind::Repr(r) => r) {
+        let packed_span = reprs.iter().find(|(r, _)| matches!(r, ReprAttr::ReprPacked(..))).map(|(_, s)| *s);
+
+        if let Some(packed_span) = packed_span && !reprs.iter().any(|(x, _)| *x == ReprAttr::ReprC || *x == ReprAttr::ReprRust) {
+            span_lint_and_then(
+                cx,
+                REPR_PACKED_WITHOUT_ABI,
+                item_span,
+                "item uses `packed` representation without ABI-qualification",
+                |diag| {
+                    diag.warn("unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI")
+                        .help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
+                        .span_label(packed_span, "`packed` representation set here");
+                },
+            );
         }
-    }) && let Some(packed) = items
-        .iter()
-        .find(|item| item.ident().is_some_and(|ident| matches!(ident.name, sym::packed)))
-        && !items.iter().any(|item| {
-            item.ident()
-                .is_some_and(|ident| matches!(ident.name, sym::C | sym::Rust))
-        })
-    {
-        span_lint_and_then(
-            cx,
-            REPR_PACKED_WITHOUT_ABI,
-            item_span,
-            "item uses `packed` representation without ABI-qualification",
-            |diag| {
-                diag.warn("unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI")
-                    .help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
-                    .span_label(packed.span(), "`packed` representation set here");
-            },
-        );
     }
 }
diff --git a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
index 478ba7a187b..6ee3290fa76 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
@@ -15,7 +15,7 @@ pub(super) fn check(
 ) {
     if cfg_attr.has_name(sym::clippy)
         && let Some(ident) = behind_cfg_attr.ident()
-        && Level::from_symbol(ident.name, Some(attr.id)).is_some()
+        && Level::from_symbol(ident.name, || Some(attr.id)).is_some()
         && let Some(items) = behind_cfg_attr.meta_item_list()
     {
         let nb_items = items.len();
diff --git a/src/tools/clippy/clippy_lints/src/attrs/utils.rs b/src/tools/clippy/clippy_lints/src/attrs/utils.rs
index a667649f734..0e650e49392 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/utils.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/utils.rs
@@ -17,7 +17,7 @@ pub(super) fn is_word(nmi: &MetaItemInner, expected: Symbol) -> bool {
 }
 
 pub(super) fn is_lint_level(symbol: Symbol, attr_id: AttrId) -> bool {
-    Level::from_symbol(symbol, Some(attr_id)).is_some()
+    Level::from_symbol(symbol, || Some(attr_id)).is_some()
 }
 
 pub(super) fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
diff --git a/src/tools/clippy/clippy_lints/src/default_union_representation.rs b/src/tools/clippy/clippy_lints/src/default_union_representation.rs
index 9f020d3081c..6e6d81db11c 100644
--- a/src/tools/clippy/clippy_lints/src/default_union_representation.rs
+++ b/src/tools/clippy/clippy_lints/src/default_union_representation.rs
@@ -1,10 +1,10 @@
 use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_attr_parsing::{find_attr, AttributeKind, ReprAttr};
 use rustc_hir::{HirId, Item, ItemKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::{self, FieldDef};
 use rustc_session::declare_lint_pass;
-use rustc_span::sym;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -97,16 +97,7 @@ fn is_zst<'tcx>(cx: &LateContext<'tcx>, field: &FieldDef, args: ty::GenericArgsR
 }
 
 fn has_c_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
-    cx.tcx.hir().attrs(hir_id).iter().any(|attr| {
-        if attr.has_name(sym::repr) {
-            if let Some(items) = attr.meta_item_list() {
-                for item in items {
-                    if item.is_word() && matches!(item.name_or_empty(), sym::C) {
-                        return true;
-                    }
-                }
-            }
-        }
-        false
-    })
+    let attrs = cx.tcx.hir().attrs(hir_id);
+
+    find_attr!(attrs, AttributeKind::Repr(r) if r.iter().any(|(x, _)| *x == ReprAttr::ReprC))
 }
diff --git a/src/tools/clippy/clippy_lints/src/disallowed_macros.rs b/src/tools/clippy/clippy_lints/src/disallowed_macros.rs
index 4e8853821c3..6de16e306c9 100644
--- a/src/tools/clippy/clippy_lints/src/disallowed_macros.rs
+++ b/src/tools/clippy/clippy_lints/src/disallowed_macros.rs
@@ -1,3 +1,4 @@
+
 use clippy_config::Conf;
 use clippy_config::types::create_disallowed_map;
 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
diff --git a/src/tools/clippy/clippy_lints/src/doc/include_in_doc_without_cfg.rs b/src/tools/clippy/clippy_lints/src/doc/include_in_doc_without_cfg.rs
index 4b40fc0b1ee..aa29705cf93 100644
--- a/src/tools/clippy/clippy_lints/src/doc/include_in_doc_without_cfg.rs
+++ b/src/tools/clippy/clippy_lints/src/doc/include_in_doc_without_cfg.rs
@@ -1,18 +1,17 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_opt;
-use rustc_ast::AttrStyle;
 use rustc_errors::Applicability;
-use rustc_hir::{AttrArgs, AttrKind, Attribute};
-use rustc_lint::LateContext;
+use rustc_lint::EarlyContext;
+use rustc_ast::{Attribute, AttrKind, AttrArgs, AttrStyle};
 
 use super::DOC_INCLUDE_WITHOUT_CFG;
 
-pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) {
+pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
     for attr in attrs {
         if !attr.span.from_expansion()
             && let AttrKind::Normal(ref item) = attr.kind
             && attr.doc_str().is_some()
-            && let AttrArgs::Eq { expr: meta, .. } = &item.args
+            && let AttrArgs::Eq { expr: meta, .. } = &item.item.args
             && !attr.span.contains(meta.span)
             // Since the `include_str` is already expanded at this point, we can only take the
             // whole attribute snippet and then modify for our suggestion.
diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs
index 713d62a8801..42192801af7 100644
--- a/src/tools/clippy/clippy_lints/src/doc/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs
@@ -20,7 +20,7 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
-use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty;
 use rustc_resolve::rustdoc::{
@@ -577,6 +577,13 @@ impl_lint_pass!(Documentation => [
     DOC_INCLUDE_WITHOUT_CFG,
 ]);
 
+
+impl EarlyLintPass for Documentation {
+    fn check_attributes(&mut self, cx: &EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) {
+        include_in_doc_without_cfg::check(cx, attrs);
+    }
+}
+
 impl<'tcx> LateLintPass<'tcx> for Documentation {
     fn check_attributes(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
         let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else {
@@ -704,14 +711,13 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
         Some(("fake".into(), "fake".into()))
     }
 
-    include_in_doc_without_cfg::check(cx, attrs);
     if suspicious_doc_comments::check(cx, attrs) || is_doc_hidden(attrs) {
         return None;
     }
 
     let (fragments, _) = attrs_to_doc_fragments(
         attrs.iter().filter_map(|attr| {
-            if attr.span.in_external_macro(cx.sess().source_map()) {
+            if !attr.doc_str_and_comment_kind().is_some() || attr.span().in_external_macro(cx.sess().source_map()) {
                 None
             } else {
                 Some((attr, None))
diff --git a/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs b/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs
index 84393213e6f..bfc36deea7b 100644
--- a/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs
+++ b/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs
@@ -3,6 +3,7 @@ use rustc_ast::AttrStyle;
 use rustc_ast::token::CommentKind;
 use rustc_errors::Applicability;
 use rustc_hir::Attribute;
+use rustc_attr_parsing::AttributeKind;
 use rustc_lint::LateContext;
 use rustc_span::Span;
 
@@ -36,15 +37,14 @@ fn collect_doc_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> {
     attrs
         .iter()
         .filter_map(|attr| {
-            if let Some((sym, com_kind)) = attr.doc_str_and_comment_kind()
-                && let AttrStyle::Outer = attr.style
-                && let Some(com) = sym.as_str().strip_prefix('!')
+            if let Attribute::Parsed(AttributeKind::DocComment{ style: AttrStyle::Outer, kind, comment, ..}) = attr
+                && let Some(com) = comment.as_str().strip_prefix('!')
             {
-                let sugg = match com_kind {
+                let sugg = match kind {
                     CommentKind::Line => format!("//!{com}"),
                     CommentKind::Block => format!("/*!{com}*/"),
                 };
-                Some((attr.span, sugg))
+                Some((attr.span(), sugg))
             } else {
                 None
             }
diff --git a/src/tools/clippy/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/src/tools/clippy/clippy_lints/src/doc/too_long_first_doc_paragraph.rs
index 1f89cab9148..1eda73a9672 100644
--- a/src/tools/clippy/clippy_lints/src/doc/too_long_first_doc_paragraph.rs
+++ b/src/tools/clippy/clippy_lints/src/doc/too_long_first_doc_paragraph.rs
@@ -1,5 +1,6 @@
 use rustc_errors::Applicability;
 use rustc_hir::{Attribute, Item, ItemKind};
+use rustc_attr_parsing::AttributeKind;
 use rustc_lint::LateContext;
 
 use clippy_utils::diagnostics::span_lint_and_then;
@@ -43,9 +44,9 @@ pub(super) fn check(
     let mut should_suggest_empty_doc = false;
 
     for attr in attrs {
-        if let Some(doc) = attr.doc_str() {
-            spans.push(attr.span);
-            let doc = doc.as_str();
+        if let Attribute::Parsed(AttributeKind::DocComment {span, comment, ..}) = attr {
+            spans.push(span);
+            let doc = comment.as_str();
             let doc = doc.trim();
             if spans.len() == 1 {
                 // We make this suggestion only if the first doc line ends with a punctuation
@@ -78,7 +79,7 @@ pub(super) fn check(
                 && let new_span = first_span.with_hi(second_span.lo()).with_lo(first_span.hi())
                 && let Some(snippet) = snippet_opt(cx, new_span)
             {
-                let Some(first) = snippet_opt(cx, first_span) else {
+                let Some(first) = snippet_opt(cx, *first_span) else {
                     return;
                 };
                 let Some(comment_form) = first.get(..3) else {
diff --git a/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs b/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs
index 0599e08e6c0..0bdb99d7b9a 100644
--- a/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs
+++ b/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs
@@ -46,7 +46,8 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes {
             .hir()
             .attrs(item.hir_id())
             .iter()
-            .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span));
+            .filter(|i| i.is_doc_comment())
+            .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span()));
         let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else {
             return;
         };
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 e6e3ea59a9f..dbc08fca362 100644
--- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
@@ -95,6 +95,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
     }
 }
 
+// FIXME: needs to be an EARLY LINT. all attribute lints should be
 #[allow(clippy::too_many_arguments)]
 fn check_needless_must_use(
     cx: &LateContext<'_>,
@@ -117,7 +118,7 @@ fn check_needless_must_use(
                 fn_header_span,
                 "this unit-returning function has a `#[must_use]` attribute",
                 |diag| {
-                    diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable);
+                    diag.span_suggestion(attr.span(), "remove the attribute", "", Applicability::MachineApplicable);
                 },
             );
         } else {
@@ -130,7 +131,7 @@ fn check_needless_must_use(
                 "this unit-returning function has a `#[must_use]` attribute",
                 |diag| {
                     let mut attrs_without_must_use = attrs.to_vec();
-                    attrs_without_must_use.retain(|a| a.id != attr.id);
+                    attrs_without_must_use.retain(|a| a.id() != attr.id());
                     let sugg_str = attrs_without_must_use
                         .iter()
                         .map(|a| {
@@ -143,7 +144,7 @@ fn check_needless_must_use(
                         .join(", ");
 
                     diag.span_suggestion(
-                        attrs[0].span.with_hi(attrs[attrs.len() - 1].span.hi()),
+                        attrs[0].span().with_hi(attrs[attrs.len() - 1].span().hi()),
                         "change these attributes to",
                         sugg_str,
                         Applicability::MachineApplicable,
diff --git a/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs
index 39ff3c13bcc..5b58113169b 100644
--- a/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs
+++ b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs
@@ -183,7 +183,7 @@ fn suggestion<'tcx>(
 
 fn field_with_attrs_span(tcx: TyCtxt<'_>, field: &hir::ExprField<'_>) -> Span {
     if let Some(attr) = tcx.hir().attrs(field.hir_id).first() {
-        field.span.with_lo(attr.span.lo())
+        field.span.with_lo(attr.span().lo())
     } else {
         field.span
     }
diff --git a/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs
index 1b900f6be8e..9b4a3b3f9c8 100644
--- a/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs
+++ b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs
@@ -42,10 +42,10 @@ impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
             span_lint_and_then(
                 cx,
                 INLINE_FN_WITHOUT_BODY,
-                attr.span,
+                attr.span(),
                 format!("use of `#[inline]` on trait method `{}` which has no body", item.ident),
                 |diag| {
-                    diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable);
+                    diag.suggest_remove_item(cx, attr.span(), "remove", Applicability::MachineApplicable);
                 },
             );
         }
diff --git a/src/tools/clippy/clippy_lints/src/large_include_file.rs b/src/tools/clippy/clippy_lints/src/large_include_file.rs
index f3d62b513e8..53dc070833b 100644
--- a/src/tools/clippy/clippy_lints/src/large_include_file.rs
+++ b/src/tools/clippy/clippy_lints/src/large_include_file.rs
@@ -3,10 +3,11 @@ use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::macros::root_macro_call_first_node;
 use clippy_utils::source::snippet_opt;
 use rustc_ast::LitKind;
-use rustc_hir::{AttrArgs, AttrKind, Attribute, Expr, ExprKind};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_hir::{Expr, ExprKind};
+use rustc_ast::{Attribute, AttrArgs, AttrKind};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 use rustc_session::impl_lint_pass;
-use rustc_span::{Span, sym};
+use rustc_span::sym;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -52,24 +53,6 @@ impl LargeIncludeFile {
 
 impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
 
-impl LargeIncludeFile {
-    fn emit_lint(&self, cx: &LateContext<'_>, span: Span) {
-        #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
-        span_lint_and_then(
-            cx,
-            LARGE_INCLUDE_FILE,
-            span,
-            "attempted to include a large file",
-            |diag| {
-                diag.note(format!(
-                    "the configuration allows a maximum size of {} bytes",
-                    self.max_file_size
-                ));
-            },
-        );
-    }
-}
-
 impl LateLintPass<'_> for LargeIncludeFile {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
         if let ExprKind::Lit(lit) = &expr.kind
@@ -85,18 +68,32 @@ impl LateLintPass<'_> for LargeIncludeFile {
             && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
                 || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
         {
-            self.emit_lint(cx, expr.span.source_callsite());
+            #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
+            span_lint_and_then(
+                cx,
+                LARGE_INCLUDE_FILE,
+                expr.span.source_callsite(),
+                "attempted to include a large file",
+                |diag| {
+                    diag.note(format!(
+                        "the configuration allows a maximum size of {} bytes",
+                        self.max_file_size
+                    ));
+                },
+            );
         }
     }
+}
 
-    fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
+impl EarlyLintPass for LargeIncludeFile {
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
         if !attr.span.from_expansion()
             // Currently, rustc limits the usage of macro at the top-level of attributes,
             // so we don't need to recurse into each level.
             && let AttrKind::Normal(ref item) = attr.kind
             && let Some(doc) = attr.doc_str()
             && doc.as_str().len() as u64 > self.max_file_size
-            && let AttrArgs::Eq { expr: meta, .. } = &item.args
+            && let AttrArgs::Eq { expr: meta, .. } = &item.item.args
             && !attr.span.contains(meta.span)
             // Since the `include_str` is already expanded at this point, we can only take the
             // whole attribute snippet and then modify for our suggestion.
@@ -113,7 +110,19 @@ impl LateLintPass<'_> for LargeIncludeFile {
             && let sub_snippet = sub_snippet.trim()
             && (sub_snippet.starts_with("include_str!") || sub_snippet.starts_with("include_bytes!"))
         {
-            self.emit_lint(cx, attr.span);
+            #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
+            span_lint_and_then(
+                cx,
+                LARGE_INCLUDE_FILE,
+                attr.span,
+                "attempted to include a large file",
+                |diag| {
+                    diag.note(format!(
+                        "the configuration allows a maximum size of {} bytes",
+                        self.max_file_size
+                    ));
+                },
+            );
         }
     }
 }
diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs
index 13218331a67..177f83921cd 100644
--- a/src/tools/clippy/clippy_lints/src/lib.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.rs
@@ -407,9 +407,9 @@ mod zombie_processes;
 
 use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation};
 use clippy_utils::macros::FormatArgsStorage;
+use utils::attr_collector::{AttrCollector, AttrStorage};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_lint::{Lint, LintId};
-use utils::attr_collector::{AttrCollector, AttrStorage};
 
 /// Register all pre expansion lints
 ///
@@ -717,6 +717,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(conf)));
     store.register_late_pass(move |tcx| Box::new(functions::Functions::new(tcx, conf)));
     store.register_late_pass(move |_| Box::new(doc::Documentation::new(conf)));
+    store.register_early_pass(move || Box::new(doc::Documentation::new(conf)));
     store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply));
     store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq));
     store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
@@ -860,6 +861,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_early_pass(|| Box::new(pub_use::PubUse));
     store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
     store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf)));
+    store.register_early_pass(move || Box::new(large_include_file::LargeIncludeFile::new(conf)));
     store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace));
     store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
     store.register_early_pass(|| Box::<duplicate_mod::DuplicateMod>::default());
diff --git a/src/tools/clippy/clippy_lints/src/macro_use.rs b/src/tools/clippy/clippy_lints/src/macro_use.rs
index 37412866539..165e8c2ea05 100644
--- a/src/tools/clippy/clippy_lints/src/macro_use.rs
+++ b/src/tools/clippy/clippy_lints/src/macro_use.rs
@@ -94,7 +94,7 @@ impl LateLintPass<'_> for MacroUseImports {
         {
             for kid in cx.tcx.module_children(id) {
                 if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
-                    let span = mac_attr.span;
+                    let span = mac_attr.span();
                     let def_path = cx.tcx.def_path_str(mac_id);
                     self.imports.push((def_path, span, hir_id));
                 }
@@ -104,8 +104,8 @@ impl LateLintPass<'_> for MacroUseImports {
         }
     }
     fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &hir::Attribute) {
-        if attr.span.from_expansion() {
-            self.push_unique_macro(cx, attr.span);
+        if attr.span().from_expansion() {
+            self.push_unique_macro(cx, attr.span());
         }
     }
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
index 00800231fe4..83d8a509390 100644
--- a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
@@ -116,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
                             if let Some(non_exhaustive) =
                                 attr::find_by_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive)
                             {
-                                diag.span_note(non_exhaustive.span, "the struct is already non-exhaustive");
+                                diag.span_note(non_exhaustive.span(), "the struct is already non-exhaustive");
                             } else {
                                 let indent = snippet_indent(cx, item.span).unwrap_or_default();
                                 diag.span_suggestion_verbose(
diff --git a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs
index b73b9083a99..6eca3f12cf2 100644
--- a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs
+++ b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs
@@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi {
                         .span
                         .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len()))
                         .shrink_to_lo();
-                    let attr_snippet = snippet(cx, attr.span, "..");
+                    let attr_snippet = snippet(cx, attr.span(), "..");
 
                     span_lint_and_then(
                         cx,
diff --git a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
index 93abf95e357..16916e3aaad 100644
--- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
+++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
@@ -432,7 +432,7 @@ fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Spa
         .hir()
         .attrs(hir_id)
         .iter()
-        .fold(span, |acc, attr| acc.to(attr.span)))
+        .fold(span, |acc, attr| acc.to(attr.span())))
 }
 
 enum HasSafetyComment {
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 40ddd75b7fa..7fc25e3617d 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -93,6 +93,7 @@ use std::sync::{Mutex, MutexGuard, OnceLock};
 
 use itertools::Itertools;
 use rustc_ast::ast::{self, LitKind, RangeLimits};
+use rustc_attr_parsing::{find_attr, AttributeKind};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::packed::Pu128;
 use rustc_data_structures::unhash::UnhashMap;
@@ -1949,7 +1950,7 @@ pub fn has_attr(attrs: &[hir::Attribute], symbol: Symbol) -> bool {
 }
 
 pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
-    has_attr(cx.tcx.hir().attrs(hir_id), sym::repr)
+    find_attr!(cx.tcx.hir().attrs(hir_id), AttributeKind::Repr(..))
 }
 
 pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
diff --git a/src/tools/clippy/tests/ui-toml/large_include_file/large_include_file.stderr b/src/tools/clippy/tests/ui-toml/large_include_file/large_include_file.stderr
index 82b926cc53b..c9f0e661dbd 100644
--- a/src/tools/clippy/tests/ui-toml/large_include_file/large_include_file.stderr
+++ b/src/tools/clippy/tests/ui-toml/large_include_file/large_include_file.stderr
@@ -1,26 +1,26 @@
 error: attempted to include a large file
-  --> tests/ui-toml/large_include_file/large_include_file.rs:14:43
+  --> tests/ui-toml/large_include_file/large_include_file.rs:19:1
    |
-LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt");
-   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[doc = include_str!("too_big.txt")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: the configuration allows a maximum size of 600 bytes
    = note: `-D clippy::large-include-file` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::large_include_file)]`
 
 error: attempted to include a large file
-  --> tests/ui-toml/large_include_file/large_include_file.rs:16:35
+  --> tests/ui-toml/large_include_file/large_include_file.rs:14:43
    |
-LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt");
-   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt");
+   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: the configuration allows a maximum size of 600 bytes
 
 error: attempted to include a large file
-  --> tests/ui-toml/large_include_file/large_include_file.rs:19:1
+  --> tests/ui-toml/large_include_file/large_include_file.rs:16:35
    |
-LL | #[doc = include_str!("too_big.txt")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt");
+   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: the configuration allows a maximum size of 600 bytes
 
diff --git a/src/tools/clippy/tests/ui/must_use_unit.fixed b/src/tools/clippy/tests/ui/must_use_unit.fixed
index b92d9379c90..f2b990beddc 100644
--- a/src/tools/clippy/tests/ui/must_use_unit.fixed
+++ b/src/tools/clippy/tests/ui/must_use_unit.fixed
@@ -24,8 +24,8 @@ fn main() {
     );
 }
 
-#[cfg_attr(all(), deprecated)]
+#[cfg_attr(all(), )]
 fn issue_12320() {}
 
-#[cfg_attr(all(), deprecated, doc = "foo")]
+#[cfg_attr(all(), deprecateddoc = "foo", doc = "foo", must_use)]
 fn issue_12320_2() {}
diff --git a/src/tools/clippy/tests/ui/must_use_unit.stderr b/src/tools/clippy/tests/ui/must_use_unit.stderr
index b435568deea..40a0dc3ca9b 100644
--- a/src/tools/clippy/tests/ui/must_use_unit.stderr
+++ b/src/tools/clippy/tests/ui/must_use_unit.stderr
@@ -25,19 +25,21 @@ LL | #[must_use = "With note"]
 LL | pub fn must_use_with_note() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+[Unparsed(AttrItem { path: AttrPath { segments: [must_use#0], span: tests/ui/must_use_unit.rs:30:19: 30:27 (#0) }, args: Empty, id: HashIgnoredAttrId { attr_id: AttrId(9) }, style: Outer, span: tests/ui/must_use_unit.rs:30:19: 30:27 (#0) }), Parsed(Deprecation { deprecation: Deprecation { since: Unspecified, note: None, suggestion: None }, span: tests/ui/must_use_unit.rs:30:29: 30:39 (#0) })]
 error: this unit-returning function has a `#[must_use]` attribute
   --> tests/ui/must_use_unit.rs:31:1
    |
 LL | #[cfg_attr(all(), must_use, deprecated)]
-   |                   -------------------- help: change these attributes to: `deprecated`
+   |                   -------------------- help: change these attributes to
 LL | fn issue_12320() {}
    | ^^^^^^^^^^^^^^^^
 
+[Unparsed(AttrItem { path: AttrPath { segments: [doc#0], span: tests/ui/must_use_unit.rs:33:31: 33:34 (#0) }, args: Eq { eq_span: tests/ui/must_use_unit.rs:33:35: 33:36 (#0), expr: MetaItemLit { symbol: "foo", suffix: None, kind: Str("foo", Cooked), span: tests/ui/must_use_unit.rs:33:37: 33:42 (#0) } }, id: HashIgnoredAttrId { attr_id: AttrId(12) }, style: Outer, span: tests/ui/must_use_unit.rs:33:31: 33:42 (#0) }), Unparsed(AttrItem { path: AttrPath { segments: [must_use#0], span: tests/ui/must_use_unit.rs:33:44: 33:52 (#0) }, args: Empty, id: HashIgnoredAttrId { attr_id: AttrId(13) }, style: Outer, span: tests/ui/must_use_unit.rs:33:44: 33:52 (#0) }), Parsed(Deprecation { deprecation: Deprecation { since: Unspecified, note: None, suggestion: None }, span: tests/ui/must_use_unit.rs:33:19: 33:29 (#0) })]
 error: this unit-returning function has a `#[must_use]` attribute
   --> tests/ui/must_use_unit.rs:34:1
    |
 LL | #[cfg_attr(all(), deprecated, doc = "foo", must_use)]
-   |                   --------------------------------- help: change these attributes to: `deprecated, doc = "foo"`
+   |                             -- help: change these attributes to: `doc = "foo",`
 LL | fn issue_12320_2() {}
    | ^^^^^^^^^^^^^^^^^^
 
diff --git a/tests/rustdoc-json/enums/discriminant/struct.rs b/tests/rustdoc-json/enums/discriminant/struct.rs
index 0ac40cda733..24d5f5b08c2 100644
--- a/tests/rustdoc-json/enums/discriminant/struct.rs
+++ b/tests/rustdoc-json/enums/discriminant/struct.rs
@@ -1,7 +1,7 @@
 // ignore-tidy-linelength
 
 #[repr(i32)]
-//@ is "$.index[*][?(@.name=='Foo')].attrs" '["#[repr(i32)]"]'
+//@ is "$.index[*][?(@.name=='Foo')].attrs" '["#[attr=\"Repr([ReprInt(SignedInt(I32))])\")]\n"]'
 pub enum Foo {
     //@ is    "$.index[*][?(@.name=='Struct')].inner.variant.discriminant" null
     //@ count "$.index[*][?(@.name=='Struct')].inner.variant.kind.struct.fields[*]" 0
diff --git a/tests/rustdoc-json/enums/discriminant/tuple.rs b/tests/rustdoc-json/enums/discriminant/tuple.rs
index fbff5aacd67..a50ae8b9189 100644
--- a/tests/rustdoc-json/enums/discriminant/tuple.rs
+++ b/tests/rustdoc-json/enums/discriminant/tuple.rs
@@ -1,7 +1,7 @@
 // ignore-tidy-linelength
 
 #[repr(u32)]
-//@ is "$.index[*][?(@.name=='Foo')].attrs" '["#[repr(u32)]"]'
+//@ is "$.index[*][?(@.name=='Foo')].attrs" '["#[attr=\"Repr([ReprInt(UnsignedInt(U32))])\")]\n"]'
 pub enum Foo {
     //@ is    "$.index[*][?(@.name=='Tuple')].inner.variant.discriminant" null
     //@ count "$.index[*][?(@.name=='Tuple')].inner.variant.kind.tuple[*]" 0