Skip to content

Commit ec8e8fd

Browse files
authored
refactor(non_canonical_impls): lint starting from impls (#15749)
Based on #15520 (comment), with the following changes/additions: - No longer use the `Trait` enum for quickly filtering out irrelevant impls -- instead, check the `trait_of` basically right away: 1. pre-fetch `DefId`s of the relevant traits into the lint pass 2. reuse `cx.tcx.impl_trait_ref`'s output for the the `DefId` of the trait being implemented 3. compare those `DefId`s, which should be very cheap - Next, check whether `self_ty` implements the other relevant trait. - Pre-filter impl items in the same (lazy) iterator, but delay the proc-macro check as much as possible changelog: none Not auto-assigning since @blyxyas and/or @Jarcho will be the ones reviewing it: r? ghost
2 parents d230acd + 3c02c0e commit ec8e8fd

8 files changed

+357
-67
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
765765
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf)));
766766
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
767767
store.register_late_pass(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf)));
768-
store.register_late_pass(|_| Box::new(non_canonical_impls::NonCanonicalImpls));
768+
store.register_late_pass(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx)));
769769
store.register_late_pass(move |_| Box::new(single_call_fn::SingleCallFn::new(conf)));
770770
store.register_early_pass(move || Box::new(raw_strings::RawStrings::new(conf)));
771771
store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf)));

clippy_lints/src/non_canonical_impls.rs

Lines changed: 96 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use clippy_utils::res::{MaybeDef, MaybeQPath};
33
use clippy_utils::ty::implements_trait;
44
use clippy_utils::{is_from_proc_macro, last_path_segment, std_or_core};
55
use rustc_errors::Applicability;
6-
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, LangItem, Node, UnOp};
6+
use rustc_hir::def_id::DefId;
7+
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};
78
use rustc_lint::{LateContext, LateLintPass, LintContext};
8-
use rustc_middle::ty::{EarlyBinder, TraitRef, TypeckResults};
9-
use rustc_session::declare_lint_pass;
9+
use rustc_middle::ty::{EarlyBinder, TyCtxt, TypeckResults};
10+
use rustc_session::impl_lint_pass;
1011
use rustc_span::sym;
1112
use rustc_span::symbol::kw;
1213

@@ -107,33 +108,96 @@ declare_clippy_lint! {
107108
suspicious,
108109
"non-canonical implementation of `PartialOrd` on an `Ord` type"
109110
}
110-
declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
111+
impl_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
112+
113+
#[expect(
114+
clippy::struct_field_names,
115+
reason = "`_trait` suffix is meaningful on its own, \
116+
and creating an inner `StoredTraits` struct would just add a level of indirection"
117+
)]
118+
pub(crate) struct NonCanonicalImpls {
119+
partial_ord_trait: Option<DefId>,
120+
ord_trait: Option<DefId>,
121+
clone_trait: Option<DefId>,
122+
copy_trait: Option<DefId>,
123+
}
124+
125+
impl NonCanonicalImpls {
126+
pub(crate) fn new(tcx: TyCtxt<'_>) -> Self {
127+
let lang_items = tcx.lang_items();
128+
Self {
129+
partial_ord_trait: lang_items.partial_ord_trait(),
130+
ord_trait: tcx.get_diagnostic_item(sym::Ord),
131+
clone_trait: lang_items.clone_trait(),
132+
copy_trait: lang_items.copy_trait(),
133+
}
134+
}
135+
}
136+
137+
/// The traits that this lint looks at
138+
enum Trait {
139+
Clone,
140+
PartialOrd,
141+
}
111142

112143
impl LateLintPass<'_> for NonCanonicalImpls {
113-
fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) {
114-
if let ImplItemKind::Fn(_, impl_item_id) = impl_item.kind
115-
&& let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id())
116-
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
117-
&& let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id)
118-
// NOTE: check this early to avoid expensive checks that come after this one
119-
&& matches!(trait_name, Some(sym::Clone | sym::PartialOrd))
144+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
145+
if let ItemKind::Impl(impl_) = item.kind
146+
// Both `PartialOrd` and `Clone` have one required method, and `PartialOrd` can have 5 methods in total
147+
&& (1..=5).contains(&impl_.items.len())
148+
&& let Some(of_trait) = impl_.of_trait
149+
&& let Some(trait_did) = of_trait.trait_ref.trait_def_id()
150+
// Check this early to hopefully bail out as soon as possible
151+
&& let trait_ = if Some(trait_did) == self.clone_trait {
152+
Trait::Clone
153+
} else if Some(trait_did) == self.partial_ord_trait {
154+
Trait::PartialOrd
155+
} else {
156+
return;
157+
}
120158
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
121-
&& let body = cx.tcx.hir_body(impl_item_id)
122-
&& let ExprKind::Block(block, ..) = body.value.kind
123-
&& !block.span.in_external_macro(cx.sess().source_map())
124-
&& !is_from_proc_macro(cx, impl_item)
125159
{
126-
if trait_name == Some(sym::Clone)
127-
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
128-
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
129-
{
130-
check_clone_on_copy(cx, impl_item, block);
131-
} else if trait_name == Some(sym::PartialOrd)
132-
&& impl_item.ident.name == sym::partial_cmp
133-
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
134-
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
135-
{
136-
check_partial_ord_on_ord(cx, impl_item, item, &trait_impl, body, block);
160+
let mut assoc_fns = impl_
161+
.items
162+
.iter()
163+
.map(|id| cx.tcx.hir_impl_item(*id))
164+
.filter_map(|assoc| {
165+
if let ImplItemKind::Fn(_, body_id) = assoc.kind
166+
&& let body = cx.tcx.hir_body(body_id)
167+
&& let ExprKind::Block(block, ..) = body.value.kind
168+
&& !block.span.in_external_macro(cx.sess().source_map())
169+
{
170+
Some((assoc, body, block))
171+
} else {
172+
None
173+
}
174+
});
175+
176+
match trait_ {
177+
Trait::Clone => {
178+
if let Some(copy_trait) = self.copy_trait
179+
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
180+
&& implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])
181+
{
182+
for (assoc, _, block) in assoc_fns {
183+
check_clone_on_copy(cx, assoc, block);
184+
}
185+
}
186+
},
187+
Trait::PartialOrd => {
188+
if let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
189+
// If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
190+
// since it doesn't have an `Rhs`
191+
&& let [lhs, rhs] = trait_impl.args.as_slice()
192+
&& lhs == rhs
193+
&& let Some(ord_trait) = self.ord_trait
194+
&& implements_trait(cx, trait_impl.self_ty(), ord_trait, &[])
195+
&& let Some((assoc, body, block)) =
196+
assoc_fns.find(|(assoc, _, _)| assoc.ident.name == sym::partial_cmp)
197+
{
198+
check_partial_ord_on_ord(cx, assoc, item, body, block);
199+
}
200+
},
137201
}
138202
}
139203
}
@@ -151,6 +215,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
151215
return;
152216
}
153217

218+
if is_from_proc_macro(cx, impl_item) {
219+
return;
220+
}
221+
154222
span_lint_and_sugg(
155223
cx,
156224
NON_CANONICAL_CLONE_IMPL,
@@ -162,7 +230,7 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
162230
);
163231
}
164232

165-
if impl_item.ident.name == sym::clone_from {
233+
if impl_item.ident.name == sym::clone_from && !is_from_proc_macro(cx, impl_item) {
166234
span_lint_and_sugg(
167235
cx,
168236
NON_CANONICAL_CLONE_IMPL,
@@ -179,7 +247,6 @@ fn check_partial_ord_on_ord<'tcx>(
179247
cx: &LateContext<'tcx>,
180248
impl_item: &ImplItem<'_>,
181249
item: &Item<'_>,
182-
trait_impl: &TraitRef<'_>,
183250
body: &Body<'_>,
184251
block: &Block<'tcx>,
185252
) {
@@ -204,12 +271,7 @@ fn check_partial_ord_on_ord<'tcx>(
204271
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
205272
{
206273
return;
207-
}
208-
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
209-
// suggestion tons more complex.
210-
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
211-
&& lhs != rhs
212-
{
274+
} else if is_from_proc_macro(cx, impl_item) {
213275
return;
214276
}
215277

tests/ui/non_canonical_clone_impl.fixed

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![no_main]
55

66
extern crate proc_macros;
7-
use proc_macros::with_span;
7+
use proc_macros::inline_macros;
88

99
// lint
1010

@@ -100,18 +100,45 @@ impl<A: Copy> Clone for Uwu<A> {
100100

101101
impl<A: std::fmt::Debug + Copy + Clone> Copy for Uwu<A> {}
102102

103-
// should skip proc macros, see https://github.com/rust-lang/rust-clippy/issues/12788
104-
#[derive(proc_macro_derive::NonCanonicalClone)]
105-
pub struct G;
103+
#[inline_macros]
104+
mod issue12788 {
105+
use proc_macros::{external, with_span};
106106

107-
with_span!(
108-
span
107+
// lint -- not an external macro
108+
inline!(
109+
#[derive(Copy)]
110+
pub struct A;
109111

110-
#[derive(Copy)]
111-
struct H;
112-
impl Clone for H {
113-
fn clone(&self) -> Self {
114-
todo!()
112+
impl Clone for A {
113+
fn clone(&self) -> Self { *self }
115114
}
116-
}
117-
);
115+
);
116+
117+
// do not lint -- should skip external macros
118+
external!(
119+
#[derive(Copy)]
120+
pub struct B;
121+
122+
impl Clone for B {
123+
fn clone(&self) -> Self {
124+
todo!()
125+
}
126+
}
127+
);
128+
129+
// do not lint -- should skip proc macros
130+
#[derive(proc_macro_derive::NonCanonicalClone)]
131+
pub struct C;
132+
133+
with_span!(
134+
span
135+
136+
#[derive(Copy)]
137+
struct D;
138+
impl Clone for D {
139+
fn clone(&self) -> Self {
140+
todo!()
141+
}
142+
}
143+
);
144+
}

tests/ui/non_canonical_clone_impl.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![no_main]
55

66
extern crate proc_macros;
7-
use proc_macros::with_span;
7+
use proc_macros::inline_macros;
88

99
// lint
1010

@@ -114,18 +114,48 @@ impl<A: Copy> Clone for Uwu<A> {
114114

115115
impl<A: std::fmt::Debug + Copy + Clone> Copy for Uwu<A> {}
116116

117-
// should skip proc macros, see https://github.com/rust-lang/rust-clippy/issues/12788
118-
#[derive(proc_macro_derive::NonCanonicalClone)]
119-
pub struct G;
117+
#[inline_macros]
118+
mod issue12788 {
119+
use proc_macros::{external, with_span};
120120

121-
with_span!(
122-
span
121+
// lint -- not an external macro
122+
inline!(
123+
#[derive(Copy)]
124+
pub struct A;
123125

124-
#[derive(Copy)]
125-
struct H;
126-
impl Clone for H {
127-
fn clone(&self) -> Self {
128-
todo!()
126+
impl Clone for A {
127+
fn clone(&self) -> Self {
128+
//~^ non_canonical_clone_impl
129+
todo!()
130+
}
129131
}
130-
}
131-
);
132+
);
133+
134+
// do not lint -- should skip external macros
135+
external!(
136+
#[derive(Copy)]
137+
pub struct B;
138+
139+
impl Clone for B {
140+
fn clone(&self) -> Self {
141+
todo!()
142+
}
143+
}
144+
);
145+
146+
// do not lint -- should skip proc macros
147+
#[derive(proc_macro_derive::NonCanonicalClone)]
148+
pub struct C;
149+
150+
with_span!(
151+
span
152+
153+
#[derive(Copy)]
154+
struct D;
155+
impl Clone for D {
156+
fn clone(&self) -> Self {
157+
todo!()
158+
}
159+
}
160+
);
161+
}

tests/ui/non_canonical_clone_impl.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,17 @@ LL | | *self = source.clone();
4141
LL | | }
4242
| |_____^ help: remove it
4343

44-
error: aborting due to 4 previous errors
44+
error: non-canonical implementation of `clone` on a `Copy` type
45+
--> tests/ui/non_canonical_clone_impl.rs:127:37
46+
|
47+
LL | fn clone(&self) -> Self {
48+
| _____________________________________^
49+
LL | |
50+
LL | | todo!()
51+
LL | | }
52+
| |_____________^ help: change this to: `{ *self }`
53+
|
54+
= note: this error originates in the macro `__inline_mac_mod_issue12788` (in Nightly builds, run with -Z macro-backtrace for more info)
55+
56+
error: aborting due to 5 previous errors
4557

0 commit comments

Comments
 (0)