Skip to content

Commit 9718c47

Browse files
committed
refactor(non_canonical_impls): lint starting from impls
1 parent 21a5948 commit 9718c47

File tree

2 files changed

+78
-28
lines changed

2 files changed

+78
-28
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: 77 additions & 27 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};
9-
use rustc_lint::{LateContext, LateLintPass, LintContext};
10-
use rustc_middle::ty::{EarlyBinder, TraitRef};
11-
use rustc_session::declare_lint_pass;
7+
use rustc_hir::def_id::{DefId, LocalDefId};
8+
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::ty::{EarlyBinder, TraitRef, TyCtxt};
11+
use rustc_session::impl_lint_pass;
1212
use rustc_span::sym;
1313
use rustc_span::symbol::kw;
1414

@@ -109,33 +109,83 @@ 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())
139+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
140+
if let ItemKind::Impl(impl_) = item.kind
141+
&& (1..=2).contains(&impl_.items.len())
118142
&& 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))
122143
&& !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)
127144
{
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-
&& impl_item.ident.name == sym::partial_cmp
135-
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
136-
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
137-
{
138-
check_partial_ord_on_ord(cx, impl_item, item, &trait_impl, body, block);
145+
let assoc_fns = impl_
146+
.items
147+
.iter()
148+
.map(|id| cx.tcx.hir_impl_item(*id))
149+
.filter_map(|assoc| {
150+
if let ImplItemKind::Fn(_, assoc_id) = assoc.kind
151+
&& let body = cx.tcx.hir_body(assoc_id)
152+
&& let ExprKind::Block(block, ..) = body.value.kind
153+
{
154+
Some((assoc, body, block))
155+
} else {
156+
None
157+
}
158+
});
159+
160+
// NOTE: In the first branch, we don't want to pull the "implements `Copy`" check into the
161+
// let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
162+
// the second branch. The same goes for the "implements `Ord`" check in the second branch, but
163+
// there, the lint still fires, as there is no third branch. And we don't want that.
164+
#[expect(clippy::collapsible_if)]
165+
if Some(trait_impl.def_id) == self.clone_trait {
166+
if let Some(copy_trait) = self.copy_trait
167+
&& implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])
168+
{
169+
for (assoc, _, block) in assoc_fns {
170+
// NOTE: we don't bother doing the method name check before the proc-macro check, because we
171+
// lint both methods of `Clone`, and therefore would need to repeat the proc-macro check anyway
172+
if !is_from_proc_macro(cx, assoc) {
173+
check_clone_on_copy(cx, assoc, block);
174+
}
175+
}
176+
}
177+
} else if Some(trait_impl.def_id) == self.partial_ord_trait {
178+
if let Some(ord_trait) = self.ord_trait
179+
&& implements_trait(cx, trait_impl.self_ty(), ord_trait, &[])
180+
{
181+
for (assoc, body, block) in assoc_fns {
182+
// NOTE: `PartialOrd` has methods `lt/le/ge/gt` which we don't lint, so
183+
// don't bother checking whether _they_ come from a proc-macro
184+
if assoc.ident.name == sym::partial_cmp && !is_from_proc_macro(cx, assoc) {
185+
check_partial_ord_on_ord(cx, assoc, item, &trait_impl, body, block);
186+
}
187+
}
188+
}
139189
}
140190
}
141191
}

0 commit comments

Comments
 (0)