From d84505b2ca9d5c79c3700eb2098f8d0dc9aabb2e Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 18 Sep 2025 22:30:21 +0200 Subject: [PATCH] Post lint on the type HIR node Out of the 5 lints in the `derive` directory, only two of them (`unsafe_derive_deserialize` and `derive_partial_eq_without_eq`) posted the lint on the ADT node. This fixes the situation for the three other lints: - `derive_hash_with_manual_eq` - `derive_ord_xor_partial_ord` - `expl_impl_clone_on_copy` This allows `#[expect]` to be used on the ADT to silence the lint. Also, this makes `expl_impl_clone_on_copy` properly use an "help" message instead of a "note" one when suggesting a fix. --- .../src/derive/derive_ord_xor_partial_ord.rs | 7 ++++--- .../derive/derive_partial_eq_without_eq.rs | 13 ++++++++---- .../src/derive/derived_hash_with_manual_eq.rs | 10 ++++++---- .../src/derive/expl_impl_clone_on_copy.rs | 20 +++++++++++++------ clippy_lints/src/derive/mod.rs | 16 ++++++++++----- .../src/derive/unsafe_derive_deserialize.rs | 12 +++++++---- tests/ui/derive.rs | 13 ++++++++++++ tests/ui/derive.stderr | 10 +++++----- tests/ui/derive_ord_xor_partial_ord.rs | 15 ++++++++++++++ tests/ui/derived_hash_with_manual_eq.rs | 16 +++++++++++++++ 10 files changed, 101 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs index cbbcb2f7a3ba..274c699ff9d2 100644 --- a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs +++ b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use rustc_hir as hir; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use rustc_hir::{self as hir, HirId}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; @@ -12,6 +12,7 @@ pub(super) fn check<'tcx>( span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>, + adt_hir_id: HirId, ord_is_automatically_derived: bool, ) { if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) @@ -38,7 +39,7 @@ pub(super) fn check<'tcx>( "you are deriving `Ord` but have implemented `PartialOrd` explicitly" }; - span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| { + span_lint_hir_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, adt_hir_id, span, mess, |diag| { if let Some(local_def_id) = impl_id.as_local() { let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); diff --git a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs index ed7881c461ff..fbace0bd73ac 100644 --- a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs +++ b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs @@ -2,8 +2,8 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::has_non_exhaustive_attr; use clippy_utils::ty::implements_trait_with_env; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, HirId}; use rustc_lint::LateContext; use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast}; use rustc_span::{Span, sym}; @@ -11,7 +11,13 @@ use rustc_span::{Span, sym}; use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ; /// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { if let ty::Adt(adt, args) = ty.kind() && cx.tcx.visibility(adt.did()).is_public() && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) @@ -20,7 +26,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::T && !has_non_exhaustive_attr(cx.tcx, *adt) && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) - && let Some(local_def_id) = adt.did().as_local() // If all of our fields implement `Eq`, we can implement `Eq` too && adt .all_fields() @@ -30,7 +35,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::T span_lint_hir_and_then( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, - cx.tcx.local_def_id_to_hir_id(local_def_id), + adt_hir_id, span.ctxt().outer_expn_data().call_site, "you are deriving `PartialEq` and can implement `Eq`", |diag| { diff --git a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs index 6f36a58025a2..afc02ce32d48 100644 --- a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs +++ b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use rustc_hir as hir; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use rustc_hir::{HirId, TraitRef}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; @@ -10,8 +10,9 @@ use super::DERIVED_HASH_WITH_MANUAL_EQ; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, span: Span, - trait_ref: &hir::TraitRef<'_>, + trait_ref: &TraitRef<'_>, ty: Ty<'tcx>, + adt_hir_id: HirId, hash_is_automatically_derived: bool, ) { if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait() @@ -31,9 +32,10 @@ pub(super) fn check<'tcx>( // Only care about `impl PartialEq for Foo` // For `impl PartialEq for A, input_types is [A, B] if trait_ref.instantiate_identity().args.type_at(1) == ty { - span_lint_and_then( + span_lint_hir_and_then( cx, DERIVED_HASH_WITH_MANUAL_EQ, + adt_hir_id, span, "you are deriving `Hash` but have implemented `PartialEq` explicitly", |diag| { diff --git a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs index 6b97b4bd6b4d..dfb723b86eb9 100644 --- a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs +++ b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs @@ -1,13 +1,19 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::ty::{implements_trait, is_copy}; -use rustc_hir::{self as hir, Item}; +use rustc_hir::{self as hir, HirId, Item}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; use super::EXPL_IMPL_CLONE_ON_COPY; /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { let clone_id = match cx.tcx.lang_items().clone_trait() { Some(id) if trait_ref.trait_def_id() == Some(id) => id, _ => return, @@ -54,12 +60,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h return; } - span_lint_and_note( + span_lint_hir_and_then( cx, EXPL_IMPL_CLONE_ON_COPY, + adt_hir_id, item.span, "you are implementing `Clone` explicitly on a `Copy` type", - Some(item.span), - "consider deriving `Clone` or removing `Copy`", + |diag| { + diag.span_help(item.span, "consider deriving `Clone` or removing `Copy`"); + }, ); } diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index 1d63394ce37d..06efc2709faa 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -1,3 +1,5 @@ +use clippy_utils::path_res; +use rustc_hir::def::Res; use rustc_hir::{Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -194,21 +196,25 @@ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { of_trait: Some(of_trait), + self_ty, .. }) = item.kind + && let Res::Def(_, def_id) = path_res(cx, self_ty) + && let Some(local_def_id) = def_id.as_local() { + let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); let trait_ref = &of_trait.trait_ref; let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); - derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, is_automatically_derived); - derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, is_automatically_derived); + derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); + derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); if is_automatically_derived { - unsafe_derive_deserialize::check(cx, item, trait_ref, ty); - derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty); + unsafe_derive_deserialize::check(cx, item, trait_ref, ty, adt_hir_id); + derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty, adt_hir_id); } else { - expl_impl_clone_on_copy::check(cx, item, trait_ref, ty); + expl_impl_clone_on_copy::check(cx, item, trait_ref, ty, adt_hir_id); } } } diff --git a/clippy_lints/src/derive/unsafe_derive_deserialize.rs b/clippy_lints/src/derive/unsafe_derive_deserialize.rs index c391e7b62289..38f3251fd389 100644 --- a/clippy_lints/src/derive/unsafe_derive_deserialize.rs +++ b/clippy_lints/src/derive/unsafe_derive_deserialize.rs @@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::{is_lint_allowed, paths}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item}; -use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Item, UnsafeSource}; +use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, UnsafeSource}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; @@ -13,7 +13,13 @@ use rustc_span::{Span, sym}; use super::UNSAFE_DERIVE_DESERIALIZE; /// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { let mut visitor = UnsafeVisitor { cx }; walk_item(&mut visitor, item).is_break() @@ -22,8 +28,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h if let Some(trait_def_id) = trait_ref.trait_def_id() && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id) && let ty::Adt(def, _) = ty.kind() - && let Some(local_def_id) = def.did().as_local() - && let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) && !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id) && cx .tcx diff --git a/tests/ui/derive.rs b/tests/ui/derive.rs index 2c5fa0be9755..305f73c92cd5 100644 --- a/tests/ui/derive.rs +++ b/tests/ui/derive.rs @@ -130,3 +130,16 @@ fn issue14558() { } fn main() {} + +mod issue15708 { + // Check that the lint posts on the type definition node + #[expect(clippy::expl_impl_clone_on_copy)] + #[derive(Copy)] + struct S; + + impl Clone for S { + fn clone(&self) -> Self { + S + } + } +} diff --git a/tests/ui/derive.stderr b/tests/ui/derive.stderr index ff2c24ff48ee..48e55fc7469e 100644 --- a/tests/ui/derive.stderr +++ b/tests/ui/derive.stderr @@ -9,7 +9,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:15:1 | LL | / impl Clone for Qux { @@ -33,7 +33,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:41:1 | LL | / impl<'a> Clone for Lt<'a> { @@ -55,7 +55,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:54:1 | LL | / impl Clone for BigArray { @@ -77,7 +77,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:67:1 | LL | / impl Clone for FnPtr { @@ -99,7 +99,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:89:1 | LL | / impl Clone for Generic2 { diff --git a/tests/ui/derive_ord_xor_partial_ord.rs b/tests/ui/derive_ord_xor_partial_ord.rs index 3ef4ee9463dc..b4bb24b0d2fe 100644 --- a/tests/ui/derive_ord_xor_partial_ord.rs +++ b/tests/ui/derive_ord_xor_partial_ord.rs @@ -76,3 +76,18 @@ mod use_ord { } fn main() {} + +mod issue15708 { + use std::cmp::{Ord, Ordering}; + + // Check that the lint posts on the type definition node + #[expect(clippy::derive_ord_xor_partial_ord)] + #[derive(PartialOrd, PartialEq, Eq)] + struct DerivePartialOrdInUseOrd; + + impl Ord for DerivePartialOrdInUseOrd { + fn cmp(&self, other: &Self) -> Ordering { + Ordering::Less + } + } +} diff --git a/tests/ui/derived_hash_with_manual_eq.rs b/tests/ui/derived_hash_with_manual_eq.rs index 88b574add3f2..9f5c85d7fbc3 100644 --- a/tests/ui/derived_hash_with_manual_eq.rs +++ b/tests/ui/derived_hash_with_manual_eq.rs @@ -41,3 +41,19 @@ impl std::hash::Hash for Bah { } fn main() {} + +mod issue15708 { + // Check that the lint posts on the type definition node + #[expect(clippy::derived_hash_with_manual_eq)] + #[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, Hash)] + pub struct Span { + start: usize, + end: usize, + } + + impl PartialEq for Span { + fn eq(&self, other: &Self) -> bool { + self.start.cmp(&other.start).then(self.end.cmp(&other.end)).is_eq() + } + } +}