Skip to content

Commit 3e0590c

Browse files
authored
non_canonical_impls: split the main check function (#15520)
this is based #15519, but mainly to avoid gnarly rebase conflicts later changelog: none
2 parents 4f07fe2 + 21a5948 commit 3e0590c

File tree

1 file changed

+149
-142
lines changed

1 file changed

+149
-142
lines changed

clippy_lints/src/non_canonical_impls.rs

Lines changed: 149 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use clippy_utils::{
55
};
66
use rustc_errors::Applicability;
77
use rustc_hir::def_id::LocalDefId;
8-
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
8+
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, LangItem, Node, UnOp};
99
use rustc_lint::{LateContext, LateLintPass, LintContext};
10-
use rustc_middle::ty::EarlyBinder;
10+
use rustc_middle::ty::{EarlyBinder, TraitRef};
1111
use rustc_session::declare_lint_pass;
1212
use rustc_span::sym;
1313
use rustc_span::symbol::kw;
@@ -112,140 +112,146 @@ declare_clippy_lint! {
112112
declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
113113

114114
impl LateLintPass<'_> for NonCanonicalImpls {
115-
#[expect(clippy::too_many_lines)]
116115
fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) {
117-
let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) else {
118-
return;
119-
};
120-
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
121-
return;
122-
};
123-
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
124-
return;
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))
122+
&& !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)
127+
{
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);
139+
}
125140
}
126-
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir_impl_item(impl_item.impl_item_id()).kind else {
127-
return;
128-
};
129-
let body = cx.tcx.hir_body(impl_item_id);
130-
let ExprKind::Block(block, ..) = body.value.kind else {
131-
return;
132-
};
133-
if block.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, impl_item) {
141+
}
142+
}
143+
144+
fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) {
145+
if impl_item.ident.name == sym::clone {
146+
if block.stmts.is_empty()
147+
&& let Some(expr) = block.expr
148+
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
149+
&& let ExprKind::Path(qpath) = deref.kind
150+
&& last_path_segment(&qpath).ident.name == kw::SelfLower
151+
{
152+
// this is the canonical implementation, `fn clone(&self) -> Self { *self }`
134153
return;
135154
}
136155

137-
let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id);
138-
if trait_name == Some(sym::Clone)
139-
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
140-
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
141-
{
142-
if impl_item.ident.name == sym::clone {
143-
if block.stmts.is_empty()
144-
&& let Some(expr) = block.expr
145-
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
146-
&& let ExprKind::Path(qpath) = deref.kind
147-
&& last_path_segment(&qpath).ident.name == kw::SelfLower
148-
{
149-
} else {
150-
span_lint_and_sugg(
151-
cx,
152-
NON_CANONICAL_CLONE_IMPL,
153-
block.span,
154-
"non-canonical implementation of `clone` on a `Copy` type",
155-
"change this to",
156-
"{ *self }".to_owned(),
157-
Applicability::MaybeIncorrect,
158-
);
156+
span_lint_and_sugg(
157+
cx,
158+
NON_CANONICAL_CLONE_IMPL,
159+
block.span,
160+
"non-canonical implementation of `clone` on a `Copy` type",
161+
"change this to",
162+
"{ *self }".to_owned(),
163+
Applicability::MaybeIncorrect,
164+
);
165+
}
159166

160-
return;
161-
}
162-
}
167+
if impl_item.ident.name == sym::clone_from {
168+
span_lint_and_sugg(
169+
cx,
170+
NON_CANONICAL_CLONE_IMPL,
171+
impl_item.span,
172+
"unnecessary implementation of `clone_from` on a `Copy` type",
173+
"remove it",
174+
String::new(),
175+
Applicability::MaybeIncorrect,
176+
);
177+
}
178+
}
163179

164-
if impl_item.ident.name == sym::clone_from {
165-
span_lint_and_sugg(
166-
cx,
167-
NON_CANONICAL_CLONE_IMPL,
168-
impl_item.span,
169-
"unnecessary implementation of `clone_from` on a `Copy` type",
170-
"remove it",
171-
String::new(),
172-
Applicability::MaybeIncorrect,
173-
);
174-
}
175-
} else if trait_name == Some(sym::PartialOrd)
176-
&& impl_item.ident.name == sym::partial_cmp
177-
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
178-
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
179-
{
180-
// If the `cmp` call likely needs to be fully qualified in the suggestion
181-
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
182-
// access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
183-
let mut needs_fully_qualified = false;
180+
fn check_partial_ord_on_ord<'tcx>(
181+
cx: &LateContext<'tcx>,
182+
impl_item: &ImplItem<'_>,
183+
item: &Item<'_>,
184+
trait_impl: &TraitRef<'_>,
185+
body: &Body<'_>,
186+
block: &Block<'tcx>,
187+
) {
188+
// If the `cmp` call likely needs to be fully qualified in the suggestion
189+
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
190+
// access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
184191

185-
if block.stmts.is_empty()
186-
&& let Some(expr) = block.expr
187-
&& expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified)
188-
{
189-
return;
190-
}
191-
// Fix #12683, allow [`needless_return`] here
192-
else if block.expr.is_none()
193-
&& let Some(stmt) = block.stmts.first()
194-
&& let rustc_hir::StmtKind::Semi(Expr {
195-
kind: ExprKind::Ret(Some(ret)),
196-
..
197-
}) = stmt.kind
198-
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
199-
{
192+
let mut needs_fully_qualified = false;
193+
if block.stmts.is_empty()
194+
&& let Some(expr) = block.expr
195+
&& expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified)
196+
{
197+
return;
198+
}
199+
// Fix #12683, allow [`needless_return`] here
200+
else if block.expr.is_none()
201+
&& let Some(stmt) = block.stmts.first()
202+
&& let rustc_hir::StmtKind::Semi(Expr {
203+
kind: ExprKind::Ret(Some(ret)),
204+
..
205+
}) = stmt.kind
206+
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
207+
{
208+
return;
209+
}
210+
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
211+
// suggestion tons more complex.
212+
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
213+
&& lhs != rhs
214+
{
215+
return;
216+
}
217+
218+
span_lint_and_then(
219+
cx,
220+
NON_CANONICAL_PARTIAL_ORD_IMPL,
221+
item.span,
222+
"non-canonical implementation of `partial_cmp` on an `Ord` type",
223+
|diag| {
224+
let [_, other] = body.params else {
200225
return;
201-
}
202-
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
203-
// suggestion tons more complex.
204-
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
205-
&& lhs != rhs
206-
{
226+
};
227+
let Some(std_or_core) = std_or_core(cx) else {
207228
return;
208-
}
209-
210-
span_lint_and_then(
211-
cx,
212-
NON_CANONICAL_PARTIAL_ORD_IMPL,
213-
item.span,
214-
"non-canonical implementation of `partial_cmp` on an `Ord` type",
215-
|diag| {
216-
let [_, other] = body.params else {
217-
return;
218-
};
219-
let Some(std_or_core) = std_or_core(cx) else {
220-
return;
221-
};
222-
223-
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
224-
(Some(other_ident), true) => vec![(
225-
block.span,
226-
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
227-
)],
228-
(Some(other_ident), false) => {
229-
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
230-
},
231-
(None, true) => vec![
232-
(
233-
block.span,
234-
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
235-
),
236-
(other.pat.span, "other".to_owned()),
237-
],
238-
(None, false) => vec![
239-
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
240-
(other.pat.span, "other".to_owned()),
241-
],
242-
};
229+
};
243230

244-
diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);
231+
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
232+
(Some(other_ident), true) => vec![(
233+
block.span,
234+
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
235+
)],
236+
(Some(other_ident), false) => {
237+
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
245238
},
246-
);
247-
}
248-
}
239+
(None, true) => vec![
240+
(
241+
block.span,
242+
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
243+
),
244+
(other.pat.span, "other".to_owned()),
245+
],
246+
(None, false) => vec![
247+
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
248+
(other.pat.span, "other".to_owned()),
249+
],
250+
};
251+
252+
diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);
253+
},
254+
);
249255
}
250256

251257
/// Return true if `expr_kind` is a `cmp` call.
@@ -256,26 +262,27 @@ fn expr_is_cmp<'tcx>(
256262
needs_fully_qualified: &mut bool,
257263
) -> bool {
258264
let impl_item_did = impl_item.owner_id.def_id;
259-
if let ExprKind::Call(
260-
Expr {
261-
kind: ExprKind::Path(some_path),
262-
hir_id: some_hir_id,
263-
..
264-
},
265-
[cmp_expr],
266-
) = expr.kind
267-
{
268-
is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
265+
match expr.kind {
266+
ExprKind::Call(
267+
Expr {
268+
kind: ExprKind::Path(some_path),
269+
hir_id: some_hir_id,
270+
..
271+
},
272+
[cmp_expr],
273+
) => {
274+
is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
269275
// Fix #11178, allow `Self::cmp(self, ..)` too
270276
&& self_cmp_call(cx, cmp_expr, impl_item_did, needs_fully_qualified)
271-
} else if let ExprKind::MethodCall(_, recv, [], _) = expr.kind {
272-
cx.tcx
273-
.typeck(impl_item_did)
274-
.type_dependent_def_id(expr.hir_id)
275-
.is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into))
276-
&& self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified)
277-
} else {
278-
false
277+
},
278+
ExprKind::MethodCall(_, recv, [], _) => {
279+
cx.tcx
280+
.typeck(impl_item_did)
281+
.type_dependent_def_id(expr.hir_id)
282+
.is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into))
283+
&& self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified)
284+
},
285+
_ => false,
279286
}
280287
}
281288

0 commit comments

Comments
 (0)