@@ -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,78 @@ 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
+ pub ( crate ) struct NonCanonicalImpls {
115
+ partial_ord_trait : Option < DefId > ,
116
+ ord_trait : Option < DefId > ,
117
+ clone_trait : Option < DefId > ,
118
+ copy_trait : Option < DefId > ,
119
+ }
120
+
121
+ impl NonCanonicalImpls {
122
+ pub ( crate ) fn new ( tcx : TyCtxt < ' _ > ) -> Self {
123
+ let lang_items = tcx. lang_items ( ) ;
124
+ Self {
125
+ partial_ord_trait : lang_items. partial_ord_trait ( ) ,
126
+ ord_trait : tcx. get_diagnostic_item ( sym:: Ord ) ,
127
+ clone_trait : lang_items. clone_trait ( ) ,
128
+ copy_trait : lang_items. copy_trait ( ) ,
129
+ }
130
+ }
131
+ }
113
132
114
133
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 ( ) )
134
+ fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
135
+ if let ItemKind :: Impl ( impl_ ) = item . kind
136
+ && ( 1 ..= 2 ) . contains ( & impl_ . items . len ( ) )
118
137
&& 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
138
&& !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
139
{
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) ;
140
+ let assoc_fns = impl_
141
+ . items
142
+ . iter ( )
143
+ . map ( |id| cx. tcx . hir_impl_item ( * id) )
144
+ . filter_map ( |assoc| {
145
+ if let ImplItemKind :: Fn ( _, assoc_id) = assoc. kind
146
+ && let body = cx. tcx . hir_body ( assoc_id)
147
+ && let ExprKind :: Block ( block, ..) = body. value . kind
148
+ {
149
+ Some ( ( assoc, body, block) )
150
+ } else {
151
+ None
152
+ }
153
+ } ) ;
154
+
155
+ // NOTE: In the first branch, we don't want to pull the "implements `Copy`" check into the
156
+ // let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
157
+ // the second branch. The same goes for the "implements `Ord`" check in the second branch, but
158
+ // there, the lint still fires, as there is no third branch. And we don't want that.
159
+ #[ expect( clippy:: collapsible_if) ]
160
+ if Some ( trait_impl. def_id ) == self . clone_trait {
161
+ if let Some ( copy_trait) = self . copy_trait
162
+ && implements_trait ( cx, trait_impl. self_ty ( ) , copy_trait, & [ ] )
163
+ {
164
+ for ( assoc, _, block) in assoc_fns {
165
+ // NOTE: we don't bother doing the method name check before the proc-macro check, because we
166
+ // lint both methods of `Clone`, and therefore would need to repeat the proc-macro check anyway
167
+ if !is_from_proc_macro ( cx, assoc) {
168
+ check_clone_on_copy ( cx, assoc, block)
169
+ }
170
+ }
171
+ }
172
+ } else if Some ( trait_impl. def_id ) == self . partial_ord_trait {
173
+ if let Some ( ord_trait) = self . ord_trait
174
+ && implements_trait ( cx, trait_impl. self_ty ( ) , ord_trait, & [ ] )
175
+ {
176
+ for ( assoc, body, block) in assoc_fns {
177
+ // NOTE: `PartialOrd` has methods `lt/le/ge/gt` which we don't lint, so
178
+ // don't bother checking whether _they_ come from a proc-macro
179
+ if assoc. ident . name == sym:: partial_cmp && !is_from_proc_macro ( cx, assoc) {
180
+ check_partial_ord_on_ord ( cx, assoc, item, & trait_impl, body, block) ;
181
+ }
182
+ }
183
+ }
139
184
}
140
185
}
141
186
}
0 commit comments