Skip to content

Commit 8a11cf8

Browse files
committed
Split out each lint into a separate module
Best viewed with `git diff --color-moved`
1 parent 4fd9a06 commit 8a11cf8

File tree

6 files changed

+356
-324
lines changed

6 files changed

+356
-324
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_hir as hir;
3+
use rustc_lint::LateContext;
4+
use rustc_middle::ty::Ty;
5+
use rustc_span::{Span, sym};
6+
7+
use super::DERIVE_ORD_XOR_PARTIAL_ORD;
8+
9+
/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
10+
pub(super) fn check<'tcx>(
11+
cx: &LateContext<'tcx>,
12+
span: Span,
13+
trait_ref: &hir::TraitRef<'_>,
14+
ty: Ty<'tcx>,
15+
ord_is_automatically_derived: bool,
16+
) {
17+
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
18+
&& let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait()
19+
&& let Some(def_id) = &trait_ref.trait_def_id()
20+
&& *def_id == ord_trait_def_id
21+
{
22+
// Look for the PartialOrd implementations for `ty`
23+
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
24+
let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
25+
26+
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
27+
return;
28+
}
29+
30+
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
31+
32+
// Only care about `impl PartialOrd<Foo> for Foo`
33+
// For `impl PartialOrd<B> for A, input_types is [A, B]
34+
if trait_ref.instantiate_identity().args.type_at(1) == ty {
35+
let mess = if partial_ord_is_automatically_derived {
36+
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
37+
} else {
38+
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
39+
};
40+
41+
span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| {
42+
if let Some(local_def_id) = impl_id.as_local() {
43+
let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id);
44+
diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here");
45+
}
46+
});
47+
}
48+
});
49+
}
50+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use clippy_utils::diagnostics::span_lint_hir_and_then;
2+
use clippy_utils::has_non_exhaustive_attr;
3+
use clippy_utils::ty::implements_trait_with_env;
4+
use rustc_errors::Applicability;
5+
use rustc_hir as hir;
6+
use rustc_hir::def_id::DefId;
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast};
9+
use rustc_span::{Span, sym};
10+
11+
use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ;
12+
13+
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
14+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
15+
if let ty::Adt(adt, args) = ty.kind()
16+
&& cx.tcx.visibility(adt.did()).is_public()
17+
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
18+
&& let Some(def_id) = trait_ref.trait_def_id()
19+
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
20+
&& !has_non_exhaustive_attr(cx.tcx, *adt)
21+
&& !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id)
22+
&& let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
23+
&& let Some(local_def_id) = adt.did().as_local()
24+
// If all of our fields implement `Eq`, we can implement `Eq` too
25+
&& adt
26+
.all_fields()
27+
.map(|f| f.ty(cx.tcx, args))
28+
.all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[]))
29+
{
30+
span_lint_hir_and_then(
31+
cx,
32+
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
33+
cx.tcx.local_def_id_to_hir_id(local_def_id),
34+
span.ctxt().outer_expn_data().call_site,
35+
"you are deriving `PartialEq` and can implement `Eq`",
36+
|diag| {
37+
diag.span_suggestion(
38+
span.ctxt().outer_expn_data().call_site,
39+
"consider deriving `Eq` as well",
40+
"PartialEq, Eq",
41+
Applicability::MachineApplicable,
42+
);
43+
},
44+
);
45+
}
46+
}
47+
48+
fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool {
49+
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
50+
}
51+
52+
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
53+
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
54+
// Initial map from generic index to param def.
55+
// Vec<(param_def, needs_eq)>
56+
let mut params = tcx
57+
.generics_of(did)
58+
.own_params
59+
.iter()
60+
.map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. })))
61+
.collect::<Vec<_>>();
62+
63+
let ty_predicates = tcx.predicates_of(did).predicates;
64+
for (p, _) in ty_predicates {
65+
if let ClauseKind::Trait(p) = p.kind().skip_binder()
66+
&& p.trait_ref.def_id == eq_trait_id
67+
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
68+
{
69+
// Flag types which already have an `Eq` bound.
70+
params[self_ty.index as usize].1 = false;
71+
}
72+
}
73+
74+
let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain(
75+
params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| {
76+
ClauseKind::Trait(TraitPredicate {
77+
trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]),
78+
polarity: ty::PredicatePolarity::Positive,
79+
})
80+
.upcast(tcx)
81+
}),
82+
)));
83+
ty::TypingEnv {
84+
typing_mode: ty::TypingMode::non_body_analysis(),
85+
param_env,
86+
}
87+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_hir as hir;
3+
use rustc_lint::LateContext;
4+
use rustc_middle::ty::Ty;
5+
use rustc_span::{Span, sym};
6+
7+
use super::DERIVED_HASH_WITH_MANUAL_EQ;
8+
9+
/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint.
10+
pub(super) fn check<'tcx>(
11+
cx: &LateContext<'tcx>,
12+
span: Span,
13+
trait_ref: &hir::TraitRef<'_>,
14+
ty: Ty<'tcx>,
15+
hash_is_automatically_derived: bool,
16+
) {
17+
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
18+
&& let Some(def_id) = trait_ref.trait_def_id()
19+
&& cx.tcx.is_diagnostic_item(sym::Hash, def_id)
20+
{
21+
// Look for the PartialEq implementations for `ty`
22+
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
23+
let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
24+
25+
if !hash_is_automatically_derived || peq_is_automatically_derived {
26+
return;
27+
}
28+
29+
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
30+
31+
// Only care about `impl PartialEq<Foo> for Foo`
32+
// For `impl PartialEq<B> for A, input_types is [A, B]
33+
if trait_ref.instantiate_identity().args.type_at(1) == ty {
34+
span_lint_and_then(
35+
cx,
36+
DERIVED_HASH_WITH_MANUAL_EQ,
37+
span,
38+
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
39+
|diag| {
40+
if let Some(local_def_id) = impl_id.as_local() {
41+
let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id);
42+
diag.span_note(cx.tcx.hir_span(hir_id), "`PartialEq` implemented here");
43+
}
44+
},
45+
);
46+
}
47+
});
48+
}
49+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use clippy_utils::diagnostics::span_lint_and_note;
2+
use clippy_utils::ty::{implements_trait, is_copy};
3+
use rustc_hir::{self as hir, Item};
4+
use rustc_lint::LateContext;
5+
use rustc_middle::ty::{self, GenericArgKind, Ty};
6+
7+
use super::EXPL_IMPL_CLONE_ON_COPY;
8+
9+
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
10+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
11+
let clone_id = match cx.tcx.lang_items().clone_trait() {
12+
Some(id) if trait_ref.trait_def_id() == Some(id) => id,
13+
_ => return,
14+
};
15+
let Some(copy_id) = cx.tcx.lang_items().copy_trait() else {
16+
return;
17+
};
18+
let (ty_adt, ty_subs) = match *ty.kind() {
19+
// Unions can't derive clone.
20+
ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
21+
_ => return,
22+
};
23+
// If the current self type doesn't implement Copy (due to generic constraints), search to see if
24+
// there's a Copy impl for any instance of the adt.
25+
if !is_copy(cx, ty) {
26+
if ty_subs.non_erasable_generics().next().is_some() {
27+
let has_copy_impl = cx.tcx.local_trait_impls(copy_id).iter().any(|&id| {
28+
matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _)
29+
if ty_adt.did() == adt.did())
30+
});
31+
if !has_copy_impl {
32+
return;
33+
}
34+
} else {
35+
return;
36+
}
37+
}
38+
// Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
39+
// this impl.
40+
if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
41+
return;
42+
}
43+
// `#[repr(packed)]` structs with type/const parameters can't derive `Clone`.
44+
// https://github.com/rust-lang/rust-clippy/issues/10188
45+
if ty_adt.repr().packed()
46+
&& ty_subs
47+
.iter()
48+
.any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_)))
49+
{
50+
return;
51+
}
52+
// The presence of `unsafe` fields prevents deriving `Clone` automatically
53+
if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) {
54+
return;
55+
}
56+
57+
span_lint_and_note(
58+
cx,
59+
EXPL_IMPL_CLONE_ON_COPY,
60+
item.span,
61+
"you are implementing `Clone` explicitly on a `Copy` type",
62+
Some(item.span),
63+
"consider deriving `Clone` or removing `Copy`",
64+
);
65+
}

0 commit comments

Comments
 (0)