Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf)));
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
store.register_late_pass(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf)));
store.register_late_pass(|_| Box::new(non_canonical_impls::NonCanonicalImpls));
store.register_late_pass(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx)));
store.register_late_pass(move |_| Box::new(single_call_fn::SingleCallFn::new(conf)));
store.register_early_pass(move || Box::new(raw_strings::RawStrings::new(conf)));
store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf)));
Expand Down
128 changes: 94 additions & 34 deletions clippy_lints/src/non_canonical_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use clippy_utils::{
is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core,
};
use rustc_errors::Applicability;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, LangItem, Node, UnOp};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{EarlyBinder, TraitRef};
use rustc_session::declare_lint_pass;
use rustc_middle::ty::{EarlyBinder, TyCtxt};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
use rustc_span::symbol::kw;

Expand Down Expand Up @@ -109,33 +109,91 @@ declare_clippy_lint! {
suspicious,
"non-canonical implementation of `PartialOrd` on an `Ord` type"
}
declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
impl_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);

#[expect(
clippy::struct_field_names,
reason = "`_trait` suffix is meaningful on its own, \
and creating an inner `StoredTraits` struct would just add a level of indirection"
)]
pub(crate) struct NonCanonicalImpls {
partial_ord_trait: Option<DefId>,
ord_trait: Option<DefId>,
clone_trait: Option<DefId>,
copy_trait: Option<DefId>,
}

impl NonCanonicalImpls {
pub(crate) fn new(tcx: TyCtxt<'_>) -> Self {
let lang_items = tcx.lang_items();
Self {
partial_ord_trait: lang_items.partial_ord_trait(),
ord_trait: tcx.get_diagnostic_item(sym::Ord),
clone_trait: lang_items.clone_trait(),
copy_trait: lang_items.copy_trait(),
}
}
}

impl LateLintPass<'_> for NonCanonicalImpls {
fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) {
if let ImplItemKind::Fn(_, impl_item_id) = impl_item.kind
&& let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id())
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
&& let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id)
// NOTE: check this early to avoid expensive checks that come after this one
&& matches!(trait_name, Some(sym::Clone | sym::PartialOrd))
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if let ItemKind::Impl(impl_) = item.kind
// Both `PartialOrd` and `Clone` have one required method, and `PartialOrd` can have 5 methods in total
&& (1..=5).contains(&impl_.items.len())
&& let Some(of_trait) = impl_.of_trait
&& let Some(trait_did) = of_trait.trait_ref.trait_def_id()
// Check this early to hopefully bail out as soon as possible
&& [self.clone_trait, self.partial_ord_trait].contains(&Some(trait_did))
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
&& let body = cx.tcx.hir_body(impl_item_id)
&& let ExprKind::Block(block, ..) = body.value.kind
&& !block.span.in_external_macro(cx.sess().source_map())
&& !is_from_proc_macro(cx, impl_item)
{
if trait_name == Some(sym::Clone)
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
{
check_clone_on_copy(cx, impl_item, block);
} else if trait_name == Some(sym::PartialOrd)
&& impl_item.ident.name == sym::partial_cmp
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
{
check_partial_ord_on_ord(cx, impl_item, item, &trait_impl, body, block);
let assoc_fns = impl_
.items
.iter()
.map(|id| cx.tcx.hir_impl_item(*id))
.filter_map(|assoc| {
if let ImplItemKind::Fn(_, body_id) = assoc.kind
&& let body = cx.tcx.hir_body(body_id)
&& let ExprKind::Block(block, ..) = body.value.kind
&& !block.span.in_external_macro(cx.sess().source_map())
{
Some((assoc, body, block))
} else {
None
}
});

#[expect(clippy::collapsible_if)]
// Reason: In the first branch, we don't want to pull the "implements `Copy`" check into the
// let-chain, because it failing doesn't change the fact that it doesn't make sense for us to go to
// the second branch. The same goes for the inner checks in the second branch, but
// there, the lint still fires, as there is no third branch. And we don't want that.
if Some(trait_did) == self.clone_trait {
if let Some(copy_trait) = self.copy_trait
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
&& implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])
{
for (assoc, _, block) in assoc_fns {
check_clone_on_copy(cx, assoc, block);
}
}
} else if Some(trait_did) == self.partial_ord_trait {
if let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
// If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,
// since it doesn't have an `Rhs`
&& match trait_impl.args.as_slice() {
[_] => true,
[lhs, rhs] => lhs == rhs,
_ => false,
}
&& let Some(ord_trait) = self.ord_trait
&& implements_trait(cx, trait_impl.self_ty(), ord_trait, &[])
{
for (assoc, body, block) in assoc_fns {
if assoc.ident.name == sym::partial_cmp {
check_partial_ord_on_ord(cx, assoc, item, body, block);
}
}
}
}
}
}
Expand All @@ -153,6 +211,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
return;
}

if is_from_proc_macro(cx, impl_item) {
return;
}

span_lint_and_sugg(
cx,
NON_CANONICAL_CLONE_IMPL,
Expand All @@ -165,6 +227,10 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B
}

if impl_item.ident.name == sym::clone_from {
if is_from_proc_macro(cx, impl_item) {
return;
}

span_lint_and_sugg(
cx,
NON_CANONICAL_CLONE_IMPL,
Expand All @@ -181,7 +247,6 @@ fn check_partial_ord_on_ord<'tcx>(
cx: &LateContext<'tcx>,
impl_item: &ImplItem<'_>,
item: &Item<'_>,
trait_impl: &TraitRef<'_>,
body: &Body<'_>,
block: &Block<'tcx>,
) {
Expand All @@ -206,12 +271,7 @@ fn check_partial_ord_on_ord<'tcx>(
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
{
return;
}
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
// suggestion tons more complex.
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
&& lhs != rhs
{
} else if is_from_proc_macro(cx, impl_item) {
return;
}

Expand Down
53 changes: 40 additions & 13 deletions tests/ui/non_canonical_clone_impl.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![no_main]

extern crate proc_macros;
use proc_macros::with_span;
use proc_macros::inline_macros;

// lint

Expand Down Expand Up @@ -100,18 +100,45 @@ impl<A: Copy> Clone for Uwu<A> {

impl<A: std::fmt::Debug + Copy + Clone> Copy for Uwu<A> {}

// should skip proc macros, see https://github.com/rust-lang/rust-clippy/issues/12788
#[derive(proc_macro_derive::NonCanonicalClone)]
pub struct G;
#[inline_macros]
mod issue12788 {
use proc_macros::{external, with_span};

with_span!(
span
// lint -- not an external macro
inline!(
#[derive(Copy)]
pub struct A;

#[derive(Copy)]
struct H;
impl Clone for H {
fn clone(&self) -> Self {
todo!()
impl Clone for A {
fn clone(&self) -> Self { *self }
}
}
);
);

// do not lint -- should skip external macros
external!(
#[derive(Copy)]
pub struct B;

impl Clone for B {
fn clone(&self) -> Self {
todo!()
}
}
);

// do not lint -- should skip proc macros
#[derive(proc_macro_derive::NonCanonicalClone)]
pub struct C;

with_span!(
span

#[derive(Copy)]
struct D;
impl Clone for D {
fn clone(&self) -> Self {
todo!()
}
}
);
}
56 changes: 43 additions & 13 deletions tests/ui/non_canonical_clone_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![no_main]

extern crate proc_macros;
use proc_macros::with_span;
use proc_macros::inline_macros;

// lint

Expand Down Expand Up @@ -114,18 +114,48 @@ impl<A: Copy> Clone for Uwu<A> {

impl<A: std::fmt::Debug + Copy + Clone> Copy for Uwu<A> {}

// should skip proc macros, see https://github.com/rust-lang/rust-clippy/issues/12788
#[derive(proc_macro_derive::NonCanonicalClone)]
pub struct G;
#[inline_macros]
mod issue12788 {
use proc_macros::{external, with_span};

with_span!(
span
// lint -- not an external macro
inline!(
#[derive(Copy)]
pub struct A;

#[derive(Copy)]
struct H;
impl Clone for H {
fn clone(&self) -> Self {
todo!()
impl Clone for A {
fn clone(&self) -> Self {
//~^ non_canonical_clone_impl
todo!()
}
}
}
);
);

// do not lint -- should skip external macros
external!(
#[derive(Copy)]
pub struct B;

impl Clone for B {
fn clone(&self) -> Self {
todo!()
}
}
);

// do not lint -- should skip proc macros
#[derive(proc_macro_derive::NonCanonicalClone)]
pub struct C;

with_span!(
span

#[derive(Copy)]
struct D;
impl Clone for D {
fn clone(&self) -> Self {
todo!()
}
}
);
}
14 changes: 13 additions & 1 deletion tests/ui/non_canonical_clone_impl.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,17 @@ LL | | *self = source.clone();
LL | | }
| |_____^ help: remove it

error: aborting due to 4 previous errors
error: non-canonical implementation of `clone` on a `Copy` type
--> tests/ui/non_canonical_clone_impl.rs:127:37
|
LL | fn clone(&self) -> Self {
| _____________________________________^
LL | |
LL | | todo!()
LL | | }
| |_____________^ help: change this to: `{ *self }`
|
= note: this error originates in the macro `__inline_mac_mod_issue12788` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 5 previous errors

Loading