Skip to content

Commit 2a1fd22

Browse files
committed
implement manual_partial_ord_impl
first try at this
1 parent 3a1fc26 commit 2a1fd22

File tree

6 files changed

+220
-0
lines changed

6 files changed

+220
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4921,6 +4921,7 @@ Released 2018-09-13
49214921
[`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back
49224922
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
49234923
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
4924+
[`manual_partial_ord_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_partial_ord_impl
49244925
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
49254926
[`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns
49264927
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
277277
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
278278
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
279279
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
280+
crate::manual_partial_ord_impl::MANUAL_PARTIAL_ORD_IMPL_INFO,
280281
crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO,
281282
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
282283
crate::manual_retain::MANUAL_RETAIN_INFO,

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ mod manual_is_ascii_check;
188188
mod manual_let_else;
189189
mod manual_main_separator_str;
190190
mod manual_non_exhaustive;
191+
mod manual_partial_ord_impl;
191192
mod manual_range_patterns;
192193
mod manual_rem_euclid;
193194
mod manual_retain;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::{
3+
def_path_def_ids, diagnostics::span_lint_and_sugg, get_trait_def_id, match_def_path, path_res, ty::implements_trait,
4+
};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::def::Res;
7+
use rustc_hir::*;
8+
use rustc_hir_analysis::hir_ty_to_ty;
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::{declare_tool_lint, impl_lint_pass};
11+
use rustc_span::def_id::DefId;
12+
use std::cell::OnceCell;
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
/// Checks for manual implementations of both `PartialOrd` and `Ord` when only `Ord` is
17+
/// necessary.
18+
///
19+
/// ### Why is this bad?
20+
/// If both `PartialOrd` and `Ord` are implemented, `PartialOrd` will wrap the returned value of
21+
/// `Ord::cmp` in `Some`. Not doing this may silently introduce an error.
22+
///
23+
/// ### Example
24+
/// ```rust
25+
/// #[derive(Eq, PartialEq)]
26+
/// struct A(u32);
27+
///
28+
/// impl Ord for A {
29+
/// fn cmp(&self, other: &Self) -> Ordering {
30+
/// todo!();
31+
/// }
32+
/// }
33+
///
34+
/// impl PartialOrd for A {
35+
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
36+
/// todo!();
37+
/// }
38+
/// }
39+
/// ```
40+
/// Use instead:
41+
/// ```rust
42+
/// #[derive(Eq, PartialEq)]
43+
/// struct A(u32);
44+
///
45+
/// impl Ord for A {
46+
/// fn cmp(&self, other: &Self) -> Ordering {
47+
/// todo!();
48+
/// }
49+
/// }
50+
///
51+
/// impl PartialOrd for A {
52+
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
53+
/// Some(self.cmp(other))
54+
/// }
55+
/// }
56+
/// ```
57+
#[clippy::version = "1.71.0"]
58+
pub MANUAL_PARTIAL_ORD_IMPL,
59+
nursery,
60+
"default lint description"
61+
}
62+
impl_lint_pass!(ManualPartialOrdImpl => [MANUAL_PARTIAL_ORD_IMPL]);
63+
64+
#[derive(Clone)]
65+
pub struct ManualPartialOrdImpl {
66+
pub ord_def_id: OnceCell<DefId>,
67+
}
68+
69+
impl LateLintPass<'_> for ManualPartialOrdImpl {
70+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
71+
if_chain! {
72+
if let ItemKind::Impl(imp) = &item.kind;
73+
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id);
74+
if cx.tcx.is_diagnostic_item(sym!(PartialOrd), impl_trait_ref.skip_binder().def_id);
75+
then {
76+
lint_impl_body(self, cx, imp, item);
77+
}
78+
}
79+
}
80+
}
81+
82+
fn lint_impl_body(conf: &mut ManualPartialOrdImpl, cx: &LateContext<'_>, imp: &Impl<'_>, item: &Item<'_>) {
83+
for imp_item in imp.items {
84+
if_chain! {
85+
if imp_item.ident.name == sym!(partial_cmp);
86+
if let ImplItemKind::Fn(_, id) = cx.tcx.hir().impl_item(imp_item.id).kind;
87+
then {
88+
let body = cx.tcx.hir().body(id);
89+
let ord_def_id = conf.ord_def_id.get_or_init(|| get_trait_def_id(cx, &["core", "cmp", "Ord"]).unwrap());
90+
if let ExprKind::Block(block, ..)
91+
= body.value.kind && implements_trait(cx, hir_ty_to_ty(cx.tcx, imp.self_ty), *ord_def_id, &[])
92+
{
93+
if_chain! {
94+
if block.stmts.is_empty();
95+
if let Some(expr) = block.expr;
96+
if let ExprKind::Call(Expr { kind: ExprKind::Path(path), ..}, [cmp_expr]) = expr.kind;
97+
if let QPath::Resolved(_, some_path) = path;
98+
if let Some(some_seg_one) = some_path.segments.get(0);
99+
if some_seg_one.ident.name == sym!(Some);
100+
if let ExprKind::MethodCall(cmp_path, _, [other_expr], ..) = cmp_expr.kind;
101+
if cmp_path.ident.name == sym!(cmp);
102+
if let Res::Local(..) = path_res(cx, other_expr);
103+
then {}
104+
else {
105+
span_lint_and_then(
106+
cx,
107+
MANUAL_PARTIAL_ORD_IMPL,
108+
item.span,
109+
"manual implementation of `PartialOrd` when `Ord` is already implemented",
110+
|diag| {
111+
if let Some(param) = body.params.get(1)
112+
&& let PatKind::Binding(_, _, param_ident, ..) = param.pat.kind
113+
{
114+
diag.span_suggestion(
115+
block.span,
116+
"change this to",
117+
format!("{{ Some(self.cmp({})) }}",
118+
param_ident.name),
119+
Applicability::MaybeIncorrect
120+
);
121+
} else {
122+
diag.help("return the value of `self.cmp` wrapped in `Some` instead");
123+
};
124+
}
125+
);
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}
132+
}

tests/ui/manual_partial_ord_impl.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#![allow(unused)]
2+
#![warn(clippy::manual_partial_ord_impl)]
3+
#![no_main]
4+
5+
use std::cmp::Ordering;
6+
7+
// lint
8+
9+
#[derive(Eq, PartialEq)]
10+
struct A(u32);
11+
12+
impl Ord for A {
13+
fn cmp(&self, other: &Self) -> Ordering {
14+
todo!();
15+
}
16+
}
17+
18+
impl PartialOrd for A {
19+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20+
todo!();
21+
}
22+
}
23+
24+
// do not lint
25+
26+
#[derive(Eq, PartialEq)]
27+
struct B(u32);
28+
29+
impl Ord for B {
30+
fn cmp(&self, other: &Self) -> Ordering {
31+
todo!();
32+
}
33+
}
34+
35+
impl PartialOrd for B {
36+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
37+
Some(self.cmp(other))
38+
}
39+
}
40+
41+
// lint, but we cannot give a suggestion since &Self is not named
42+
43+
#[derive(Eq, PartialEq)]
44+
struct C(u32);
45+
46+
impl Ord for C {
47+
fn cmp(&self, other: &Self) -> Ordering {
48+
todo!();
49+
}
50+
}
51+
52+
impl PartialOrd for C {
53+
fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
54+
todo!();
55+
}
56+
}
57+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: manual implementation of `PartialOrd` when `Ord` is already implemented
2+
--> $DIR/manual_partial_ord_impl.rs:18:1
3+
|
4+
LL | / impl PartialOrd for A {
5+
LL | | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
6+
| | _____________________________________________________________-
7+
LL | || todo!();
8+
LL | || }
9+
| ||_____- help: change this to: `{ Some(self.cmp(other)) }`
10+
LL | | }
11+
| |__^
12+
|
13+
= note: `-D clippy::manual-partial-ord-impl` implied by `-D warnings`
14+
15+
error: manual implementation of `PartialOrd` when `Ord` is already implemented
16+
--> $DIR/manual_partial_ord_impl.rs:52:1
17+
|
18+
LL | / impl PartialOrd for C {
19+
LL | | fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
20+
LL | | todo!();
21+
LL | | }
22+
LL | | }
23+
| |_^
24+
|
25+
= help: return the value of `self.cmp` wrapped in `Some` instead
26+
27+
error: aborting due to 2 previous errors
28+

0 commit comments

Comments
 (0)