Skip to content

Commit fe84ff3

Browse files
committed
New lint: [derive_partial_eq_without_eq]
1 parent 77effb7 commit fe84ff3

29 files changed

+359
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
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_dev/src/update_lints.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev u
1717

1818
const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
1919

20-
#[derive(Clone, Copy, PartialEq)]
20+
#[derive(Clone, Copy, PartialEq, Eq)]
2121
pub enum UpdateMode {
2222
Check,
2323
Change,
@@ -372,7 +372,7 @@ fn exit_with_failure() {
372372
}
373373

374374
/// Lint data parsed from the Clippy source code.
375-
#[derive(Clone, PartialEq, Debug)]
375+
#[derive(Clone, PartialEq, Eq, Debug)]
376376
struct Lint {
377377
name: String,
378378
group: String,
@@ -414,7 +414,7 @@ impl Lint {
414414
}
415415
}
416416

417-
#[derive(Clone, PartialEq, Debug)]
417+
#[derive(Clone, PartialEq, Eq, Debug)]
418418
struct DeprecatedLint {
419419
name: String,
420420
reason: String,

clippy_lints/src/checked_conversions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ struct Conversion<'a> {
123123
}
124124

125125
/// The kind of conversion that is checked
126-
#[derive(Copy, Clone, Debug, PartialEq)]
126+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
127127
enum ConversionType {
128128
SignedToUnsigned,
129129
SignedToSigned,

clippy_lints/src/derive.rs

Lines changed: 70 additions & 2 deletions
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+
style,
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 {
@@ -179,6 +213,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
179213

180214
if is_automatically_derived {
181215
check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
216+
check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
182217
} else {
183218
check_copy_clone(cx, item, trait_ref, ty);
184219
}
@@ -419,3 +454,36 @@ 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>(cx: &LateContext<'tcx>, span: Span, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
460+
if_chain! {
461+
if let ty::Adt(adt, substs) = ty.kind();
462+
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
463+
if let Some(def_id) = trait_ref.trait_def_id();
464+
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
465+
if !implements_trait(cx, ty, eq_trait_def_id, substs);
466+
then {
467+
// If all of our fields implement `Eq`, we can implement `Eq` too
468+
for variant in adt.variants() {
469+
for field in &variant.fields {
470+
let ty = field.ty(cx.tcx, substs);
471+
472+
if !implements_trait(cx, ty, eq_trait_def_id, substs) {
473+
return;
474+
}
475+
}
476+
}
477+
478+
span_lint_and_sugg(
479+
cx,
480+
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
481+
span.ctxt().outer_expn_data().call_site,
482+
"you are deriving `PartialEq` and can implement `Eq`",
483+
"consider deriving `Eq` as well",
484+
"PartialEq, Eq".to_string(),
485+
Applicability::MachineApplicable,
486+
)
487+
}
488+
}
489+
}

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
4646
LintId::of(derivable_impls::DERIVABLE_IMPLS),
4747
LintId::of(derive::DERIVE_HASH_XOR_EQ),
4848
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
49+
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
4950
LintId::of(disallowed_methods::DISALLOWED_METHODS),
5051
LintId::of(disallowed_types::DISALLOWED_TYPES),
5152
LintId::of(doc::MISSING_SAFETY_DOC),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
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_style.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
1616
LintId::of(comparison_chain::COMPARISON_CHAIN),
1717
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
1818
LintId::of(dereference::NEEDLESS_BORROW),
19+
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
1920
LintId::of(disallowed_methods::DISALLOWED_METHODS),
2021
LintId::of(disallowed_types::DISALLOWED_TYPES),
2122
LintId::of(doc::MISSING_SAFETY_DOC),

clippy_lints/src/loops/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_span::symbol::{sym, Symbol};
1313
use rustc_typeck::hir_ty_to_ty;
1414
use std::iter::Iterator;
1515

16-
#[derive(Debug, PartialEq)]
16+
#[derive(Debug, PartialEq, Eq)]
1717
enum IncrementVisitorVarState {
1818
Initial, // Not examined yet
1919
IncrOnce, // Incremented exactly once, may be a loop counter

clippy_lints/src/methods/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2835,7 +2835,7 @@ const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
28352835
ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
28362836
];
28372837

2838-
#[derive(Clone, Copy, PartialEq, Debug)]
2838+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
28392839
enum SelfKind {
28402840
Value,
28412841
Ref,

clippy_lints/src/methods/str_splitn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ enum IterUsageKind {
271271
NextTuple,
272272
}
273273

274-
#[derive(Debug, PartialEq)]
274+
#[derive(Debug, PartialEq, Eq)]
275275
enum UnwrapKind {
276276
Unwrap,
277277
QuestionMark,

clippy_utils/src/numeric_literal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
22
use std::iter;
33

4-
#[derive(Debug, PartialEq, Copy, Clone)]
4+
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
55
pub enum Radix {
66
Binary,
77
Octal,

tests/ui/absurd-extreme-comparisons.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn main() {
3737

3838
use std::cmp::{Ordering, PartialEq, PartialOrd};
3939

40-
#[derive(PartialEq, PartialOrd)]
40+
#[derive(PartialEq, Eq, PartialOrd)]
4141
pub struct U(u64);
4242

4343
impl PartialEq<u32> for U {

tests/ui/assign_ops2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fn main() {
2424

2525
use std::ops::{Mul, MulAssign};
2626

27-
#[derive(Copy, Clone, Debug, PartialEq)]
27+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2828
pub struct Wrap(i64);
2929

3030
impl Mul<i64> for Wrap {

tests/ui/cmp_owned/asymmetric_partial_eq.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-rustfix
2-
#![allow(unused, clippy::redundant_clone)] // See #5700
2+
#![allow(unused, clippy::redundant_clone, clippy::derive_partial_eq_without_eq)] // See #5700
33

44
// Define the types in each module to avoid trait impls leaking between modules.
55
macro_rules! impl_types {

tests/ui/cmp_owned/asymmetric_partial_eq.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-rustfix
2-
#![allow(unused, clippy::redundant_clone)] // See #5700
2+
#![allow(unused, clippy::redundant_clone, clippy::derive_partial_eq_without_eq)] // See #5700
33

44
// Define the types in each module to avoid trait impls leaking between modules.
55
macro_rules! impl_types {

tests/ui/cmp_owned/with_suggestion.fixed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl ToOwned for Foo {
4545
}
4646
}
4747

48-
#[derive(PartialEq)]
48+
#[derive(PartialEq, Eq)]
4949
struct Bar;
5050

5151
impl PartialEq<Foo> for Bar {
@@ -61,7 +61,7 @@ impl std::borrow::Borrow<Foo> for Bar {
6161
}
6262
}
6363

64-
#[derive(PartialEq)]
64+
#[derive(PartialEq, Eq)]
6565
struct Baz;
6666

6767
impl ToOwned for Baz {

tests/ui/cmp_owned/with_suggestion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl ToOwned for Foo {
4545
}
4646
}
4747

48-
#[derive(PartialEq)]
48+
#[derive(PartialEq, Eq)]
4949
struct Bar;
5050

5151
impl PartialEq<Foo> for Bar {
@@ -61,7 +61,7 @@ impl std::borrow::Borrow<Foo> for Bar {
6161
}
6262
}
6363

64-
#[derive(PartialEq)]
64+
#[derive(PartialEq, Eq)]
6565
struct Baz;
6666

6767
impl ToOwned for Baz {

tests/ui/cmp_owned/without_suggestion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl ToOwned for Foo {
2626
}
2727
}
2828

29-
#[derive(PartialEq)]
29+
#[derive(PartialEq, Eq)]
3030
struct Baz;
3131

3232
impl ToOwned for Baz {
@@ -36,7 +36,7 @@ impl ToOwned for Baz {
3636
}
3737
}
3838

39-
#[derive(PartialEq)]
39+
#[derive(PartialEq, Eq)]
4040
struct Bar;
4141

4242
impl PartialEq<Foo> for Bar {

tests/ui/crashes/ice-6254.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// panicked at 'assertion failed: rows.iter().all(|r| r.len() == v.len())',
33
// compiler/rustc_mir_build/src/thir/pattern/_match.rs:2030:5
44

5+
#[allow(clippy::derive_partial_eq_without_eq)]
56
#[derive(PartialEq)]
67
struct Foo(i32);
78
const FOO_REF_REF: &&Foo = &&Foo(42);

tests/ui/crashes/ice-6254.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: to use a constant of type `Foo` in a pattern, `Foo` must be annotated with `#[derive(PartialEq, Eq)]`
2-
--> $DIR/ice-6254.rs:12:9
2+
--> $DIR/ice-6254.rs:13:9
33
|
44
LL | FOO_REF_REF => {},
55
| ^^^^^^^^^^^

tests/ui/derive_hash_xor_eq.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::derive_partial_eq_without_eq)]
2+
13
#[derive(PartialEq, Hash)]
24
struct Foo;
35

tests/ui/derive_hash_xor_eq.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
error: you are deriving `Hash` but have implemented `PartialEq` explicitly
2-
--> $DIR/derive_hash_xor_eq.rs:10:10
2+
--> $DIR/derive_hash_xor_eq.rs:12:10
33
|
44
LL | #[derive(Hash)]
55
| ^^^^
66
|
77
= note: `#[deny(clippy::derive_hash_xor_eq)]` on by default
88
note: `PartialEq` implemented here
9-
--> $DIR/derive_hash_xor_eq.rs:13:1
9+
--> $DIR/derive_hash_xor_eq.rs:15:1
1010
|
1111
LL | / impl PartialEq for Bar {
1212
LL | | fn eq(&self, _: &Bar) -> bool {
@@ -17,13 +17,13 @@ LL | | }
1717
= note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
1818

1919
error: you are deriving `Hash` but have implemented `PartialEq` explicitly
20-
--> $DIR/derive_hash_xor_eq.rs:19:10
20+
--> $DIR/derive_hash_xor_eq.rs:21:10
2121
|
2222
LL | #[derive(Hash)]
2323
| ^^^^
2424
|
2525
note: `PartialEq` implemented here
26-
--> $DIR/derive_hash_xor_eq.rs:22:1
26+
--> $DIR/derive_hash_xor_eq.rs:24:1
2727
|
2828
LL | / impl PartialEq<Baz> for Baz {
2929
LL | | fn eq(&self, _: &Baz) -> bool {
@@ -34,30 +34,30 @@ LL | | }
3434
= note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
3535

3636
error: you are implementing `Hash` explicitly but have derived `PartialEq`
37-
--> $DIR/derive_hash_xor_eq.rs:31:1
37+
--> $DIR/derive_hash_xor_eq.rs:33:1
3838
|
3939
LL | / impl std::hash::Hash for Bah {
4040
LL | | fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
4141
LL | | }
4242
| |_^
4343
|
4444
note: `PartialEq` implemented here
45-
--> $DIR/derive_hash_xor_eq.rs:28:10
45+
--> $DIR/derive_hash_xor_eq.rs:30:10
4646
|
4747
LL | #[derive(PartialEq)]
4848
| ^^^^^^^^^
4949
= note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
5050

5151
error: you are implementing `Hash` explicitly but have derived `PartialEq`
52-
--> $DIR/derive_hash_xor_eq.rs:49:5
52+
--> $DIR/derive_hash_xor_eq.rs:51:5
5353
|
5454
LL | / impl Hash for Foo3 {
5555
LL | | fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
5656
LL | | }
5757
| |_____^
5858
|
5959
note: `PartialEq` implemented here
60-
--> $DIR/derive_hash_xor_eq.rs:46:14
60+
--> $DIR/derive_hash_xor_eq.rs:48:14
6161
|
6262
LL | #[derive(PartialEq)]
6363
| ^^^^^^^^^

0 commit comments

Comments
 (0)