Skip to content

Commit 3c02c0e

Browse files
committed
refactor(non_canonical_impls): lint starting from impls
1 parent 16880f7 commit 3c02c0e

File tree

2 files changed

+98
-31
lines changed

2 files changed

+98
-31
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: 97 additions & 30 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, 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,36 +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-
// If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
133-
// since it doesn't have an `Rhs`
134-
&& let [lhs, rhs] = trait_impl.args.as_slice() && lhs == rhs
135-
&& impl_item.ident.name == sym::partial_cmp
136-
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
137-
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
138-
{
139-
check_partial_ord_on_ord(cx, impl_item, item, 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+
},
140201
}
141202
}
142203
}
@@ -154,6 +215,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
154215
return;
155216
}
156217

218+
if is_from_proc_macro(cx, impl_item) {
219+
return;
220+
}
221+
157222
span_lint_and_sugg(
158223
cx,
159224
NON_CANONICAL_CLONE_IMPL,
@@ -165,7 +230,7 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
165230
);
166231
}
167232

168-
if impl_item.ident.name == sym::clone_from {
233+
if impl_item.ident.name == sym::clone_from && !is_from_proc_macro(cx, impl_item) {
169234
span_lint_and_sugg(
170235
cx,
171236
NON_CANONICAL_CLONE_IMPL,
@@ -206,6 +271,8 @@ fn check_partial_ord_on_ord<'tcx>(
206271
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
207272
{
208273
return;
274+
} else if is_from_proc_macro(cx, impl_item) {
275+
return;
209276
}
210277

211278
span_lint_and_then(

0 commit comments

Comments
 (0)