@@ -5,9 +5,9 @@ use clippy_utils::{
55} ;
66use rustc_errors:: Applicability ;
77use rustc_hir:: def_id:: LocalDefId ;
8- use rustc_hir:: { Expr , ExprKind , ImplItem , ImplItemKind , LangItem , Node , UnOp } ;
8+ use rustc_hir:: { Block , Body , Expr , ExprKind , ImplItem , ImplItemKind , Item , LangItem , Node , UnOp } ;
99use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10- use rustc_middle:: ty:: EarlyBinder ;
10+ use rustc_middle:: ty:: { EarlyBinder , TraitRef } ;
1111use rustc_session:: declare_lint_pass;
1212use rustc_span:: sym;
1313use rustc_span:: symbol:: kw;
@@ -112,140 +112,146 @@ declare_clippy_lint! {
112112declare_lint_pass ! ( NonCanonicalImpls => [ NON_CANONICAL_CLONE_IMPL , NON_CANONICAL_PARTIAL_ORD_IMPL ] ) ;
113113
114114impl LateLintPass < ' _ > for NonCanonicalImpls {
115- #[ expect( clippy:: too_many_lines) ]
116115 fn check_impl_item < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , impl_item : & ImplItem < ' tcx > ) {
117- let Node :: Item ( item) = cx. tcx . parent_hir_node ( impl_item. hir_id ( ) ) else {
118- return ;
119- } ;
120- let Some ( trait_impl) = cx. tcx . impl_trait_ref ( item. owner_id ) . map ( EarlyBinder :: skip_binder) else {
121- return ;
122- } ;
123- if cx. tcx . is_automatically_derived ( item. owner_id . to_def_id ( ) ) {
124- return ;
116+ if let ImplItemKind :: Fn ( _, impl_item_id) = impl_item. kind
117+ && let Node :: Item ( item) = cx. tcx . parent_hir_node ( impl_item. hir_id ( ) )
118+ && let Some ( trait_impl) = cx. tcx . impl_trait_ref ( item. owner_id ) . map ( EarlyBinder :: skip_binder)
119+ && let trait_name = cx. tcx . get_diagnostic_name ( trait_impl. def_id )
120+ // NOTE: check this early to avoid expensive checks that come after this one
121+ && matches ! ( trait_name, Some ( sym:: Clone | sym:: PartialOrd ) )
122+ && !cx. tcx . is_automatically_derived ( item. owner_id . to_def_id ( ) )
123+ && let body = cx. tcx . hir_body ( impl_item_id)
124+ && let ExprKind :: Block ( block, ..) = body. value . kind
125+ && !block. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
126+ && !is_from_proc_macro ( cx, impl_item)
127+ {
128+ if trait_name == Some ( sym:: Clone )
129+ && let Some ( copy_def_id) = cx. tcx . get_diagnostic_item ( sym:: Copy )
130+ && implements_trait ( cx, trait_impl. self_ty ( ) , copy_def_id, & [ ] )
131+ {
132+ check_clone_on_copy ( cx, impl_item, block) ;
133+ } else if trait_name == Some ( sym:: PartialOrd )
134+ && impl_item. ident . name == sym:: partial_cmp
135+ && let Some ( ord_def_id) = cx. tcx . get_diagnostic_item ( sym:: Ord )
136+ && implements_trait ( cx, trait_impl. self_ty ( ) , ord_def_id, & [ ] )
137+ {
138+ check_partial_ord_on_ord ( cx, impl_item, item, & trait_impl, body, block) ;
139+ }
125140 }
126- let ImplItemKind :: Fn ( _, impl_item_id) = cx. tcx . hir_impl_item ( impl_item. impl_item_id ( ) ) . kind else {
127- return ;
128- } ;
129- let body = cx. tcx . hir_body ( impl_item_id) ;
130- let ExprKind :: Block ( block, ..) = body. value . kind else {
131- return ;
132- } ;
133- if block. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) || is_from_proc_macro ( cx, impl_item) {
141+ }
142+ }
143+
144+ fn check_clone_on_copy ( cx : & LateContext < ' _ > , impl_item : & ImplItem < ' _ > , block : & Block < ' _ > ) {
145+ if impl_item. ident . name == sym:: clone {
146+ if block. stmts . is_empty ( )
147+ && let Some ( expr) = block. expr
148+ && let ExprKind :: Unary ( UnOp :: Deref , deref) = expr. kind
149+ && let ExprKind :: Path ( qpath) = deref. kind
150+ && last_path_segment ( & qpath) . ident . name == kw:: SelfLower
151+ {
152+ // this is the canonical implementation, `fn clone(&self) -> Self { *self }`
134153 return ;
135154 }
136155
137- let trait_name = cx. tcx . get_diagnostic_name ( trait_impl. def_id ) ;
138- if trait_name == Some ( sym:: Clone )
139- && let Some ( copy_def_id) = cx. tcx . get_diagnostic_item ( sym:: Copy )
140- && implements_trait ( cx, trait_impl. self_ty ( ) , copy_def_id, & [ ] )
141- {
142- if impl_item. ident . name == sym:: clone {
143- if block. stmts . is_empty ( )
144- && let Some ( expr) = block. expr
145- && let ExprKind :: Unary ( UnOp :: Deref , deref) = expr. kind
146- && let ExprKind :: Path ( qpath) = deref. kind
147- && last_path_segment ( & qpath) . ident . name == kw:: SelfLower
148- {
149- } else {
150- span_lint_and_sugg (
151- cx,
152- NON_CANONICAL_CLONE_IMPL ,
153- block. span ,
154- "non-canonical implementation of `clone` on a `Copy` type" ,
155- "change this to" ,
156- "{ *self }" . to_owned ( ) ,
157- Applicability :: MaybeIncorrect ,
158- ) ;
156+ span_lint_and_sugg (
157+ cx,
158+ NON_CANONICAL_CLONE_IMPL ,
159+ block. span ,
160+ "non-canonical implementation of `clone` on a `Copy` type" ,
161+ "change this to" ,
162+ "{ *self }" . to_owned ( ) ,
163+ Applicability :: MaybeIncorrect ,
164+ ) ;
165+ }
159166
160- return ;
161- }
162- }
167+ if impl_item. ident . name == sym:: clone_from {
168+ span_lint_and_sugg (
169+ cx,
170+ NON_CANONICAL_CLONE_IMPL ,
171+ impl_item. span ,
172+ "unnecessary implementation of `clone_from` on a `Copy` type" ,
173+ "remove it" ,
174+ String :: new ( ) ,
175+ Applicability :: MaybeIncorrect ,
176+ ) ;
177+ }
178+ }
163179
164- if impl_item. ident . name == sym:: clone_from {
165- span_lint_and_sugg (
166- cx,
167- NON_CANONICAL_CLONE_IMPL ,
168- impl_item. span ,
169- "unnecessary implementation of `clone_from` on a `Copy` type" ,
170- "remove it" ,
171- String :: new ( ) ,
172- Applicability :: MaybeIncorrect ,
173- ) ;
174- }
175- } else if trait_name == Some ( sym:: PartialOrd )
176- && impl_item. ident . name == sym:: partial_cmp
177- && let Some ( ord_def_id) = cx. tcx . get_diagnostic_item ( sym:: Ord )
178- && implements_trait ( cx, trait_impl. self_ty ( ) , ord_def_id, & [ ] )
179- {
180- // If the `cmp` call likely needs to be fully qualified in the suggestion
181- // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
182- // access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
183- let mut needs_fully_qualified = false ;
180+ fn check_partial_ord_on_ord < ' tcx > (
181+ cx : & LateContext < ' tcx > ,
182+ impl_item : & ImplItem < ' _ > ,
183+ item : & Item < ' _ > ,
184+ trait_impl : & TraitRef < ' _ > ,
185+ body : & Body < ' _ > ,
186+ block : & Block < ' tcx > ,
187+ ) {
188+ // If the `cmp` call likely needs to be fully qualified in the suggestion
189+ // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
190+ // access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
184191
185- if block. stmts . is_empty ( )
186- && let Some ( expr) = block. expr
187- && expr_is_cmp ( cx, expr, impl_item, & mut needs_fully_qualified)
188- {
189- return ;
190- }
191- // Fix #12683, allow [`needless_return`] here
192- else if block. expr . is_none ( )
193- && let Some ( stmt) = block. stmts . first ( )
194- && let rustc_hir:: StmtKind :: Semi ( Expr {
195- kind : ExprKind :: Ret ( Some ( ret) ) ,
196- ..
197- } ) = stmt. kind
198- && expr_is_cmp ( cx, ret, impl_item, & mut needs_fully_qualified)
199- {
192+ let mut needs_fully_qualified = false ;
193+ if block. stmts . is_empty ( )
194+ && let Some ( expr) = block. expr
195+ && expr_is_cmp ( cx, expr, impl_item, & mut needs_fully_qualified)
196+ {
197+ return ;
198+ }
199+ // Fix #12683, allow [`needless_return`] here
200+ else if block. expr . is_none ( )
201+ && let Some ( stmt) = block. stmts . first ( )
202+ && let rustc_hir:: StmtKind :: Semi ( Expr {
203+ kind : ExprKind :: Ret ( Some ( ret) ) ,
204+ ..
205+ } ) = stmt. kind
206+ && expr_is_cmp ( cx, ret, impl_item, & mut needs_fully_qualified)
207+ {
208+ return ;
209+ }
210+ // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
211+ // suggestion tons more complex.
212+ else if let [ lhs, rhs, ..] = trait_impl. args . as_slice ( )
213+ && lhs != rhs
214+ {
215+ return ;
216+ }
217+
218+ span_lint_and_then (
219+ cx,
220+ NON_CANONICAL_PARTIAL_ORD_IMPL ,
221+ item. span ,
222+ "non-canonical implementation of `partial_cmp` on an `Ord` type" ,
223+ |diag| {
224+ let [ _, other] = body. params else {
200225 return ;
201- }
202- // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
203- // suggestion tons more complex.
204- else if let [ lhs, rhs, ..] = trait_impl. args . as_slice ( )
205- && lhs != rhs
206- {
226+ } ;
227+ let Some ( std_or_core) = std_or_core ( cx) else {
207228 return ;
208- }
209-
210- span_lint_and_then (
211- cx,
212- NON_CANONICAL_PARTIAL_ORD_IMPL ,
213- item. span ,
214- "non-canonical implementation of `partial_cmp` on an `Ord` type" ,
215- |diag| {
216- let [ _, other] = body. params else {
217- return ;
218- } ;
219- let Some ( std_or_core) = std_or_core ( cx) else {
220- return ;
221- } ;
222-
223- let suggs = match ( other. pat . simple_ident ( ) , needs_fully_qualified) {
224- ( Some ( other_ident) , true ) => vec ! [ (
225- block. span,
226- format!( "{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}" , other_ident. name) ,
227- ) ] ,
228- ( Some ( other_ident) , false ) => {
229- vec ! [ ( block. span, format!( "{{ Some(self.cmp({})) }}" , other_ident. name) ) ]
230- } ,
231- ( None , true ) => vec ! [
232- (
233- block. span,
234- format!( "{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}" ) ,
235- ) ,
236- ( other. pat. span, "other" . to_owned( ) ) ,
237- ] ,
238- ( None , false ) => vec ! [
239- ( block. span, "{ Some(self.cmp(other)) }" . to_owned( ) ) ,
240- ( other. pat. span, "other" . to_owned( ) ) ,
241- ] ,
242- } ;
229+ } ;
243230
244- diag. multipart_suggestion ( "change this to" , suggs, Applicability :: Unspecified ) ;
231+ let suggs = match ( other. pat . simple_ident ( ) , needs_fully_qualified) {
232+ ( Some ( other_ident) , true ) => vec ! [ (
233+ block. span,
234+ format!( "{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}" , other_ident. name) ,
235+ ) ] ,
236+ ( Some ( other_ident) , false ) => {
237+ vec ! [ ( block. span, format!( "{{ Some(self.cmp({})) }}" , other_ident. name) ) ]
245238 } ,
246- ) ;
247- }
248- }
239+ ( None , true ) => vec ! [
240+ (
241+ block. span,
242+ format!( "{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}" ) ,
243+ ) ,
244+ ( other. pat. span, "other" . to_owned( ) ) ,
245+ ] ,
246+ ( None , false ) => vec ! [
247+ ( block. span, "{ Some(self.cmp(other)) }" . to_owned( ) ) ,
248+ ( other. pat. span, "other" . to_owned( ) ) ,
249+ ] ,
250+ } ;
251+
252+ diag. multipart_suggestion ( "change this to" , suggs, Applicability :: Unspecified ) ;
253+ } ,
254+ ) ;
249255}
250256
251257/// Return true if `expr_kind` is a `cmp` call.
@@ -256,26 +262,27 @@ fn expr_is_cmp<'tcx>(
256262 needs_fully_qualified : & mut bool ,
257263) -> bool {
258264 let impl_item_did = impl_item. owner_id . def_id ;
259- if let ExprKind :: Call (
260- Expr {
261- kind : ExprKind :: Path ( some_path ) ,
262- hir_id : some_hir_id ,
263- ..
264- } ,
265- [ cmp_expr ] ,
266- ) = expr . kind
267- {
268- is_res_lang_ctor ( cx, cx. qpath_res ( some_path, * some_hir_id) , LangItem :: OptionSome )
265+ match expr . kind {
266+ ExprKind :: Call (
267+ Expr {
268+ kind : ExprKind :: Path ( some_path ) ,
269+ hir_id : some_hir_id ,
270+ ..
271+ } ,
272+ [ cmp_expr ] ,
273+ ) => {
274+ is_res_lang_ctor ( cx, cx. qpath_res ( some_path, * some_hir_id) , LangItem :: OptionSome )
269275 // Fix #11178, allow `Self::cmp(self, ..)` too
270276 && self_cmp_call ( cx, cmp_expr, impl_item_did, needs_fully_qualified)
271- } else if let ExprKind :: MethodCall ( _, recv, [ ] , _) = expr. kind {
272- cx. tcx
273- . typeck ( impl_item_did)
274- . type_dependent_def_id ( expr. hir_id )
275- . is_some_and ( |def_id| is_diag_trait_item ( cx, def_id, sym:: Into ) )
276- && self_cmp_call ( cx, recv, impl_item_did, needs_fully_qualified)
277- } else {
278- false
277+ } ,
278+ ExprKind :: MethodCall ( _, recv, [ ] , _) => {
279+ cx. tcx
280+ . typeck ( impl_item_did)
281+ . type_dependent_def_id ( expr. hir_id )
282+ . is_some_and ( |def_id| is_diag_trait_item ( cx, def_id, sym:: Into ) )
283+ && self_cmp_call ( cx, recv, impl_item_did, needs_fully_qualified)
284+ } ,
285+ _ => false ,
279286 }
280287}
281288
0 commit comments