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
+ } ;
2
6
use rustc_errors:: Applicability ;
7
+ use rustc_hir:: { def:: Res , Expr , ExprKind , ImplItem , ImplItemKind , ItemKind , LangItem , Node , PatKind , UnOp } ;
3
8
use rustc_hir:: { ExprKind , ImplItem , ImplItemKind , Node , UnOp } ;
4
9
use rustc_lint:: { LateContext , LateLintPass } ;
5
10
use rustc_middle:: ty:: EarlyBinder ;
6
11
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
7
12
use rustc_span:: { sym, symbol} ;
13
+ use std:: borrow:: Cow ;
8
14
9
15
declare_clippy_lint ! {
10
16
/// ### What it does
@@ -45,10 +51,59 @@ declare_clippy_lint! {
45
51
correctness,
46
52
"manual implementation of `Clone` on a `Copy` type"
47
53
}
48
- declare_lint_pass ! ( IncorrectImpls => [ INCORRECT_CLONE_IMPL_ON_COPY_TYPE ] ) ;
54
+ declare_clippy_lint ! {
55
+ /// ### What it does
56
+ /// Checks for manual implementations of both `PartialOrd` and `Ord` when only `Ord` is
57
+ /// necessary.
58
+ ///
59
+ /// ### Why is this bad?
60
+ /// If both `PartialOrd` and `Ord` are implemented, they must agree. This is commonly done by
61
+ /// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently
62
+ /// introduce an error upon refactoring.
63
+ ///
64
+ /// ### Example
65
+ /// ```rust,ignore
66
+ /// #[derive(Eq, PartialEq)]
67
+ /// struct A(u32);
68
+ ///
69
+ /// impl Ord for A {
70
+ /// fn cmp(&self, other: &Self) -> Ordering {
71
+ /// todo!();
72
+ /// }
73
+ /// }
74
+ ///
75
+ /// impl PartialOrd for A {
76
+ /// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
77
+ /// todo!();
78
+ /// }
79
+ /// }
80
+ /// ```
81
+ /// Use instead:
82
+ /// ```rust,ignore
83
+ /// #[derive(Eq, PartialEq)]
84
+ /// struct A(u32);
85
+ ///
86
+ /// impl Ord for A {
87
+ /// fn cmp(&self, other: &Self) -> Ordering {
88
+ /// todo!();
89
+ /// }
90
+ /// }
91
+ ///
92
+ /// impl PartialOrd for A {
93
+ /// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94
+ /// Some(self.cmp(other))
95
+ /// }
96
+ /// }
97
+ /// ```
98
+ #[ clippy:: version = "1.72.0" ]
99
+ pub INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE ,
100
+ correctness,
101
+ "manual implementation of `PartialOrd` when `Ord` is already implemented"
102
+ }
103
+ declare_lint_pass ! ( IncorrectImpls => [ INCORRECT_CLONE_IMPL_ON_COPY_TYPE , INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE ] ) ;
49
104
50
105
impl LateLintPass < ' _ > for IncorrectImpls {
51
- #[ expect( clippy:: needless_return ) ]
106
+ #[ expect( clippy:: too_many_lines ) ]
52
107
fn check_impl_item ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
53
108
let node = get_parent_node ( cx. tcx , impl_item. hir_id ( ) ) ;
54
109
let Some ( Node :: Item ( item) ) = node else {
@@ -68,10 +123,7 @@ impl LateLintPass<'_> for IncorrectImpls {
68
123
let ExprKind :: Block ( block, ..) = body. value . kind else {
69
124
return ;
70
125
} ;
71
- // Above is duplicated from the `duplicate_manual_partial_ord_impl` branch.
72
- // Remove it while solving conflicts once that PR is merged.
73
126
74
- // Actual implementation; remove this comment once aforementioned PR is merged
75
127
if cx. tcx . is_diagnostic_item ( sym:: Clone , trait_impl_def_id)
76
128
&& let Some ( copy_def_id) = cx. tcx . get_diagnostic_item ( sym:: Copy )
77
129
&& implements_trait (
@@ -116,5 +168,71 @@ impl LateLintPass<'_> for IncorrectImpls {
116
168
return ;
117
169
}
118
170
}
171
+
172
+ if cx. tcx . is_diagnostic_item ( sym:: PartialOrd , trait_impl_def_id)
173
+ && impl_item. ident . name == sym:: partial_cmp
174
+ && let Some ( ord_def_id) = cx
175
+ . tcx
176
+ . diagnostic_items ( trait_impl. def_id . krate )
177
+ . name_to_id
178
+ . get ( & sym:: Ord )
179
+ && implements_trait (
180
+ cx,
181
+ hir_ty_to_ty ( cx. tcx , imp. self_ty ) ,
182
+ * ord_def_id,
183
+ trait_impl. substs ,
184
+ )
185
+ {
186
+ if block. stmts . is_empty ( )
187
+ && let Some ( expr) = block. expr
188
+ && let ExprKind :: Call (
189
+ Expr {
190
+ kind : ExprKind :: Path ( some_path) ,
191
+ hir_id : some_hir_id,
192
+ ..
193
+ } ,
194
+ [ cmp_expr] ,
195
+ ) = expr. kind
196
+ && is_res_lang_ctor ( cx, cx. qpath_res ( some_path, * some_hir_id) , LangItem :: OptionSome )
197
+ && let ExprKind :: MethodCall ( cmp_path, _, [ other_expr] , ..) = cmp_expr. kind
198
+ && cmp_path. ident . name == sym:: cmp
199
+ && let Res :: Local ( ..) = path_res ( cx, other_expr)
200
+ { } else {
201
+ // If lhs and rhs are not the same type, bail. This makes creating a valid
202
+ // suggestion tons more complex.
203
+ if let Some ( lhs) = trait_impl. substs . get ( 0 )
204
+ && let Some ( rhs) = trait_impl. substs . get ( 1 )
205
+ && lhs != rhs
206
+ {
207
+ return ;
208
+ }
209
+
210
+ span_lint_and_then (
211
+ cx,
212
+ INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE ,
213
+ item. span ,
214
+ "incorrect implementation of `partial_cmp` on an `Ord` type" ,
215
+ |diag| {
216
+ let ( help, app) = if let Some ( other) = body. params . get ( 1 )
217
+ && let PatKind :: Binding ( _, _, other_ident, ..) = other. pat . kind
218
+ {
219
+ (
220
+ Cow :: Owned ( format ! ( "{{ Some(self.cmp({})) }}" , other_ident. name) ) ,
221
+ Applicability :: Unspecified ,
222
+ )
223
+ } else {
224
+ ( Cow :: Borrowed ( "{ Some(self.cmp(...)) }" ) , Applicability :: HasPlaceholders )
225
+ } ;
226
+
227
+ diag. span_suggestion (
228
+ block. span ,
229
+ "change this to" ,
230
+ help,
231
+ app,
232
+ ) ;
233
+ }
234
+ ) ;
235
+ }
236
+ }
119
237
}
120
238
}
0 commit comments