@@ -5,9 +5,9 @@ use clippy_utils::{
5
5
} ;
6
6
use rustc_errors:: Applicability ;
7
7
use 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 } ;
9
9
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10
- use rustc_middle:: ty:: EarlyBinder ;
10
+ use rustc_middle:: ty:: { EarlyBinder , TraitRef } ;
11
11
use rustc_session:: declare_lint_pass;
12
12
use rustc_span:: sym;
13
13
use rustc_span:: symbol:: kw;
@@ -112,140 +112,146 @@ declare_clippy_lint! {
112
112
declare_lint_pass ! ( NonCanonicalImpls => [ NON_CANONICAL_CLONE_IMPL , NON_CANONICAL_PARTIAL_ORD_IMPL ] ) ;
113
113
114
114
impl LateLintPass < ' _ > for NonCanonicalImpls {
115
- #[ expect( clippy:: too_many_lines) ]
116
115
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
+ }
125
140
}
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 }`
134
153
return ;
135
154
}
136
155
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
+ }
159
166
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
+ }
163
179
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`.
184
191
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 {
200
225
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 {
207
228
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
+ } ;
243
230
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) ) ]
245
238
} ,
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
+ ) ;
249
255
}
250
256
251
257
/// Return true if `expr_kind` is a `cmp` call.
@@ -256,26 +262,27 @@ fn expr_is_cmp<'tcx>(
256
262
needs_fully_qualified : & mut bool ,
257
263
) -> bool {
258
264
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 )
269
275
// Fix #11178, allow `Self::cmp(self, ..)` too
270
276
&& 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 ,
279
286
}
280
287
}
281
288
0 commit comments