From e9fdf11c665f194bc269794e8523f61c7fb1591a Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 26 Dec 2025 20:57:35 +0100 Subject: [PATCH] Convert clippy to use the new parsed representation --- .../clippy/clippy_lints/src/attrs/mod.rs | 4 +- .../src/attrs/should_panic_without_expect.rs | 6 +-- .../clippy/clippy_lints/src/cfg_not_test.rs | 52 +++++++++++-------- .../src/doc/include_in_doc_without_cfg.rs | 4 +- .../clippy_lints/src/incompatible_msrv.rs | 9 ++-- .../clippy_lints/src/large_include_file.rs | 3 +- .../clippy_lints/src/methods/is_empty.rs | 5 +- .../clippy_lints/src/new_without_default.rs | 11 ++-- .../clippy/clippy_utils/src/ast_utils/mod.rs | 10 +++- src/tools/clippy/clippy_utils/src/lib.rs | 25 ++++----- 10 files changed, 72 insertions(+), 57 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/attrs/mod.rs b/src/tools/clippy/clippy_lints/src/attrs/mod.rs index 679ccfb8de3a..366f5873a1aa 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mod.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mod.rs @@ -16,7 +16,7 @@ mod utils; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv, MsrvStack}; -use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind}; +use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind, AttrItemKind}; use rustc_hir::{ImplItem, Item, ItemKind, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -604,7 +604,7 @@ impl EarlyLintPass for PostExpansionEarlyAttributes { if attr.has_name(sym::ignore) && match &attr.kind { - AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }), + AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrItemKind::Unparsed(AttrArgs::Eq { .. })), AttrKind::DocComment(..) => true, } { diff --git a/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs b/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs index fd27e30a67f3..b854a3070bef 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs @@ -2,19 +2,19 @@ use super::{Attribute, SHOULD_PANIC_WITHOUT_EXPECT}; use clippy_utils::diagnostics::span_lint_and_sugg; use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::TokenTree; -use rustc_ast::{AttrArgs, AttrKind}; +use rustc_ast::{AttrArgs, AttrKind, AttrItemKind}; use rustc_errors::Applicability; use rustc_lint::EarlyContext; use rustc_span::sym; pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) { if let AttrKind::Normal(normal_attr) = &attr.kind { - if let AttrArgs::Eq { .. } = &normal_attr.item.args { + if let AttrItemKind::Unparsed(AttrArgs::Eq { .. }) = &normal_attr.item.args { // `#[should_panic = ".."]` found, good return; } - if let AttrArgs::Delimited(args) = &normal_attr.item.args + if let AttrItemKind::Unparsed(AttrArgs::Delimited(args)) = &normal_attr.item.args && let mut tt_iter = args.tokens.iter() && let Some(TokenTree::Token( Token { diff --git a/src/tools/clippy/clippy_lints/src/cfg_not_test.rs b/src/tools/clippy/clippy_lints/src/cfg_not_test.rs index 7590fe96fd21..ec543d02c9dd 100644 --- a/src/tools/clippy/clippy_lints/src/cfg_not_test.rs +++ b/src/tools/clippy/clippy_lints/src/cfg_not_test.rs @@ -1,7 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; -use rustc_ast::MetaItemInner; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; +use rustc_ast::AttrItemKind; +use rustc_ast::EarlyParsedAttribute; +use rustc_span::sym; +use rustc_ast::attr::data_structures::CfgEntry; declare_clippy_lint! { /// ### What it does @@ -32,29 +35,34 @@ declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]); impl EarlyLintPass for CfgNotTest { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) { - if attr.has_name(rustc_span::sym::cfg_trace) && contains_not_test(attr.meta_item_list().as_deref(), false) { - span_lint_and_then( - cx, - CFG_NOT_TEST, - attr.span, - "code is excluded from test builds", - |diag| { - diag.help("consider not excluding any code from test builds"); - diag.note_once("this could increase code coverage despite not actually being tested"); - }, - ); + if attr.has_name(sym::cfg_trace) { + let AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)) = &attr.get_normal_item().args else { + unreachable!() + }; + + if contains_not_test(&cfg, false) { + span_lint_and_then( + cx, + CFG_NOT_TEST, + attr.span, + "code is excluded from test builds", + |diag| { + diag.help("consider not excluding any code from test builds"); + diag.note_once("this could increase code coverage despite not actually being tested"); + }, + ); + } } } } -fn contains_not_test(list: Option<&[MetaItemInner]>, not: bool) -> bool { - list.is_some_and(|list| { - list.iter().any(|item| { - item.ident().is_some_and(|ident| match ident.name { - rustc_span::sym::not => contains_not_test(item.meta_item_list(), !not), - rustc_span::sym::test => not, - _ => contains_not_test(item.meta_item_list(), not), - }) - }) - }) +fn contains_not_test(cfg: &CfgEntry, not: bool) -> bool { + match cfg { + CfgEntry::All(subs, _) | CfgEntry::Any(subs, _) => subs.iter().any(|item| { + contains_not_test(item, not) + }), + CfgEntry::Not(sub, _) => contains_not_test(sub, !not), + CfgEntry::NameValue { name: sym::test, .. } => not, + _ => false + } } 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 bca1cd03bb7e..f8e9e870f629 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,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use rustc_ast::{AttrArgs, AttrKind, AttrStyle, Attribute}; +use rustc_ast::{AttrArgs, AttrKind, AttrStyle, Attribute, AttrItemKind}; use rustc_errors::Applicability; use rustc_lint::EarlyContext; @@ -11,7 +11,7 @@ pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) { if !attr.span.from_expansion() && let AttrKind::Normal(ref item) = attr.kind && attr.doc_str().is_some() - && let AttrArgs::Eq { expr: meta, .. } = &item.item.args + && let AttrItemKind::Unparsed(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/incompatible_msrv.rs b/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs index 28ea2e4fa1f0..e0149a23fccf 100644 --- a/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs +++ b/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs @@ -9,6 +9,8 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::{ExpnKind, Span}; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::find_attr; declare_clippy_lint! { /// ### What it does @@ -268,11 +270,6 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { /// attribute. fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool { cx.tcx.hir_parent_id_iter(hir_id).any(|id| { - cx.tcx.hir_attrs(id).iter().any(|attr| { - matches!( - attr.name(), - Some(sym::cfg_trace | sym::cfg_attr_trace) - ) - }) + find_attr!(cx.tcx.hir_attrs(id), AttributeKind::CfgTrace(..) | AttributeKind::CfgAttrTrace) }) } 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 48ce1afc6e69..5c37747b8c9b 100644 --- a/src/tools/clippy/clippy_lints/src/large_include_file.rs +++ b/src/tools/clippy/clippy_lints/src/large_include_file.rs @@ -1,3 +1,4 @@ +use rustc_ast::AttrItemKind; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; @@ -92,7 +93,7 @@ impl EarlyLintPass for LargeIncludeFile { && 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.item.args + && let AttrItemKind::Unparsed(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/methods/is_empty.rs b/src/tools/clippy/clippy_lints/src/methods/is_empty.rs index add01b6a0837..834456ff6668 100644 --- a/src/tools/clippy/clippy_lints/src/methods/is_empty.rs +++ b/src/tools/clippy/clippy_lints/src/methods/is_empty.rs @@ -5,7 +5,8 @@ use clippy_utils::res::MaybeResPath; use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context}; use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; -use rustc_span::sym; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::find_attr; use super::CONST_IS_EMPTY; @@ -40,7 +41,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_ fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool { cx.tcx .hir_parent_id_iter(id) - .any(|id| cx.tcx.hir_attrs(id).iter().any(|attr| attr.has_name(sym::cfg_trace))) + .any(|id| find_attr!(cx.tcx.hir_attrs(id), AttributeKind::CfgTrace(..))) } /// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs index 6fc034b6fc5d..67493d54b552 100644 --- a/src/tools/clippy/clippy_lints/src/new_without_default.rs +++ b/src/tools/clippy/clippy_lints/src/new_without_default.rs @@ -9,6 +9,8 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocKind; use rustc_session::impl_lint_pass; use rustc_span::sym; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; declare_clippy_lint! { /// ### What it does @@ -121,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { let attrs_sugg = { let mut sugg = String::new(); for attr in cx.tcx.hir_attrs(assoc_item_hir_id) { - if !attr.has_name(sym::cfg_trace) { + let Attribute::Parsed(AttributeKind::CfgTrace(attrs)) = attr else { // This might be some other attribute that the `impl Default` ought to inherit. // But it could also be one of the many attributes that: // - can't be put on an impl block -- like `#[inline]` @@ -131,10 +133,13 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // reduce the applicability app = Applicability::MaybeIncorrect; continue; + }; + + for (_, attr_span) in attrs { + sugg.push_str(&snippet_with_applicability(cx.sess(), *attr_span, "_", &mut app)); + sugg.push('\n'); } - sugg.push_str(&snippet_with_applicability(cx.sess(), attr.span(), "_", &mut app)); - sugg.push('\n'); } sugg }; diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 432d7a251a21..618719286e8f 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -976,11 +976,19 @@ pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool { l.style == r.style && match (&l.kind, &r.kind) { (DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2, - (Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_attr_args(&l.item.args, &r.item.args), + (Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_attr_item_kind(&l.item.args, &r.item.args), _ => false, } } +pub fn eq_attr_item_kind(l: &AttrItemKind, r: &AttrItemKind) -> bool { + match (l, r) { + (AttrItemKind::Unparsed(l), AttrItemKind::Unparsed(r)) => eq_attr_args(l, r), + (AttrItemKind::Parsed(_l), AttrItemKind::Parsed(_r)) => todo!(), + _ => false, + } +} + pub fn eq_attr_args(l: &AttrArgs, r: &AttrArgs) -> bool { use AttrArgs::*; match (l, r) { diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 954c32687af6..38e1542cd758 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -121,6 +121,7 @@ use rustc_middle::ty::{ self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt, TypeFlags, TypeVisitableExt, UintTy, UpvarCapture, }; +use rustc_hir::attrs::CfgEntry; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; @@ -2401,17 +2402,12 @@ pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool { /// This only checks directly applied attributes, to see if a node is inside a `#[cfg(test)]` parent /// use [`is_in_cfg_test`] pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool { - tcx.hir_attrs(id).iter().any(|attr| { - if attr.has_name(sym::cfg_trace) - && let Some(items) = attr.meta_item_list() - && let [item] = &*items - && item.has_name(sym::test) - { - true - } else { - false - } - }) + if let Some(cfgs) = find_attr!(tcx.hir_attrs(id), AttributeKind::CfgTrace(cfgs) => cfgs) + && cfgs.iter().any(|(cfg, _)| { matches!(cfg, CfgEntry::NameValue { name: sym::test, ..})}) { + true + } else { + false + } } /// Checks if any parent node of `HirId` has `#[cfg(test)]` attribute applied @@ -2426,11 +2422,10 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { /// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied. pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { - tcx.has_attr(def_id, sym::cfg_trace) - || tcx + find_attr!(tcx.get_all_attrs(def_id), AttributeKind::CfgTrace(..)) + || find_attr!(tcx .hir_parent_iter(tcx.local_def_id_to_hir_id(def_id)) - .flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)) - .any(|attr| attr.has_name(sym::cfg_trace)) + .flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)), AttributeKind::CfgTrace(..)) } /// Walks up the HIR tree from the given expression in an attempt to find where the value is