Skip to content

Commit 630ac0c

Browse files
authored
Accept self.cmp(other).into() as canonical PartialOrd impl (#14573)
`non_canonical_partial_ord_impl` will now recognize two forms of canonical implementations: `Some(self.cmp(other))` and `self.cmp(other).into()`. changelog: [`non_canonical_partial_ord_impl`]: recognize `self.cmp(other).into()` as a canonical implementation of `PartialOrd::partial_cmp()`. Fixes #13640
2 parents 1cfc95c + 0fe3317 commit 630ac0c

4 files changed

+143
-52
lines changed

Diff for: clippy_lints/src/non_canonical_impls.rs

+61-51
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::ty::implements_trait;
3-
use clippy_utils::{is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core};
3+
use clippy_utils::{
4+
is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core,
5+
};
46
use rustc_errors::Applicability;
57
use rustc_hir::def_id::LocalDefId;
68
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
@@ -98,7 +100,7 @@ declare_clippy_lint! {
98100
///
99101
/// impl PartialOrd for A {
100102
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
101-
/// Some(self.cmp(other))
103+
/// Some(self.cmp(other)) // or self.cmp(other).into()
102104
/// }
103105
/// }
104106
/// ```
@@ -185,88 +187,96 @@ impl LateLintPass<'_> for NonCanonicalImpls {
185187

186188
if block.stmts.is_empty()
187189
&& let Some(expr) = block.expr
188-
&& expr_is_cmp(cx, &expr.kind, impl_item, &mut needs_fully_qualified)
190+
&& expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified)
189191
{
192+
return;
190193
}
191194
// Fix #12683, allow [`needless_return`] here
192195
else if block.expr.is_none()
193196
&& let Some(stmt) = block.stmts.first()
194197
&& let rustc_hir::StmtKind::Semi(Expr {
195-
kind: ExprKind::Ret(Some(Expr { kind: ret_kind, .. })),
198+
kind: ExprKind::Ret(Some(ret)),
196199
..
197200
}) = stmt.kind
198-
&& expr_is_cmp(cx, ret_kind, impl_item, &mut needs_fully_qualified)
201+
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
199202
{
200-
} else {
201-
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
202-
// suggestion tons more complex.
203-
if let [lhs, rhs, ..] = trait_impl.args.as_slice()
204-
&& lhs != rhs
205-
{
206-
return;
207-
}
203+
return;
204+
}
205+
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
206+
// suggestion tons more complex.
207+
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
208+
&& lhs != rhs
209+
{
210+
return;
211+
}
208212

209-
span_lint_and_then(
210-
cx,
211-
NON_CANONICAL_PARTIAL_ORD_IMPL,
212-
item.span,
213-
"non-canonical implementation of `partial_cmp` on an `Ord` type",
214-
|diag| {
215-
let [_, other] = body.params else {
216-
return;
217-
};
218-
let Some(std_or_core) = std_or_core(cx) else {
219-
return;
220-
};
213+
span_lint_and_then(
214+
cx,
215+
NON_CANONICAL_PARTIAL_ORD_IMPL,
216+
item.span,
217+
"non-canonical implementation of `partial_cmp` on an `Ord` type",
218+
|diag| {
219+
let [_, other] = body.params else {
220+
return;
221+
};
222+
let Some(std_or_core) = std_or_core(cx) else {
223+
return;
224+
};
221225

222-
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
223-
(Some(other_ident), true) => vec![(
226+
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
227+
(Some(other_ident), true) => vec![(
228+
block.span,
229+
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
230+
)],
231+
(Some(other_ident), false) => {
232+
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
233+
},
234+
(None, true) => vec![
235+
(
224236
block.span,
225-
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
226-
)],
227-
(Some(other_ident), false) => {
228-
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
229-
},
230-
(None, true) => vec![
231-
(
232-
block.span,
233-
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
234-
),
235-
(other.pat.span, "other".to_owned()),
236-
],
237-
(None, false) => vec![
238-
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
239-
(other.pat.span, "other".to_owned()),
240-
],
241-
};
237+
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
238+
),
239+
(other.pat.span, "other".to_owned()),
240+
],
241+
(None, false) => vec![
242+
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
243+
(other.pat.span, "other".to_owned()),
244+
],
245+
};
242246

243-
diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);
244-
},
245-
);
246-
}
247+
diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);
248+
},
249+
);
247250
}
248251
}
249252
}
250253

251254
/// Return true if `expr_kind` is a `cmp` call.
252255
fn expr_is_cmp<'tcx>(
253256
cx: &LateContext<'tcx>,
254-
expr_kind: &'tcx ExprKind<'tcx>,
257+
expr: &'tcx Expr<'tcx>,
255258
impl_item: &ImplItem<'_>,
256259
needs_fully_qualified: &mut bool,
257260
) -> bool {
261+
let impl_item_did = impl_item.owner_id.def_id;
258262
if let ExprKind::Call(
259263
Expr {
260264
kind: ExprKind::Path(some_path),
261265
hir_id: some_hir_id,
262266
..
263267
},
264268
[cmp_expr],
265-
) = expr_kind
269+
) = expr.kind
266270
{
267271
is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
268272
// Fix #11178, allow `Self::cmp(self, ..)` too
269-
&& self_cmp_call(cx, cmp_expr, impl_item.owner_id.def_id, needs_fully_qualified)
273+
&& self_cmp_call(cx, cmp_expr, impl_item_did, needs_fully_qualified)
274+
} else if let ExprKind::MethodCall(_, recv, [], _) = expr.kind {
275+
cx.tcx
276+
.typeck(impl_item_did)
277+
.type_dependent_def_id(expr.hir_id)
278+
.is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into))
279+
&& self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified)
270280
} else {
271281
false
272282
}

Diff for: tests/ui/non_canonical_partial_ord_impl.fixed

+33
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,36 @@ impl PartialOrd for I {
162162
return Some(self.cmp(other));
163163
}
164164
}
165+
166+
// #13640, do not lint
167+
168+
#[derive(Eq, PartialEq)]
169+
struct J(u32);
170+
171+
impl Ord for J {
172+
fn cmp(&self, other: &Self) -> Ordering {
173+
todo!();
174+
}
175+
}
176+
177+
impl PartialOrd for J {
178+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
179+
self.cmp(other).into()
180+
}
181+
}
182+
183+
// #13640, check that a simple `.into()` does not obliterate the lint
184+
185+
#[derive(Eq, PartialEq)]
186+
struct K(u32);
187+
188+
impl Ord for K {
189+
fn cmp(&self, other: &Self) -> Ordering {
190+
todo!();
191+
}
192+
}
193+
194+
impl PartialOrd for K {
195+
//~^ non_canonical_partial_ord_impl
196+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
197+
}

Diff for: tests/ui/non_canonical_partial_ord_impl.rs

+35
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,38 @@ impl PartialOrd for I {
166166
return Some(self.cmp(other));
167167
}
168168
}
169+
170+
// #13640, do not lint
171+
172+
#[derive(Eq, PartialEq)]
173+
struct J(u32);
174+
175+
impl Ord for J {
176+
fn cmp(&self, other: &Self) -> Ordering {
177+
todo!();
178+
}
179+
}
180+
181+
impl PartialOrd for J {
182+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
183+
self.cmp(other).into()
184+
}
185+
}
186+
187+
// #13640, check that a simple `.into()` does not obliterate the lint
188+
189+
#[derive(Eq, PartialEq)]
190+
struct K(u32);
191+
192+
impl Ord for K {
193+
fn cmp(&self, other: &Self) -> Ordering {
194+
todo!();
195+
}
196+
}
197+
198+
impl PartialOrd for K {
199+
//~^ non_canonical_partial_ord_impl
200+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
201+
Ordering::Greater.into()
202+
}
203+
}

Diff for: tests/ui/non_canonical_partial_ord_impl.stderr

+14-1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,18 @@ LL - fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
3131
LL + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
3232
|
3333

34-
error: aborting due to 2 previous errors
34+
error: non-canonical implementation of `partial_cmp` on an `Ord` type
35+
--> tests/ui/non_canonical_partial_ord_impl.rs:198:1
36+
|
37+
LL | / impl PartialOrd for K {
38+
LL | |
39+
LL | | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
40+
| | _____________________________________________________________-
41+
LL | || Ordering::Greater.into()
42+
LL | || }
43+
| ||_____- help: change this to: `{ Some(self.cmp(other)) }`
44+
LL | | }
45+
| |__^
46+
47+
error: aborting due to 3 previous errors
3548

0 commit comments

Comments
 (0)