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 ;
3
- use rustc_hir:: { ExprKind , ImplItem , ImplItemKind , ItemKind , Node , UnOp } ;
7
+ use rustc_hir:: { def :: Res , Expr , ExprKind , ImplItem , ImplItemKind , ItemKind , LangItem , Node , PatKind , UnOp } ;
4
8
use rustc_hir_analysis:: hir_ty_to_ty;
5
9
use rustc_lint:: { LateContext , LateLintPass } ;
6
10
use rustc_middle:: ty:: EarlyBinder ;
7
11
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
8
12
use rustc_span:: { sym, symbol} ;
13
+ use std:: borrow:: Cow ;
9
14
10
15
declare_clippy_lint ! {
11
16
/// ### What it does
@@ -46,10 +51,59 @@ declare_clippy_lint! {
46
51
correctness,
47
52
"manual implementation of `Clone` on a `Copy` type"
48
53
}
49
- 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 ] ) ;
50
104
51
105
impl LateLintPass < ' _ > for IncorrectImpls {
52
- #[ expect( clippy:: needless_return ) ]
106
+ #[ expect( clippy:: too_many_lines ) ]
53
107
fn check_impl_item ( & mut self , cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > ) {
54
108
let node = get_parent_node ( cx. tcx , impl_item. hir_id ( ) ) ;
55
109
let Some ( Node :: Item ( item) ) = node else {
@@ -72,10 +126,7 @@ impl LateLintPass<'_> for IncorrectImpls {
72
126
let ExprKind :: Block ( block, ..) = body. value . kind else {
73
127
return ;
74
128
} ;
75
- // Above is duplicated from the `duplicate_manual_partial_ord_impl` branch.
76
- // Remove it while solving conflicts once that PR is merged.
77
129
78
- // Actual implementation; remove this comment once aforementioned PR is merged
79
130
if cx. tcx . is_diagnostic_item ( sym:: Clone , trait_impl_def_id)
80
131
&& let Some ( copy_def_id) = cx. tcx . get_diagnostic_item ( sym:: Copy )
81
132
&& implements_trait (
@@ -120,5 +171,71 @@ impl LateLintPass<'_> for IncorrectImpls {
120
171
return ;
121
172
}
122
173
}
174
+
175
+ if cx. tcx . is_diagnostic_item ( sym:: PartialOrd , trait_impl_def_id)
176
+ && impl_item. ident . name == sym:: partial_cmp
177
+ && let Some ( ord_def_id) = cx
178
+ . tcx
179
+ . diagnostic_items ( trait_impl. def_id . krate )
180
+ . name_to_id
181
+ . get ( & sym:: Ord )
182
+ && implements_trait (
183
+ cx,
184
+ hir_ty_to_ty ( cx. tcx , imp. self_ty ) ,
185
+ * ord_def_id,
186
+ trait_impl. substs ,
187
+ )
188
+ {
189
+ if block. stmts . is_empty ( )
190
+ && let Some ( expr) = block. expr
191
+ && let ExprKind :: Call (
192
+ Expr {
193
+ kind : ExprKind :: Path ( some_path) ,
194
+ hir_id : some_hir_id,
195
+ ..
196
+ } ,
197
+ [ cmp_expr] ,
198
+ ) = expr. kind
199
+ && is_res_lang_ctor ( cx, cx. qpath_res ( some_path, * some_hir_id) , LangItem :: OptionSome )
200
+ && let ExprKind :: MethodCall ( cmp_path, _, [ other_expr] , ..) = cmp_expr. kind
201
+ && cmp_path. ident . name == sym:: cmp
202
+ && let Res :: Local ( ..) = path_res ( cx, other_expr)
203
+ { } else {
204
+ // If lhs and rhs are not the same type, bail. This makes creating a valid
205
+ // suggestion tons more complex.
206
+ if let Some ( lhs) = trait_impl. substs . get ( 0 )
207
+ && let Some ( rhs) = trait_impl. substs . get ( 1 )
208
+ && lhs != rhs
209
+ {
210
+ return ;
211
+ }
212
+
213
+ span_lint_and_then (
214
+ cx,
215
+ INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE ,
216
+ item. span ,
217
+ "incorrect implementation of `partial_cmp` on an `Ord` type" ,
218
+ |diag| {
219
+ let ( help, app) = if let Some ( other) = body. params . get ( 1 )
220
+ && let PatKind :: Binding ( _, _, other_ident, ..) = other. pat . kind
221
+ {
222
+ (
223
+ Cow :: Owned ( format ! ( "{{ Some(self.cmp({})) }}" , other_ident. name) ) ,
224
+ Applicability :: Unspecified ,
225
+ )
226
+ } else {
227
+ ( Cow :: Borrowed ( "{ Some(self.cmp(...)) }" ) , Applicability :: HasPlaceholders )
228
+ } ;
229
+
230
+ diag. span_suggestion (
231
+ block. span ,
232
+ "change this to" ,
233
+ help,
234
+ app,
235
+ ) ;
236
+ }
237
+ ) ;
238
+ }
239
+ }
123
240
}
124
241
}
0 commit comments