1
1
use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
2
+ use clippy_utils:: paths:: ORD_CMP ;
2
3
use clippy_utils:: ty:: implements_trait;
3
- use clippy_utils:: { get_parent_node, is_res_lang_ctor, last_path_segment, path_res} ;
4
+ use clippy_utils:: { get_parent_node, is_res_lang_ctor, last_path_segment, match_def_path , path_res, std_or_core } ;
4
5
use rustc_errors:: Applicability ;
5
- use rustc_hir:: def :: Res ;
6
+ use rustc_hir:: def_id :: LocalDefId ;
6
7
use rustc_hir:: { Expr , ExprKind , ImplItem , ImplItemKind , ItemKind , LangItem , Node , UnOp } ;
7
8
use rustc_hir_analysis:: hir_ty_to_ty;
8
9
use rustc_lint:: { LateContext , LateLintPass } ;
@@ -60,6 +61,10 @@ declare_clippy_lint! {
60
61
/// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently
61
62
/// introduce an error upon refactoring.
62
63
///
64
+ /// ### Known issues
65
+ /// Code that calls the `.into()` method instead will be flagged as incorrect, despite `.into()`
66
+ /// wrapping it in `Some`.
67
+ ///
63
68
/// ### Limitations
64
69
/// Will not lint if `Self` and `Rhs` do not have the same type.
65
70
///
@@ -191,6 +196,11 @@ impl LateLintPass<'_> for IncorrectImpls {
191
196
trait_impl. substs ,
192
197
)
193
198
{
199
+ // If the `cmp` call likely needs to be fully qualified in the suggestion
200
+ // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
201
+ // access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
202
+ let mut needs_fully_qualified = false ;
203
+
194
204
if block. stmts . is_empty ( )
195
205
&& let Some ( expr) = block. expr
196
206
&& let ExprKind :: Call (
@@ -202,9 +212,8 @@ impl LateLintPass<'_> for IncorrectImpls {
202
212
[ cmp_expr] ,
203
213
) = expr. kind
204
214
&& 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)
215
+ // Fix #11178, allow `Self::cmp(self, ..)` too
216
+ && self_cmp_call ( cx, cmp_expr, impl_item. owner_id . def_id , & mut needs_fully_qualified)
208
217
{ } else {
209
218
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
210
219
// suggestion tons more complex.
@@ -221,14 +230,29 @@ impl LateLintPass<'_> for IncorrectImpls {
221
230
let [ _, other] = body. params else {
222
231
return ;
223
232
} ;
233
+ let Some ( std_or_core) = std_or_core ( cx) else {
234
+ return ;
235
+ } ;
224
236
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 ! [
237
+ let suggs = match ( other. pat . simple_ident ( ) , needs_fully_qualified) {
238
+ ( Some ( other_ident) , true ) => vec ! [ (
239
+ block. span,
240
+ format!( "{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}" , other_ident. name) ,
241
+ ) ] ,
242
+ ( Some ( other_ident) , false ) => {
243
+ vec ! [ ( block. span, format!( "{{ Some(self.cmp({})) }}" , other_ident. name) ) ]
244
+ } ,
245
+ ( None , true ) => vec ! [
246
+ (
247
+ block. span,
248
+ format!( "{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}" ) ,
249
+ ) ,
250
+ ( other. pat. span, "other" . to_owned( ) ) ,
251
+ ] ,
252
+ ( None , false ) => vec ! [
229
253
( block. span, "{ Some(self.cmp(other)) }" . to_owned( ) ) ,
230
254
( other. pat. span, "other" . to_owned( ) ) ,
231
- ]
255
+ ] ,
232
256
} ;
233
257
234
258
diag. multipart_suggestion (
@@ -242,3 +266,31 @@ impl LateLintPass<'_> for IncorrectImpls {
242
266
}
243
267
}
244
268
}
269
+
270
+ /// Returns whether this is any of `self.cmp(..)`, `Self::cmp(self, ..)` or `Ord::cmp(self, ..)`.
271
+ fn self_cmp_call < ' tcx > (
272
+ cx : & LateContext < ' tcx > ,
273
+ cmp_expr : & ' tcx Expr < ' tcx > ,
274
+ def_id : LocalDefId ,
275
+ needs_fully_qualified : & mut bool ,
276
+ ) -> bool {
277
+ match cmp_expr. kind {
278
+ ExprKind :: Call ( path, [ _self, _other] ) => path_res ( cx, path)
279
+ . opt_def_id ( )
280
+ . is_some_and ( |def_id| match_def_path ( cx, def_id, & ORD_CMP ) ) ,
281
+ ExprKind :: MethodCall ( _, _, [ _other] , ..) => {
282
+ // We can set this to true here no matter what as if it's a `MethodCall` and goes to the
283
+ // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp`
284
+ * needs_fully_qualified = true ;
285
+
286
+ // It's a bit annoying but `typeck_results` only gives us the CURRENT body, which we
287
+ // have none, not of any `LocalDefId` we want, so we must call the query itself to avoid
288
+ // an immediate ICE
289
+ cx. tcx
290
+ . typeck ( def_id)
291
+ . type_dependent_def_id ( cmp_expr. hir_id )
292
+ . is_some_and ( |def_id| match_def_path ( cx, def_id, & ORD_CMP ) )
293
+ } ,
294
+ _ => false ,
295
+ }
296
+ }
0 commit comments