Skip to content

Commit e5db198

Browse files
committed
Auto merge of #10788 - Centri3:duplicate_manual_partial_ord_impl, r=xFrednet,blyxyas
New lint [`manual_partial_ord_and_ord_impl`] Lints when both `PartialOrd` and `Ord` are manually implemented yet the body of `PartialOrd::partial_cmp` isn't `Some(self.cmp(<other>))`. This is in `correctness` currently but I'm ok with elsewhere. Closes #10744 --- changelog: new lint [`needless_partial_ord_impl`] [#10788](#10788)
2 parents 3a1fc26 + a5dfb68 commit e5db198

14 files changed

+455
-59
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4834,6 +4834,7 @@ Released 2018-09-13
48344834
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
48354835
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
48364836
[`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type
4837+
[`incorrect_partial_ord_impl_on_ord_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_partial_ord_impl_on_ord_type
48374838
[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
48384839
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
48394840
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
207207
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
208208
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
209209
crate::incorrect_impls::INCORRECT_CLONE_IMPL_ON_COPY_TYPE_INFO,
210+
crate::incorrect_impls::INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE_INFO,
210211
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
211212
crate::indexing_slicing::INDEXING_SLICING_INFO,
212213
crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO,

clippy_lints/src/incorrect_impls.rs

Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
use clippy_utils::{diagnostics::span_lint_and_sugg, get_parent_node, last_path_segment, ty::implements_trait};
1+
use clippy_utils::{
2+
diagnostics::{span_lint_and_sugg, span_lint_and_then},
3+
get_parent_node, is_res_lang_ctor, last_path_segment, path_res,
4+
ty::implements_trait,
5+
};
26
use rustc_errors::Applicability;
3-
use rustc_hir::{ExprKind, ImplItem, ImplItemKind, Node, UnOp};
7+
use rustc_hir::{def::Res, Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp};
8+
use rustc_hir_analysis::hir_ty_to_ty;
49
use rustc_lint::{LateContext, LateLintPass};
510
use rustc_middle::ty::EarlyBinder;
611
use rustc_session::{declare_lint_pass, declare_tool_lint};
7-
use rustc_span::{sym, symbol};
12+
use rustc_span::{sym, symbol::kw};
813

914
declare_clippy_lint! {
1015
/// ### What it does
@@ -45,34 +50,89 @@ declare_clippy_lint! {
4550
correctness,
4651
"manual implementation of `Clone` on a `Copy` type"
4752
}
48-
declare_lint_pass!(IncorrectImpls => [INCORRECT_CLONE_IMPL_ON_COPY_TYPE]);
53+
declare_clippy_lint! {
54+
/// ### What it does
55+
/// Checks for manual implementations of both `PartialOrd` and `Ord` when only `Ord` is
56+
/// necessary.
57+
///
58+
/// ### Why is this bad?
59+
/// If both `PartialOrd` and `Ord` are implemented, they must agree. This is commonly done by
60+
/// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently
61+
/// introduce an error upon refactoring.
62+
///
63+
/// ### Limitations
64+
/// Will not lint if `Self` and `Rhs` do not have the same type.
65+
///
66+
/// ### Example
67+
/// ```rust
68+
/// # use std::cmp::Ordering;
69+
/// #[derive(Eq, PartialEq)]
70+
/// struct A(u32);
71+
///
72+
/// impl Ord for A {
73+
/// fn cmp(&self, other: &Self) -> Ordering {
74+
/// // ...
75+
/// # todo!();
76+
/// }
77+
/// }
78+
///
79+
/// impl PartialOrd for A {
80+
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
81+
/// // ...
82+
/// # todo!();
83+
/// }
84+
/// }
85+
/// ```
86+
/// Use instead:
87+
/// ```rust
88+
/// # use std::cmp::Ordering;
89+
/// #[derive(Eq, PartialEq)]
90+
/// struct A(u32);
91+
///
92+
/// impl Ord for A {
93+
/// fn cmp(&self, other: &Self) -> Ordering {
94+
/// // ...
95+
/// # todo!();
96+
/// }
97+
/// }
98+
///
99+
/// impl PartialOrd for A {
100+
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
101+
/// Some(self.cmp(other))
102+
/// }
103+
/// }
104+
/// ```
105+
#[clippy::version = "1.72.0"]
106+
pub INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE,
107+
correctness,
108+
"manual implementation of `PartialOrd` when `Ord` is already implemented"
109+
}
110+
declare_lint_pass!(IncorrectImpls => [INCORRECT_CLONE_IMPL_ON_COPY_TYPE, INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE]);
49111

50112
impl LateLintPass<'_> for IncorrectImpls {
51-
#[expect(clippy::needless_return)]
113+
#[expect(clippy::too_many_lines)]
52114
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
53-
let node = get_parent_node(cx.tcx, impl_item.hir_id());
54-
let Some(Node::Item(item)) = node else {
115+
let Some(Node::Item(item)) = get_parent_node(cx.tcx, impl_item.hir_id()) else {
55116
return;
56117
};
57118
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
58119
return;
59120
};
60-
let trait_impl_def_id = trait_impl.def_id;
61121
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
62122
return;
63123
}
124+
let ItemKind::Impl(imp) = item.kind else {
125+
return;
126+
};
64127
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
65128
return;
66129
};
67130
let body = cx.tcx.hir().body(impl_item_id);
68131
let ExprKind::Block(block, ..) = body.value.kind else {
69132
return;
70133
};
71-
// Above is duplicated from the `duplicate_manual_partial_ord_impl` branch.
72-
// Remove it while solving conflicts once that PR is merged.
73134

74-
// Actual implementation; remove this comment once aforementioned PR is merged
75-
if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl_def_id)
135+
if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id)
76136
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
77137
&& implements_trait(
78138
cx,
@@ -84,9 +144,9 @@ impl LateLintPass<'_> for IncorrectImpls {
84144
if impl_item.ident.name == sym::clone {
85145
if block.stmts.is_empty()
86146
&& let Some(expr) = block.expr
87-
&& let ExprKind::Unary(UnOp::Deref, inner) = expr.kind
88-
&& let ExprKind::Path(qpath) = inner.kind
89-
&& last_path_segment(&qpath).ident.name == symbol::kw::SelfLower
147+
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
148+
&& let ExprKind::Path(qpath) = deref.kind
149+
&& last_path_segment(&qpath).ident.name == kw::SelfLower
90150
{} else {
91151
span_lint_and_sugg(
92152
cx,
@@ -108,13 +168,77 @@ impl LateLintPass<'_> for IncorrectImpls {
108168
INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
109169
impl_item.span,
110170
"incorrect implementation of `clone_from` on a `Copy` type",
111-
"remove this",
171+
"remove it",
112172
String::new(),
113173
Applicability::MaybeIncorrect,
114174
);
115175

116176
return;
117177
}
118178
}
179+
180+
if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id)
181+
&& impl_item.ident.name == sym::partial_cmp
182+
&& let Some(ord_def_id) = cx
183+
.tcx
184+
.diagnostic_items(trait_impl.def_id.krate)
185+
.name_to_id
186+
.get(&sym::Ord)
187+
&& implements_trait(
188+
cx,
189+
hir_ty_to_ty(cx.tcx, imp.self_ty),
190+
*ord_def_id,
191+
trait_impl.substs,
192+
)
193+
{
194+
if block.stmts.is_empty()
195+
&& let Some(expr) = block.expr
196+
&& let ExprKind::Call(
197+
Expr {
198+
kind: ExprKind::Path(some_path),
199+
hir_id: some_hir_id,
200+
..
201+
},
202+
[cmp_expr],
203+
) = expr.kind
204+
&& is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
205+
&& let ExprKind::MethodCall(cmp_path, _, [other_expr], ..) = cmp_expr.kind
206+
&& cmp_path.ident.name == sym::cmp
207+
&& let Res::Local(..) = path_res(cx, other_expr)
208+
{} else {
209+
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
210+
// suggestion tons more complex.
211+
if let [lhs, rhs, ..] = trait_impl.substs.as_slice() && lhs != rhs {
212+
return;
213+
}
214+
215+
span_lint_and_then(
216+
cx,
217+
INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE,
218+
item.span,
219+
"incorrect implementation of `partial_cmp` on an `Ord` type",
220+
|diag| {
221+
let [_, other] = body.params else {
222+
return;
223+
};
224+
225+
let suggs = if let Some(other_ident) = other.pat.simple_ident() {
226+
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
227+
} else {
228+
vec![
229+
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
230+
(other.pat.span, "other".to_owned()),
231+
]
232+
};
233+
234+
diag.multipart_suggestion(
235+
"change this to",
236+
suggs,
237+
Applicability::Unspecified,
238+
);
239+
}
240+
);
241+
}
242+
}
119243
}
120244
}

tests/ui/bool_comparison.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#![allow(clippy::needless_if)]
44
#![warn(clippy::bool_comparison)]
5+
#![allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
56

67
fn main() {
78
let x = true;

tests/ui/bool_comparison.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#![allow(clippy::needless_if)]
44
#![warn(clippy::bool_comparison)]
5+
#![allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
56

67
fn main() {
78
let x = true;

tests/ui/bool_comparison.stderr

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,133 @@
11
error: equality checks against true are unnecessary
2-
--> $DIR/bool_comparison.rs:8:8
2+
--> $DIR/bool_comparison.rs:9:8
33
|
44
LL | if x == true {
55
| ^^^^^^^^^ help: try simplifying it as shown: `x`
66
|
77
= note: `-D clippy::bool-comparison` implied by `-D warnings`
88

99
error: equality checks against false can be replaced by a negation
10-
--> $DIR/bool_comparison.rs:13:8
10+
--> $DIR/bool_comparison.rs:14:8
1111
|
1212
LL | if x == false {
1313
| ^^^^^^^^^^ help: try simplifying it as shown: `!x`
1414

1515
error: equality checks against true are unnecessary
16-
--> $DIR/bool_comparison.rs:18:8
16+
--> $DIR/bool_comparison.rs:19:8
1717
|
1818
LL | if true == x {
1919
| ^^^^^^^^^ help: try simplifying it as shown: `x`
2020

2121
error: equality checks against false can be replaced by a negation
22-
--> $DIR/bool_comparison.rs:23:8
22+
--> $DIR/bool_comparison.rs:24:8
2323
|
2424
LL | if false == x {
2525
| ^^^^^^^^^^ help: try simplifying it as shown: `!x`
2626

2727
error: inequality checks against true can be replaced by a negation
28-
--> $DIR/bool_comparison.rs:28:8
28+
--> $DIR/bool_comparison.rs:29:8
2929
|
3030
LL | if x != true {
3131
| ^^^^^^^^^ help: try simplifying it as shown: `!x`
3232

3333
error: inequality checks against false are unnecessary
34-
--> $DIR/bool_comparison.rs:33:8
34+
--> $DIR/bool_comparison.rs:34:8
3535
|
3636
LL | if x != false {
3737
| ^^^^^^^^^^ help: try simplifying it as shown: `x`
3838

3939
error: inequality checks against true can be replaced by a negation
40-
--> $DIR/bool_comparison.rs:38:8
40+
--> $DIR/bool_comparison.rs:39:8
4141
|
4242
LL | if true != x {
4343
| ^^^^^^^^^ help: try simplifying it as shown: `!x`
4444

4545
error: inequality checks against false are unnecessary
46-
--> $DIR/bool_comparison.rs:43:8
46+
--> $DIR/bool_comparison.rs:44:8
4747
|
4848
LL | if false != x {
4949
| ^^^^^^^^^^ help: try simplifying it as shown: `x`
5050

5151
error: less than comparison against true can be replaced by a negation
52-
--> $DIR/bool_comparison.rs:48:8
52+
--> $DIR/bool_comparison.rs:49:8
5353
|
5454
LL | if x < true {
5555
| ^^^^^^^^ help: try simplifying it as shown: `!x`
5656

5757
error: greater than checks against false are unnecessary
58-
--> $DIR/bool_comparison.rs:53:8
58+
--> $DIR/bool_comparison.rs:54:8
5959
|
6060
LL | if false < x {
6161
| ^^^^^^^^^ help: try simplifying it as shown: `x`
6262

6363
error: greater than checks against false are unnecessary
64-
--> $DIR/bool_comparison.rs:58:8
64+
--> $DIR/bool_comparison.rs:59:8
6565
|
6666
LL | if x > false {
6767
| ^^^^^^^^^ help: try simplifying it as shown: `x`
6868

6969
error: less than comparison against true can be replaced by a negation
70-
--> $DIR/bool_comparison.rs:63:8
70+
--> $DIR/bool_comparison.rs:64:8
7171
|
7272
LL | if true > x {
7373
| ^^^^^^^^ help: try simplifying it as shown: `!x`
7474

7575
error: order comparisons between booleans can be simplified
76-
--> $DIR/bool_comparison.rs:69:8
76+
--> $DIR/bool_comparison.rs:70:8
7777
|
7878
LL | if x < y {
7979
| ^^^^^ help: try simplifying it as shown: `!x & y`
8080

8181
error: order comparisons between booleans can be simplified
82-
--> $DIR/bool_comparison.rs:74:8
82+
--> $DIR/bool_comparison.rs:75:8
8383
|
8484
LL | if x > y {
8585
| ^^^^^ help: try simplifying it as shown: `x & !y`
8686

8787
error: this comparison might be written more concisely
88-
--> $DIR/bool_comparison.rs:122:8
88+
--> $DIR/bool_comparison.rs:123:8
8989
|
9090
LL | if a == !b {};
9191
| ^^^^^^^ help: try simplifying it as shown: `a != b`
9292

9393
error: this comparison might be written more concisely
94-
--> $DIR/bool_comparison.rs:123:8
94+
--> $DIR/bool_comparison.rs:124:8
9595
|
9696
LL | if !a == b {};
9797
| ^^^^^^^ help: try simplifying it as shown: `a != b`
9898

9999
error: this comparison might be written more concisely
100-
--> $DIR/bool_comparison.rs:127:8
100+
--> $DIR/bool_comparison.rs:128:8
101101
|
102102
LL | if b == !a {};
103103
| ^^^^^^^ help: try simplifying it as shown: `b != a`
104104

105105
error: this comparison might be written more concisely
106-
--> $DIR/bool_comparison.rs:128:8
106+
--> $DIR/bool_comparison.rs:129:8
107107
|
108108
LL | if !b == a {};
109109
| ^^^^^^^ help: try simplifying it as shown: `b != a`
110110

111111
error: equality checks against false can be replaced by a negation
112-
--> $DIR/bool_comparison.rs:152:8
112+
--> $DIR/bool_comparison.rs:153:8
113113
|
114114
LL | if false == m!(func) {}
115115
| ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)`
116116

117117
error: equality checks against false can be replaced by a negation
118-
--> $DIR/bool_comparison.rs:153:8
118+
--> $DIR/bool_comparison.rs:154:8
119119
|
120120
LL | if m!(func) == false {}
121121
| ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)`
122122

123123
error: equality checks against true are unnecessary
124-
--> $DIR/bool_comparison.rs:154:8
124+
--> $DIR/bool_comparison.rs:155:8
125125
|
126126
LL | if true == m!(func) {}
127127
| ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)`
128128

129129
error: equality checks against true are unnecessary
130-
--> $DIR/bool_comparison.rs:155:8
130+
--> $DIR/bool_comparison.rs:156:8
131131
|
132132
LL | if m!(func) == true {}
133133
| ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)`

0 commit comments

Comments
 (0)