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+ } ;
26use 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;
49use rustc_lint:: { LateContext , LateLintPass } ;
510use rustc_middle:: ty:: EarlyBinder ;
611use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
7- use rustc_span:: { sym, symbol} ;
12+ use rustc_span:: { sym, symbol:: kw } ;
813
914declare_clippy_lint ! {
1015 /// ### What it does
@@ -45,34 +50,89 @@ declare_clippy_lint! {
4550 correctness,
4651 "manual implementation of `Clone` on a `Copy` type"
4752}
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 ] ) ;
49111
50112impl LateLintPass < ' _ > for IncorrectImpls {
51- #[ expect( clippy:: needless_return ) ]
113+ #[ expect( clippy:: too_many_lines ) ]
52114 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 {
55116 return ;
56117 } ;
57118 let Some ( trait_impl) = cx. tcx . impl_trait_ref ( item. owner_id ) . map ( EarlyBinder :: skip_binder) else {
58119 return ;
59120 } ;
60- let trait_impl_def_id = trait_impl. def_id ;
61121 if cx. tcx . is_automatically_derived ( item. owner_id . to_def_id ( ) ) {
62122 return ;
63123 }
124+ let ItemKind :: Impl ( imp) = item. kind else {
125+ return ;
126+ } ;
64127 let ImplItemKind :: Fn ( _, impl_item_id) = cx. tcx . hir ( ) . impl_item ( impl_item. impl_item_id ( ) ) . kind else {
65128 return ;
66129 } ;
67130 let body = cx. tcx . hir ( ) . body ( impl_item_id) ;
68131 let ExprKind :: Block ( block, ..) = body. value . kind else {
69132 return ;
70133 } ;
71- // Above is duplicated from the `duplicate_manual_partial_ord_impl` branch.
72- // Remove it while solving conflicts once that PR is merged.
73134
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 )
76136 && let Some ( copy_def_id) = cx. tcx . get_diagnostic_item ( sym:: Copy )
77137 && implements_trait (
78138 cx,
@@ -84,9 +144,9 @@ impl LateLintPass<'_> for IncorrectImpls {
84144 if impl_item. ident . name == sym:: clone {
85145 if block. stmts . is_empty ( )
86146 && 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
90150 { } else {
91151 span_lint_and_sugg (
92152 cx,
@@ -108,13 +168,77 @@ impl LateLintPass<'_> for IncorrectImpls {
108168 INCORRECT_CLONE_IMPL_ON_COPY_TYPE ,
109169 impl_item. span ,
110170 "incorrect implementation of `clone_from` on a `Copy` type" ,
111- "remove this " ,
171+ "remove it " ,
112172 String :: new ( ) ,
113173 Applicability :: MaybeIncorrect ,
114174 ) ;
115175
116176 return ;
117177 }
118178 }
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+ }
119243 }
120244}
0 commit comments