@@ -4,11 +4,11 @@ use clippy_utils::{
44 is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core,
55} ;
66use 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 } ;
99use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
10- use rustc_middle:: ty:: { EarlyBinder , TraitRef } ;
11- use rustc_session:: declare_lint_pass ;
10+ use rustc_middle:: ty:: { EarlyBinder , TraitRef , TyCtxt } ;
11+ use rustc_session:: impl_lint_pass ;
1212use rustc_span:: sym;
1313use rustc_span:: symbol:: kw;
1414
@@ -109,33 +109,84 @@ declare_clippy_lint! {
109109 suspicious,
110110 "non-canonical implementation of `PartialOrd` on an `Ord` type"
111111}
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+ }
113137
114138impl 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 ( ) )
118142 && 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 ) )
122143 && !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)
127144 {
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+ && !block. span . in_external_macro ( cx. sess ( ) . source_map ( ) )
154+ {
155+ Some ( ( assoc, body, block) )
156+ } else {
157+ None
158+ }
159+ } ) ;
160+
161+ #[ expect( clippy:: collapsible_if) ]
162+ // Reason: In the first branch, we don't want to pull the "implements `Copy`" check into the
163+ // let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
164+ // the second branch. The same goes for the "implements `Ord`" check in the second branch, but
165+ // there, the lint still fires, as there is no third branch. And we don't want that.
166+ if Some ( trait_impl. def_id ) == self . clone_trait {
167+ if let Some ( copy_trait) = self . copy_trait
168+ && implements_trait ( cx, trait_impl. self_ty ( ) , copy_trait, & [ ] )
169+ {
170+ for ( assoc, _, block) in assoc_fns {
171+ // NOTE: we don't bother doing the method name check before the proc-macro check, because we
172+ // lint both methods of `Clone`, and therefore would need to repeat the proc-macro check anyway
173+ if !is_from_proc_macro ( cx, assoc) {
174+ check_clone_on_copy ( cx, assoc, block) ;
175+ }
176+ }
177+ }
178+ } else if Some ( trait_impl. def_id ) == self . partial_ord_trait {
179+ if let Some ( ord_trait) = self . ord_trait
180+ && implements_trait ( cx, trait_impl. self_ty ( ) , ord_trait, & [ ] )
181+ {
182+ for ( assoc, body, block) in assoc_fns {
183+ // NOTE: `PartialOrd` has methods `lt/le/ge/gt` which we don't lint, so
184+ // don't bother checking whether _they_ come from a proc-macro
185+ if assoc. ident . name == sym:: partial_cmp && !is_from_proc_macro ( cx, assoc) {
186+ check_partial_ord_on_ord ( cx, assoc, item, & trait_impl, body, block) ;
187+ }
188+ }
189+ }
139190 }
140191 }
141192 }
0 commit comments