@@ -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 } ;
9
- use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10
- use rustc_middle:: ty:: { EarlyBinder , TraitRef } ;
11
- use rustc_session:: declare_lint_pass ;
7
+ use rustc_hir:: def_id:: { DefId , LocalDefId } ;
8
+ use rustc_hir:: { Block , Body , Expr , ExprKind , ImplItem , ImplItemKind , Item , ItemKind , LangItem , UnOp } ;
9
+ use rustc_lint:: { LateContext , LateLintPass } ;
10
+ use rustc_middle:: ty:: { EarlyBinder , TraitRef , TyCtxt } ;
11
+ use rustc_session:: impl_lint_pass ;
12
12
use rustc_span:: sym;
13
13
use rustc_span:: symbol:: kw;
14
14
@@ -109,33 +109,83 @@ 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 ( ) )
139
+ fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
140
+ if let ItemKind :: Impl ( impl_ ) = item . kind
141
+ && ( 1 ..= 2 ) . contains ( & impl_ . items . len ( ) )
118
142
&& 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
143
&& !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
144
{
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) ;
145
+ let assoc_fns = impl_
146
+ . items
147
+ . iter ( )
148
+ . map ( |id| cx. tcx . hir_impl_item ( * id) )
149
+ . filter_map ( |assoc| {
150
+ if let ImplItemKind :: Fn ( _, assoc_id) = assoc. kind
151
+ && let body = cx. tcx . hir_body ( assoc_id)
152
+ && let ExprKind :: Block ( block, ..) = body. value . kind
153
+ {
154
+ Some ( ( assoc, body, block) )
155
+ } else {
156
+ None
157
+ }
158
+ } ) ;
159
+
160
+ // NOTE: In the first branch, we don't want to pull the "implements `Copy`" check into the
161
+ // let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
162
+ // the second branch. The same goes for the "implements `Ord`" check in the second branch, but
163
+ // there, the lint still fires, as there is no third branch. And we don't want that.
164
+ #[ expect( clippy:: collapsible_if) ]
165
+ if Some ( trait_impl. def_id ) == self . clone_trait {
166
+ if let Some ( copy_trait) = self . copy_trait
167
+ && implements_trait ( cx, trait_impl. self_ty ( ) , copy_trait, & [ ] )
168
+ {
169
+ for ( assoc, _, block) in assoc_fns {
170
+ // NOTE: we don't bother doing the method name check before the proc-macro check, because we
171
+ // lint both methods of `Clone`, and therefore would need to repeat the proc-macro check anyway
172
+ if !is_from_proc_macro ( cx, assoc) {
173
+ check_clone_on_copy ( cx, assoc, block) ;
174
+ }
175
+ }
176
+ }
177
+ } else if Some ( trait_impl. def_id ) == self . partial_ord_trait {
178
+ if let Some ( ord_trait) = self . ord_trait
179
+ && implements_trait ( cx, trait_impl. self_ty ( ) , ord_trait, & [ ] )
180
+ {
181
+ for ( assoc, body, block) in assoc_fns {
182
+ // NOTE: `PartialOrd` has methods `lt/le/ge/gt` which we don't lint, so
183
+ // don't bother checking whether _they_ come from a proc-macro
184
+ if assoc. ident . name == sym:: partial_cmp && !is_from_proc_macro ( cx, assoc) {
185
+ check_partial_ord_on_ord ( cx, assoc, item, & trait_impl, body, block) ;
186
+ }
187
+ }
188
+ }
139
189
}
140
190
}
141
191
}
0 commit comments