Skip to content

Commit e776e42

Browse files
committed
refactor(non_canonical_impls): lint starting from impls
1 parent 918c0d5 commit e776e42

File tree

2 files changed

+95
-34
lines changed

2 files changed

+95
-34
lines changed

clippy_lints/src/lib.rs

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

clippy_lints/src/non_canonical_impls.rs

Lines changed: 94 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use clippy_utils::{
44
is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core,
55
};
66
use rustc_errors::Applicability;
7-
use rustc_hir::def_id::LocalDefId;
8-
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, LangItem, Node, UnOp};
7+
use rustc_hir::def_id::{DefId, LocalDefId};
8+
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};
99
use rustc_lint::{LateContext, LateLintPass, LintContext};
10-
use rustc_middle::ty::EarlyBinder;
11-
use rustc_session::declare_lint_pass;
10+
use rustc_middle::ty::{EarlyBinder, TyCtxt};
11+
use rustc_session::impl_lint_pass;
1212
use rustc_span::sym;
1313
use rustc_span::symbol::kw;
1414

@@ -109,40 +109,91 @@ declare_clippy_lint! {
109109
suspicious,
110110
"non-canonical implementation of `PartialOrd` on an `Ord` type"
111111
}
112-
declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
112+
impl_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
113+
114+
#[expect(
115+
clippy::struct_field_names,
116+
reason = "`_trait` suffix is meaningful on its own, \
117+
and creating an inner `StoredTraits` struct would just add a level of indirection"
118+
)]
119+
pub(crate) struct NonCanonicalImpls {
120+
partial_ord_trait: Option<DefId>,
121+
ord_trait: Option<DefId>,
122+
clone_trait: Option<DefId>,
123+
copy_trait: Option<DefId>,
124+
}
125+
126+
impl NonCanonicalImpls {
127+
pub(crate) fn new(tcx: TyCtxt<'_>) -> Self {
128+
let lang_items = tcx.lang_items();
129+
Self {
130+
partial_ord_trait: lang_items.partial_ord_trait(),
131+
ord_trait: tcx.get_diagnostic_item(sym::Ord),
132+
clone_trait: lang_items.clone_trait(),
133+
copy_trait: lang_items.copy_trait(),
134+
}
135+
}
136+
}
113137

114138
impl LateLintPass<'_> for NonCanonicalImpls {
115-
fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) {
116-
if let ImplItemKind::Fn(_, impl_item_id) = impl_item.kind
117-
&& let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id())
118-
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
119-
&& let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id)
120-
// NOTE: check this early to avoid expensive checks that come after this one
121-
&& matches!(trait_name, Some(sym::Clone | sym::PartialOrd))
139+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
140+
if let ItemKind::Impl(impl_) = item.kind
141+
// Both `PartialOrd` and `Clone` have one required method, and `PartialOrd` can have 5 methods in total
142+
&& (1..=5).contains(&impl_.items.len())
143+
&& let Some(of_trait) = impl_.of_trait
144+
&& let Some(trait_did) = of_trait.trait_ref.trait_def_id()
145+
// Check this early to hopefully bail out as soon as possible
146+
&& [self.clone_trait, self.partial_ord_trait].contains(&Some(trait_did))
122147
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
123-
&& let body = cx.tcx.hir_body(impl_item_id)
124-
&& let ExprKind::Block(block, ..) = body.value.kind
125-
&& !block.span.in_external_macro(cx.sess().source_map())
126-
&& !is_from_proc_macro(cx, impl_item)
127148
{
128-
if trait_name == Some(sym::Clone)
129-
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
130-
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
131-
{
132-
check_clone_on_copy(cx, impl_item, block);
133-
} else if trait_name == Some(sym::PartialOrd)
134-
// If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
135-
// since it doesn't have an `Rhs`
136-
&& match trait_impl.args.as_slice() {
137-
[_] => true,
138-
[lhs, rhs] => lhs == rhs,
139-
_ => false,
149+
let assoc_fns = impl_
150+
.items
151+
.iter()
152+
.map(|id| cx.tcx.hir_impl_item(*id))
153+
.filter_map(|assoc| {
154+
if let ImplItemKind::Fn(_, body_id) = assoc.kind
155+
&& let body = cx.tcx.hir_body(body_id)
156+
&& let ExprKind::Block(block, ..) = body.value.kind
157+
&& !block.span.in_external_macro(cx.sess().source_map())
158+
{
159+
Some((assoc, body, block))
160+
} else {
161+
None
162+
}
163+
});
164+
165+
#[expect(clippy::collapsible_if)]
166+
// Reason: In the first branch, we don't want to pull the "implements `Copy`" check into the
167+
// let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
168+
// the second branch. The same goes for the inner checks in the second branch, but
169+
// there, the lint still fires, as there is no third branch. And we don't want that.
170+
if Some(trait_did) == self.clone_trait {
171+
if let Some(copy_trait) = self.copy_trait
172+
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
173+
&& implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])
174+
{
175+
for (assoc, _, block) in assoc_fns {
176+
check_clone_on_copy(cx, assoc, block);
177+
}
178+
}
179+
} else if Some(trait_did) == self.partial_ord_trait {
180+
if let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
181+
// If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
182+
// since it doesn't have an `Rhs`
183+
&& match trait_impl.args.as_slice() {
184+
[_] => true,
185+
[lhs, rhs] => lhs == rhs,
186+
_ => false,
187+
}
188+
&& let Some(ord_trait) = self.ord_trait
189+
&& implements_trait(cx, trait_impl.self_ty(), ord_trait, &[])
190+
{
191+
for (assoc, body, block) in assoc_fns {
192+
if assoc.ident.name == sym::partial_cmp {
193+
check_partial_ord_on_ord(cx, assoc, item, body, block);
194+
}
195+
}
140196
}
141-
&& impl_item.ident.name == sym::partial_cmp
142-
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
143-
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
144-
{
145-
check_partial_ord_on_ord(cx, impl_item, item, body, block);
146197
}
147198
}
148199
}
@@ -160,6 +211,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
160211
return;
161212
}
162213

214+
if is_from_proc_macro(cx, impl_item) {
215+
return;
216+
}
217+
163218
span_lint_and_sugg(
164219
cx,
165220
NON_CANONICAL_CLONE_IMPL,
@@ -172,6 +227,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
172227
}
173228

174229
if impl_item.ident.name == sym::clone_from {
230+
if is_from_proc_macro(cx, impl_item) {
231+
return;
232+
}
233+
175234
span_lint_and_sugg(
176235
cx,
177236
NON_CANONICAL_CLONE_IMPL,
@@ -212,6 +271,8 @@ fn check_partial_ord_on_ord<'tcx>(
212271
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
213272
{
214273
return;
274+
} else if is_from_proc_macro(cx, impl_item) {
275+
return;
215276
}
216277

217278
span_lint_and_then(

0 commit comments

Comments
 (0)