Skip to content

Commit 0b9293c

Browse files
committed
New lint: [derive_partial_eq_without_eq]
1 parent 43756b6 commit 0b9293c

6 files changed

+211
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3350,6 +3350,7 @@ Released 2018-09-13
33503350
[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
33513351
[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
33523352
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
3353+
[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
33533354
[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
33543355
[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
33553356
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types

clippy_lints/src/derive.rs

+77-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::paths;
33
use clippy_utils::ty::{implements_trait, is_copy};
44
use clippy_utils::{is_automatically_derived, is_lint_allowed, match_def_path};
55
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
67
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
78
use rustc_hir::{
89
BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
@@ -156,11 +157,44 @@ declare_clippy_lint! {
156157
"deriving `serde::Deserialize` on a type that has methods using `unsafe`"
157158
}
158159

160+
declare_clippy_lint! {
161+
/// ### What it does
162+
/// Checks for types that derive `PartialEq` and could implement `Eq`.
163+
///
164+
/// ### Why is this bad?
165+
/// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
166+
/// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
167+
/// in APIs that require `Eq` types. It also allows structs containing `T` to derive
168+
/// `Eq` themselves.
169+
///
170+
/// ### Example
171+
/// ```rust
172+
/// #[derive(PartialEq)]
173+
/// struct Foo {
174+
/// i_am_eq: i32,
175+
/// i_am_eq_too: Vec<String>,
176+
/// }
177+
/// ```
178+
/// Use instead:
179+
/// ```rust
180+
/// #[derive(PartialEq, Eq)]
181+
/// struct Foo {
182+
/// i_am_eq: i32,
183+
/// i_am_eq_too: Vec<String>,
184+
/// }
185+
/// ```
186+
#[clippy::version = "1.62.0"]
187+
pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
188+
nursery,
189+
"deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
190+
}
191+
159192
declare_lint_pass!(Derive => [
160193
EXPL_IMPL_CLONE_ON_COPY,
161194
DERIVE_HASH_XOR_EQ,
162195
DERIVE_ORD_XOR_PARTIAL_ORD,
163-
UNSAFE_DERIVE_DESERIALIZE
196+
UNSAFE_DERIVE_DESERIALIZE,
197+
DERIVE_PARTIAL_EQ_WITHOUT_EQ
164198
]);
165199

166200
impl<'tcx> LateLintPass<'tcx> for Derive {
@@ -176,6 +210,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
176210

177211
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
178212
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
213+
check_partial_eq_without_eq(cx, item.span, trait_ref, ty, is_automatically_derived);
179214

180215
if is_automatically_derived {
181216
check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
@@ -419,3 +454,43 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
419454
self.cx.tcx.hir()
420455
}
421456
}
457+
458+
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
459+
fn check_partial_eq_without_eq<'tcx>(
460+
cx: &LateContext<'tcx>,
461+
span: Span,
462+
trait_ref: &TraitRef<'_>,
463+
ty: Ty<'tcx>,
464+
peq_is_automatically_derived: bool,
465+
) {
466+
if_chain! {
467+
if let ty::Adt(adt, substs) = ty.kind();
468+
if peq_is_automatically_derived;
469+
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
470+
if let Some(def_id) = trait_ref.trait_def_id();
471+
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
472+
if !implements_trait(cx, ty, eq_trait_def_id, substs);
473+
then {
474+
// If all of our fields implement `Eq`, we can implement `Eq` too
475+
for variant in adt.variants() {
476+
for field in &variant.fields {
477+
let ty = field.ty(cx.tcx, substs);
478+
479+
if !implements_trait(cx, ty, eq_trait_def_id, substs) {
480+
return;
481+
}
482+
}
483+
}
484+
485+
span_lint_and_sugg(
486+
cx,
487+
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
488+
span.ctxt().outer_expn_data().call_site,
489+
"you are deriving `PartialEq` and can implement `Eq`",
490+
"consider deriving `Eq` as well",
491+
"PartialEq, Eq".to_string(),
492+
Applicability::MachineApplicable,
493+
)
494+
}
495+
}
496+
}

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ store.register_lints(&[
113113
derivable_impls::DERIVABLE_IMPLS,
114114
derive::DERIVE_HASH_XOR_EQ,
115115
derive::DERIVE_ORD_XOR_PARTIAL_ORD,
116+
derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ,
116117
derive::EXPL_IMPL_CLONE_ON_COPY,
117118
derive::UNSAFE_DERIVE_DESERIALIZE,
118119
disallowed_methods::DISALLOWED_METHODS,

clippy_lints/src/lib.register_nursery.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
66
LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
77
LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY),
88
LintId::of(copies::BRANCHES_SHARING_CODE),
9+
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
910
LintId::of(equatable_if_let::EQUATABLE_IF_LET),
1011
LintId::of(fallible_impl_from::FALLIBLE_IMPL_FROM),
1112
LintId::of(floating_point_arithmetic::IMPRECISE_FLOPS),
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#![warn(clippy::derive_partial_eq_without_eq)]
2+
3+
// Don't warn on structs that aren't PartialEq
4+
struct NotPartialEq {
5+
foo: u32,
6+
bar: String,
7+
}
8+
9+
// Eq can be derived but is missing
10+
#[derive(Debug, PartialEq)]
11+
struct MissingEq {
12+
foo: u32,
13+
bar: String,
14+
}
15+
16+
// Eq is derived
17+
#[derive(PartialEq, Eq)]
18+
struct NotMissingEq {
19+
foo: u32,
20+
bar: String,
21+
}
22+
23+
// Eq is manually implemented
24+
#[derive(PartialEq)]
25+
struct ManualEqImpl {
26+
foo: u32,
27+
bar: String,
28+
}
29+
30+
impl Eq for ManualEqImpl {}
31+
32+
// Cannot be Eq because f32 isn't Eq
33+
#[derive(PartialEq)]
34+
struct CannotBeEq {
35+
foo: u32,
36+
bar: f32,
37+
}
38+
39+
// Don't warn if PartialEq is manually implemented
40+
struct ManualPartialEqImpl {
41+
foo: u32,
42+
bar: String,
43+
}
44+
45+
impl PartialEq for ManualPartialEqImpl {
46+
fn eq(&self, other: &Self) -> bool {
47+
self.foo == other.foo && self.bar == other.bar
48+
}
49+
}
50+
51+
// Generic fields should be properly checked for Eq-ness
52+
#[derive(PartialEq)]
53+
struct GenericNotEq<T: Eq, U: PartialEq> {
54+
foo: T,
55+
bar: U,
56+
}
57+
58+
#[derive(PartialEq)]
59+
struct GenericEq<T: Eq, U: Eq> {
60+
foo: T,
61+
bar: U,
62+
}
63+
64+
#[derive(PartialEq)]
65+
struct TupleStruct(u32);
66+
67+
#[derive(PartialEq)]
68+
struct GenericTupleStruct<T: Eq>(T);
69+
70+
#[derive(PartialEq)]
71+
struct TupleStructNotEq(f32);
72+
73+
#[derive(PartialEq)]
74+
enum Enum {
75+
Foo(u32),
76+
Bar { a: String, b: () },
77+
}
78+
79+
#[derive(PartialEq)]
80+
enum GenericEnum<T: Eq, U: Eq, V: Eq> {
81+
Foo(T),
82+
Bar { a: U, b: V },
83+
}
84+
85+
#[derive(PartialEq)]
86+
enum EnumNotEq {
87+
Foo(u32),
88+
Bar { a: String, b: f32 },
89+
}
90+
91+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: you are deriving `PartialEq` and can implement `Eq`
2+
--> $DIR/derive_partial_eq_without_eq.rs:10:17
3+
|
4+
LL | #[derive(Debug, PartialEq)]
5+
| ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
6+
|
7+
= note: `-D clippy::derive-partial-eq-without-eq` implied by `-D warnings`
8+
9+
error: you are deriving `PartialEq` and can implement `Eq`
10+
--> $DIR/derive_partial_eq_without_eq.rs:58:10
11+
|
12+
LL | #[derive(PartialEq)]
13+
| ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
14+
15+
error: you are deriving `PartialEq` and can implement `Eq`
16+
--> $DIR/derive_partial_eq_without_eq.rs:64:10
17+
|
18+
LL | #[derive(PartialEq)]
19+
| ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
20+
21+
error: you are deriving `PartialEq` and can implement `Eq`
22+
--> $DIR/derive_partial_eq_without_eq.rs:67:10
23+
|
24+
LL | #[derive(PartialEq)]
25+
| ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
26+
27+
error: you are deriving `PartialEq` and can implement `Eq`
28+
--> $DIR/derive_partial_eq_without_eq.rs:73:10
29+
|
30+
LL | #[derive(PartialEq)]
31+
| ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
32+
33+
error: you are deriving `PartialEq` and can implement `Eq`
34+
--> $DIR/derive_partial_eq_without_eq.rs:79:10
35+
|
36+
LL | #[derive(PartialEq)]
37+
| ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
38+
39+
error: aborting due to 6 previous errors
40+

0 commit comments

Comments
 (0)