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 , 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;
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
- use rustc_span:: { sym, symbol} ;
12
+ use rustc_span:: { sym, symbol:: kw } ;
8
13
9
14
declare_clippy_lint ! {
10
15
/// ### What it does
@@ -45,34 +50,89 @@ declare_clippy_lint! {
45
50
correctness,
46
51
"manual implementation of `Clone` on a `Copy` type"
47
52
}
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 ] ) ;
49
111
50
112
impl LateLintPass < ' _ > for IncorrectImpls {
51
- #[ expect( clippy:: needless_return ) ]
113
+ #[ expect( clippy:: too_many_lines ) ]
52
114
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 {
55
116
return ;
56
117
} ;
57
118
let Some ( trait_impl) = cx. tcx . impl_trait_ref ( item. owner_id ) . map ( EarlyBinder :: skip_binder) else {
58
119
return ;
59
120
} ;
60
- let trait_impl_def_id = trait_impl. def_id ;
61
121
if cx. tcx . is_automatically_derived ( item. owner_id . to_def_id ( ) ) {
62
122
return ;
63
123
}
124
+ let ItemKind :: Impl ( imp) = item. kind else {
125
+ return ;
126
+ } ;
64
127
let ImplItemKind :: Fn ( _, impl_item_id) = cx. tcx . hir ( ) . impl_item ( impl_item. impl_item_id ( ) ) . kind else {
65
128
return ;
66
129
} ;
67
130
let body = cx. tcx . hir ( ) . body ( impl_item_id) ;
68
131
let ExprKind :: Block ( block, ..) = body. value . kind else {
69
132
return ;
70
133
} ;
71
- // Above is duplicated from the `duplicate_manual_partial_ord_impl` branch.
72
- // Remove it while solving conflicts once that PR is merged.
73
134
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 )
76
136
&& let Some ( copy_def_id) = cx. tcx . get_diagnostic_item ( sym:: Copy )
77
137
&& implements_trait (
78
138
cx,
@@ -84,9 +144,9 @@ impl LateLintPass<'_> for IncorrectImpls {
84
144
if impl_item. ident . name == sym:: clone {
85
145
if block. stmts . is_empty ( )
86
146
&& 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
90
150
{ } else {
91
151
span_lint_and_sugg (
92
152
cx,
@@ -108,13 +168,77 @@ impl LateLintPass<'_> for IncorrectImpls {
108
168
INCORRECT_CLONE_IMPL_ON_COPY_TYPE ,
109
169
impl_item. span ,
110
170
"incorrect implementation of `clone_from` on a `Copy` type" ,
111
- "remove this " ,
171
+ "remove it " ,
112
172
String :: new ( ) ,
113
173
Applicability :: MaybeIncorrect ,
114
174
) ;
115
175
116
176
return ;
117
177
}
118
178
}
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
+ }
119
243
}
120
244
}
0 commit comments