@@ -4,11 +4,11 @@ use clippy_utils::{
4
4
is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core,
5
5
} ;
6
6
use rustc_errors:: Applicability ;
7
- use rustc_hir:: def_id:: LocalDefId ;
8
- use rustc_hir:: { Block , Body , Expr , ExprKind , ImplItem , ImplItemKind , Item , LangItem , Node , UnOp } ;
7
+ use rustc_hir:: def_id:: { DefId , LocalDefId } ;
8
+ use rustc_hir:: { Block , Body , Expr , ExprKind , ImplItem , ImplItemKind , Item , ItemKind , LangItem , UnOp } ;
9
9
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10
- use rustc_middle:: ty:: EarlyBinder ;
11
- use rustc_session:: declare_lint_pass ;
10
+ use rustc_middle:: ty:: { EarlyBinder , TyCtxt } ;
11
+ use rustc_session:: impl_lint_pass ;
12
12
use rustc_span:: sym;
13
13
use rustc_span:: symbol:: kw;
14
14
@@ -109,40 +109,91 @@ declare_clippy_lint! {
109
109
suspicious,
110
110
"non-canonical implementation of `PartialOrd` on an `Ord` type"
111
111
}
112
- declare_lint_pass ! ( NonCanonicalImpls => [ NON_CANONICAL_CLONE_IMPL , NON_CANONICAL_PARTIAL_ORD_IMPL ] ) ;
112
+ impl_lint_pass ! ( NonCanonicalImpls => [ NON_CANONICAL_CLONE_IMPL , NON_CANONICAL_PARTIAL_ORD_IMPL ] ) ;
113
+
114
+ #[ expect(
115
+ clippy:: struct_field_names,
116
+ reason = "`_trait` suffix is meaningful on its own, \
117
+ and creating an inner `StoredTraits` struct would just add a level of indirection"
118
+ ) ]
119
+ pub ( crate ) struct NonCanonicalImpls {
120
+ partial_ord_trait : Option < DefId > ,
121
+ ord_trait : Option < DefId > ,
122
+ clone_trait : Option < DefId > ,
123
+ copy_trait : Option < DefId > ,
124
+ }
125
+
126
+ impl NonCanonicalImpls {
127
+ pub ( crate ) fn new ( tcx : TyCtxt < ' _ > ) -> Self {
128
+ let lang_items = tcx. lang_items ( ) ;
129
+ Self {
130
+ partial_ord_trait : lang_items. partial_ord_trait ( ) ,
131
+ ord_trait : tcx. get_diagnostic_item ( sym:: Ord ) ,
132
+ clone_trait : lang_items. clone_trait ( ) ,
133
+ copy_trait : lang_items. copy_trait ( ) ,
134
+ }
135
+ }
136
+ }
113
137
114
138
impl LateLintPass < ' _ > for NonCanonicalImpls {
115
- fn check_impl_item < ' tcx > ( & mut self , cx : & LateContext < ' tcx > , impl_item : & ImplItem < ' tcx > ) {
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 ) )
139
+ fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
140
+ if let ItemKind :: Impl ( impl_) = item. kind
141
+ // Both `PartialOrd` and `Clone` have one required method, and `PartialOrd` can have 5 methods in total
142
+ && ( 1 ..=5 ) . contains ( & impl_. items . len ( ) )
143
+ && let Some ( of_trait) = impl_. of_trait
144
+ && let Some ( trait_did) = of_trait. trait_ref . trait_def_id ( )
145
+ // Check this early to hopefully bail out as soon as possible
146
+ && [ self . clone_trait , self . partial_ord_trait ] . contains ( & Some ( trait_did) )
122
147
&& !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
148
{
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
- // If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
135
- // since it doesn't have an `Rhs`
136
- && match trait_impl. args . as_slice ( ) {
137
- [ _] => true ,
138
- [ lhs, rhs] => lhs == rhs,
139
- _ => false ,
149
+ let assoc_fns = impl_
150
+ . items
151
+ . iter ( )
152
+ . map ( |id| cx. tcx . hir_impl_item ( * id) )
153
+ . filter_map ( |assoc| {
154
+ if let ImplItemKind :: Fn ( _, body_id) = assoc. kind
155
+ && let body = cx. tcx . hir_body ( body_id)
156
+ && let ExprKind :: Block ( block, ..) = body. value . kind
157
+ && !block. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
158
+ {
159
+ Some ( ( assoc, body, block) )
160
+ } else {
161
+ None
162
+ }
163
+ } ) ;
164
+
165
+ #[ expect( clippy:: collapsible_if) ]
166
+ // Reason: In the first branch, we don't want to pull the "implements `Copy`" check into the
167
+ // let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
168
+ // the second branch. The same goes for the inner checks in the second branch, but
169
+ // there, the lint still fires, as there is no third branch. And we don't want that.
170
+ if Some ( trait_did) == self . clone_trait {
171
+ if let Some ( copy_trait) = self . copy_trait
172
+ && let Some ( trait_impl) = cx. tcx . impl_trait_ref ( item. owner_id ) . map ( EarlyBinder :: skip_binder)
173
+ && implements_trait ( cx, trait_impl. self_ty ( ) , copy_trait, & [ ] )
174
+ {
175
+ for ( assoc, _, block) in assoc_fns {
176
+ check_clone_on_copy ( cx, assoc, block) ;
177
+ }
178
+ }
179
+ } else if Some ( trait_did) == self . partial_ord_trait {
180
+ if let Some ( trait_impl) = cx. tcx . impl_trait_ref ( item. owner_id ) . map ( EarlyBinder :: skip_binder)
181
+ // If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
182
+ // since it doesn't have an `Rhs`
183
+ && match trait_impl. args . as_slice ( ) {
184
+ [ _] => true ,
185
+ [ lhs, rhs] => lhs == rhs,
186
+ _ => false ,
187
+ }
188
+ && let Some ( ord_trait) = self . ord_trait
189
+ && implements_trait ( cx, trait_impl. self_ty ( ) , ord_trait, & [ ] )
190
+ {
191
+ for ( assoc, body, block) in assoc_fns {
192
+ if assoc. ident . name == sym:: partial_cmp {
193
+ check_partial_ord_on_ord ( cx, assoc, item, body, block) ;
194
+ }
195
+ }
140
196
}
141
- && impl_item. ident . name == sym:: partial_cmp
142
- && let Some ( ord_def_id) = cx. tcx . get_diagnostic_item ( sym:: Ord )
143
- && implements_trait ( cx, trait_impl. self_ty ( ) , ord_def_id, & [ ] )
144
- {
145
- check_partial_ord_on_ord ( cx, impl_item, item, body, block) ;
146
197
}
147
198
}
148
199
}
@@ -160,6 +211,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
160
211
return ;
161
212
}
162
213
214
+ if is_from_proc_macro ( cx, impl_item) {
215
+ return ;
216
+ }
217
+
163
218
span_lint_and_sugg (
164
219
cx,
165
220
NON_CANONICAL_CLONE_IMPL ,
@@ -172,6 +227,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
172
227
}
173
228
174
229
if impl_item. ident . name == sym:: clone_from {
230
+ if is_from_proc_macro ( cx, impl_item) {
231
+ return ;
232
+ }
233
+
175
234
span_lint_and_sugg (
176
235
cx,
177
236
NON_CANONICAL_CLONE_IMPL ,
@@ -212,6 +271,8 @@ fn check_partial_ord_on_ord<'tcx>(
212
271
&& expr_is_cmp ( cx, ret, impl_item, & mut needs_fully_qualified)
213
272
{
214
273
return ;
274
+ } else if is_from_proc_macro ( cx, impl_item) {
275
+ return ;
215
276
}
216
277
217
278
span_lint_and_then (
0 commit comments