11use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
2+ use clippy_utils:: paths:: ORD_CMP ;
23use 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 } ;
45use rustc_errors:: Applicability ;
5- use rustc_hir:: def :: Res ;
6+ use rustc_hir:: def_id :: LocalDefId ;
67use rustc_hir:: { Expr , ExprKind , ImplItem , ImplItemKind , ItemKind , LangItem , Node , UnOp } ;
78use rustc_hir_analysis:: hir_ty_to_ty;
89use rustc_lint:: { LateContext , LateLintPass } ;
@@ -60,6 +61,10 @@ declare_clippy_lint! {
6061 /// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently
6162 /// introduce an error upon refactoring.
6263 ///
64+ /// ### Known issues
65+ /// Code that calls the `.into()` method instead will be flagged as incorrect, despite `.into()`
66+ /// wrapping it in `Some`.
67+ ///
6368 /// ### Limitations
6469 /// Will not lint if `Self` and `Rhs` do not have the same type.
6570 ///
@@ -191,6 +196,11 @@ impl LateLintPass<'_> for IncorrectImpls {
191196 trait_impl. substs ,
192197 )
193198 {
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+
194204 if block. stmts . is_empty ( )
195205 && let Some ( expr) = block. expr
196206 && let ExprKind :: Call (
@@ -202,9 +212,8 @@ impl LateLintPass<'_> for IncorrectImpls {
202212 [ cmp_expr] ,
203213 ) = expr. kind
204214 && 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)
208217 { } else {
209218 // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
210219 // suggestion tons more complex.
@@ -221,14 +230,29 @@ impl LateLintPass<'_> for IncorrectImpls {
221230 let [ _, other] = body. params else {
222231 return ;
223232 } ;
233+ let Some ( std_or_core) = std_or_core ( cx) else {
234+ return ;
235+ } ;
224236
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 ! [
229253 ( block. span, "{ Some(self.cmp(other)) }" . to_owned( ) ) ,
230254 ( other. pat. span, "other" . to_owned( ) ) ,
231- ]
255+ ] ,
232256 } ;
233257
234258 diag. multipart_suggestion (
@@ -242,3 +266,31 @@ impl LateLintPass<'_> for IncorrectImpls {
242266 }
243267 }
244268}
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