about summary refs log tree commit diff
diff options
context:
space:
mode:
authornsunderland1 <sunderland.nicholas@protonmail.com>2022-05-06 00:10:11 -0700
committernsunderland1 <sunderland.nicholas@protonmail.com>2022-05-09 22:13:39 -0700
commitfe84ff336055412f0df8a2e625eaf46c6701e574 (patch)
treed11078a4dd396ce2801b7c20b812e758b37fa4ca
parent77effb7bb2667a71dd9db8514cd20bbfc2c6f241 (diff)
downloadrust-fe84ff336055412f0df8a2e625eaf46c6701e574.tar.gz
rust-fe84ff336055412f0df8a2e625eaf46c6701e574.zip
New lint: [`derive_partial_eq_without_eq`]
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_dev/src/update_lints.rs6
-rw-r--r--clippy_lints/src/checked_conversions.rs2
-rw-r--r--clippy_lints/src/derive.rs72
-rw-r--r--clippy_lints/src/lib.register_all.rs1
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/lib.register_style.rs1
-rw-r--r--clippy_lints/src/loops/utils.rs2
-rw-r--r--clippy_lints/src/methods/mod.rs2
-rw-r--r--clippy_lints/src/methods/str_splitn.rs2
-rw-r--r--clippy_utils/src/numeric_literal.rs2
-rw-r--r--tests/ui/absurd-extreme-comparisons.rs2
-rw-r--r--tests/ui/assign_ops2.rs2
-rw-r--r--tests/ui/cmp_owned/asymmetric_partial_eq.fixed2
-rw-r--r--tests/ui/cmp_owned/asymmetric_partial_eq.rs2
-rw-r--r--tests/ui/cmp_owned/with_suggestion.fixed4
-rw-r--r--tests/ui/cmp_owned/with_suggestion.rs4
-rw-r--r--tests/ui/cmp_owned/without_suggestion.rs4
-rw-r--r--tests/ui/crashes/ice-6254.rs1
-rw-r--r--tests/ui/crashes/ice-6254.stderr2
-rw-r--r--tests/ui/derive_hash_xor_eq.rs2
-rw-r--r--tests/ui/derive_hash_xor_eq.stderr16
-rw-r--r--tests/ui/derive_partial_eq_without_eq.fixed98
-rw-r--r--tests/ui/derive_partial_eq_without_eq.rs98
-rw-r--r--tests/ui/derive_partial_eq_without_eq.stderr46
-rw-r--r--tests/ui/equatable_if_let.fixed2
-rw-r--r--tests/ui/equatable_if_let.rs2
-rw-r--r--tests/ui/unit_cmp.rs6
-rw-r--r--tests/ui/unit_cmp.stderr12
29 files changed, 359 insertions, 38 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 751f9fccd88..0608c360d30 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3350,6 +3350,7 @@ Released 2018-09-13
 [`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
 [`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
 [`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
+[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
 [`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
 [`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
 [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs
index 1a6a4336da2..e9cc4f29943 100644
--- a/clippy_dev/src/update_lints.rs
+++ b/clippy_dev/src/update_lints.rs
@@ -17,7 +17,7 @@ const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev u
 
 const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
 
-#[derive(Clone, Copy, PartialEq)]
+#[derive(Clone, Copy, PartialEq, Eq)]
 pub enum UpdateMode {
     Check,
     Change,
@@ -372,7 +372,7 @@ fn exit_with_failure() {
 }
 
 /// Lint data parsed from the Clippy source code.
-#[derive(Clone, PartialEq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug)]
 struct Lint {
     name: String,
     group: String,
@@ -414,7 +414,7 @@ impl Lint {
     }
 }
 
-#[derive(Clone, PartialEq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug)]
 struct DeprecatedLint {
     name: String,
     reason: String,
diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs
index e23428e216d..28c77ea40ef 100644
--- a/clippy_lints/src/checked_conversions.rs
+++ b/clippy_lints/src/checked_conversions.rs
@@ -123,7 +123,7 @@ struct Conversion<'a> {
 }
 
 /// The kind of conversion that is checked
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum ConversionType {
     SignedToUnsigned,
     SignedToSigned,
diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs
index 557e101494e..a4757ebd8c7 100644
--- a/clippy_lints/src/derive.rs
+++ b/clippy_lints/src/derive.rs
@@ -1,8 +1,9 @@
-use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::paths;
 use clippy_utils::ty::{implements_trait, is_copy};
 use clippy_utils::{is_automatically_derived, is_lint_allowed, match_def_path};
 use if_chain::if_chain;
+use rustc_errors::Applicability;
 use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
 use rustc_hir::{
     BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
@@ -156,11 +157,44 @@ declare_clippy_lint! {
     "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for types that derive `PartialEq` and could implement `Eq`.
+    ///
+    /// ### Why is this bad?
+    /// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
+    /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
+    /// in APIs that require `Eq` types. It also allows structs containing `T` to derive
+    /// `Eq` themselves.
+    ///
+    /// ### Example
+    /// ```rust
+    /// #[derive(PartialEq)]
+    /// struct Foo {
+    ///     i_am_eq: i32,
+    ///     i_am_eq_too: Vec<String>,
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// #[derive(PartialEq, Eq)]
+    /// struct Foo {
+    ///     i_am_eq: i32,
+    ///     i_am_eq_too: Vec<String>,
+    /// }
+    /// ```
+    #[clippy::version = "1.62.0"]
+    pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
+    style,
+    "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
+}
+
 declare_lint_pass!(Derive => [
     EXPL_IMPL_CLONE_ON_COPY,
     DERIVE_HASH_XOR_EQ,
     DERIVE_ORD_XOR_PARTIAL_ORD,
-    UNSAFE_DERIVE_DESERIALIZE
+    UNSAFE_DERIVE_DESERIALIZE,
+    DERIVE_PARTIAL_EQ_WITHOUT_EQ
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Derive {
@@ -179,6 +213,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
 
             if is_automatically_derived {
                 check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
+                check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
             } else {
                 check_copy_clone(cx, item, trait_ref, ty);
             }
@@ -419,3 +454,36 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
         self.cx.tcx.hir()
     }
 }
+
+/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
+fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
+    if_chain! {
+        if let ty::Adt(adt, substs) = ty.kind();
+        if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
+        if let Some(def_id) = trait_ref.trait_def_id();
+        if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
+        if !implements_trait(cx, ty, eq_trait_def_id, substs);
+        then {
+            // If all of our fields implement `Eq`, we can implement `Eq` too
+            for variant in adt.variants() {
+                for field in &variant.fields {
+                    let ty = field.ty(cx.tcx, substs);
+
+                    if !implements_trait(cx, ty, eq_trait_def_id, substs) {
+                        return;
+                    }
+                }
+            }
+
+            span_lint_and_sugg(
+                cx,
+                DERIVE_PARTIAL_EQ_WITHOUT_EQ,
+                span.ctxt().outer_expn_data().call_site,
+                "you are deriving `PartialEq` and can implement `Eq`",
+                "consider deriving `Eq` as well",
+                "PartialEq, Eq".to_string(),
+                Applicability::MachineApplicable,
+            )
+        }
+    }
+}
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs
index e68718f9fdf..0f43b320f76 100644
--- a/clippy_lints/src/lib.register_all.rs
+++ b/clippy_lints/src/lib.register_all.rs
@@ -46,6 +46,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(derivable_impls::DERIVABLE_IMPLS),
     LintId::of(derive::DERIVE_HASH_XOR_EQ),
     LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
+    LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
     LintId::of(disallowed_methods::DISALLOWED_METHODS),
     LintId::of(disallowed_types::DISALLOWED_TYPES),
     LintId::of(doc::MISSING_SAFETY_DOC),
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index 5768edc5018..4e302b10a0f 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -113,6 +113,7 @@ store.register_lints(&[
     derivable_impls::DERIVABLE_IMPLS,
     derive::DERIVE_HASH_XOR_EQ,
     derive::DERIVE_ORD_XOR_PARTIAL_ORD,
+    derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ,
     derive::EXPL_IMPL_CLONE_ON_COPY,
     derive::UNSAFE_DERIVE_DESERIALIZE,
     disallowed_methods::DISALLOWED_METHODS,
diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs
index d183c0449cd..62f26d821a0 100644
--- a/clippy_lints/src/lib.register_style.rs
+++ b/clippy_lints/src/lib.register_style.rs
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
     LintId::of(comparison_chain::COMPARISON_CHAIN),
     LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
     LintId::of(dereference::NEEDLESS_BORROW),
+    LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
     LintId::of(disallowed_methods::DISALLOWED_METHODS),
     LintId::of(disallowed_types::DISALLOWED_TYPES),
     LintId::of(doc::MISSING_SAFETY_DOC),
diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs
index 772d251b620..4801a84eb92 100644
--- a/clippy_lints/src/loops/utils.rs
+++ b/clippy_lints/src/loops/utils.rs
@@ -13,7 +13,7 @@ use rustc_span::symbol::{sym, Symbol};
 use rustc_typeck::hir_ty_to_ty;
 use std::iter::Iterator;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Eq)]
 enum IncrementVisitorVarState {
     Initial,  // Not examined yet
     IncrOnce, // Incremented exactly once, may be a loop counter
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index e452614ce17..cfeee4d0c70 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -2835,7 +2835,7 @@ const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
     ShouldImplTraitCase::new("std::ops::Sub", "sub",  2,  FN_HEADER,  SelfKind::Value,  OutType::Any, true),
 ];
 
-#[derive(Clone, Copy, PartialEq, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 enum SelfKind {
     Value,
     Ref,
diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs
index fc375763542..90651a6ba04 100644
--- a/clippy_lints/src/methods/str_splitn.rs
+++ b/clippy_lints/src/methods/str_splitn.rs
@@ -271,7 +271,7 @@ enum IterUsageKind {
     NextTuple,
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Eq)]
 enum UnwrapKind {
     Unwrap,
     QuestionMark,
diff --git a/clippy_utils/src/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs
index b92d42e8323..3fb5415ce02 100644
--- a/clippy_utils/src/numeric_literal.rs
+++ b/clippy_utils/src/numeric_literal.rs
@@ -1,7 +1,7 @@
 use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
 use std::iter;
 
-#[derive(Debug, PartialEq, Copy, Clone)]
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
 pub enum Radix {
     Binary,
     Octal,
diff --git a/tests/ui/absurd-extreme-comparisons.rs b/tests/ui/absurd-extreme-comparisons.rs
index d205b383d1f..f682b280c1b 100644
--- a/tests/ui/absurd-extreme-comparisons.rs
+++ b/tests/ui/absurd-extreme-comparisons.rs
@@ -37,7 +37,7 @@ fn main() {
 
 use std::cmp::{Ordering, PartialEq, PartialOrd};
 
-#[derive(PartialEq, PartialOrd)]
+#[derive(PartialEq, Eq, PartialOrd)]
 pub struct U(u64);
 
 impl PartialEq<u32> for U {
diff --git a/tests/ui/assign_ops2.rs b/tests/ui/assign_ops2.rs
index 4703a8c7777..f6d3a8fa3f0 100644
--- a/tests/ui/assign_ops2.rs
+++ b/tests/ui/assign_ops2.rs
@@ -24,7 +24,7 @@ fn main() {
 
 use std::ops::{Mul, MulAssign};
 
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct Wrap(i64);
 
 impl Mul<i64> for Wrap {
diff --git a/tests/ui/cmp_owned/asymmetric_partial_eq.fixed b/tests/ui/cmp_owned/asymmetric_partial_eq.fixed
index 3305ac9bf8b..abd059c2308 100644
--- a/tests/ui/cmp_owned/asymmetric_partial_eq.fixed
+++ b/tests/ui/cmp_owned/asymmetric_partial_eq.fixed
@@ -1,5 +1,5 @@
 // run-rustfix
-#![allow(unused, clippy::redundant_clone)] // See #5700
+#![allow(unused, clippy::redundant_clone, clippy::derive_partial_eq_without_eq)] // See #5700
 
 // Define the types in each module to avoid trait impls leaking between modules.
 macro_rules! impl_types {
diff --git a/tests/ui/cmp_owned/asymmetric_partial_eq.rs b/tests/ui/cmp_owned/asymmetric_partial_eq.rs
index 88bc2f51dd6..020ef5f840b 100644
--- a/tests/ui/cmp_owned/asymmetric_partial_eq.rs
+++ b/tests/ui/cmp_owned/asymmetric_partial_eq.rs
@@ -1,5 +1,5 @@
 // run-rustfix
-#![allow(unused, clippy::redundant_clone)] // See #5700
+#![allow(unused, clippy::redundant_clone, clippy::derive_partial_eq_without_eq)] // See #5700
 
 // Define the types in each module to avoid trait impls leaking between modules.
 macro_rules! impl_types {
diff --git a/tests/ui/cmp_owned/with_suggestion.fixed b/tests/ui/cmp_owned/with_suggestion.fixed
index 05fb96339e3..b28c4378e33 100644
--- a/tests/ui/cmp_owned/with_suggestion.fixed
+++ b/tests/ui/cmp_owned/with_suggestion.fixed
@@ -45,7 +45,7 @@ impl ToOwned for Foo {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Eq)]
 struct Bar;
 
 impl PartialEq<Foo> for Bar {
@@ -61,7 +61,7 @@ impl std::borrow::Borrow<Foo> for Bar {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Eq)]
 struct Baz;
 
 impl ToOwned for Baz {
diff --git a/tests/ui/cmp_owned/with_suggestion.rs b/tests/ui/cmp_owned/with_suggestion.rs
index 0a02825ed82..c1089010fe1 100644
--- a/tests/ui/cmp_owned/with_suggestion.rs
+++ b/tests/ui/cmp_owned/with_suggestion.rs
@@ -45,7 +45,7 @@ impl ToOwned for Foo {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Eq)]
 struct Bar;
 
 impl PartialEq<Foo> for Bar {
@@ -61,7 +61,7 @@ impl std::borrow::Borrow<Foo> for Bar {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Eq)]
 struct Baz;
 
 impl ToOwned for Baz {
diff --git a/tests/ui/cmp_owned/without_suggestion.rs b/tests/ui/cmp_owned/without_suggestion.rs
index f44a3901fb4..738d082339a 100644
--- a/tests/ui/cmp_owned/without_suggestion.rs
+++ b/tests/ui/cmp_owned/without_suggestion.rs
@@ -26,7 +26,7 @@ impl ToOwned for Foo {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Eq)]
 struct Baz;
 
 impl ToOwned for Baz {
@@ -36,7 +36,7 @@ impl ToOwned for Baz {
     }
 }
 
-#[derive(PartialEq)]
+#[derive(PartialEq, Eq)]
 struct Bar;
 
 impl PartialEq<Foo> for Bar {
diff --git a/tests/ui/crashes/ice-6254.rs b/tests/ui/crashes/ice-6254.rs
index c19eca43884..a2a60a16915 100644
--- a/tests/ui/crashes/ice-6254.rs
+++ b/tests/ui/crashes/ice-6254.rs
@@ -2,6 +2,7 @@
 // panicked at 'assertion failed: rows.iter().all(|r| r.len() == v.len())',
 // compiler/rustc_mir_build/src/thir/pattern/_match.rs:2030:5
 
+#[allow(clippy::derive_partial_eq_without_eq)]
 #[derive(PartialEq)]
 struct Foo(i32);
 const FOO_REF_REF: &&Foo = &&Foo(42);
diff --git a/tests/ui/crashes/ice-6254.stderr b/tests/ui/crashes/ice-6254.stderr
index 95ebf23d818..f37ab2e9b0c 100644
--- a/tests/ui/crashes/ice-6254.stderr
+++ b/tests/ui/crashes/ice-6254.stderr
@@ -1,5 +1,5 @@
 error: to use a constant of type `Foo` in a pattern, `Foo` must be annotated with `#[derive(PartialEq, Eq)]`
-  --> $DIR/ice-6254.rs:12:9
+  --> $DIR/ice-6254.rs:13:9
    |
 LL |         FOO_REF_REF => {},
    |         ^^^^^^^^^^^
diff --git a/tests/ui/derive_hash_xor_eq.rs b/tests/ui/derive_hash_xor_eq.rs
index 10abe22d53e..813ddc56646 100644
--- a/tests/ui/derive_hash_xor_eq.rs
+++ b/tests/ui/derive_hash_xor_eq.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::derive_partial_eq_without_eq)]
+
 #[derive(PartialEq, Hash)]
 struct Foo;
 
diff --git a/tests/ui/derive_hash_xor_eq.stderr b/tests/ui/derive_hash_xor_eq.stderr
index b383072ca4d..e5184bd1407 100644
--- a/tests/ui/derive_hash_xor_eq.stderr
+++ b/tests/ui/derive_hash_xor_eq.stderr
@@ -1,12 +1,12 @@
 error: you are deriving `Hash` but have implemented `PartialEq` explicitly
-  --> $DIR/derive_hash_xor_eq.rs:10:10
+  --> $DIR/derive_hash_xor_eq.rs:12:10
    |
 LL | #[derive(Hash)]
    |          ^^^^
    |
    = note: `#[deny(clippy::derive_hash_xor_eq)]` on by default
 note: `PartialEq` implemented here
-  --> $DIR/derive_hash_xor_eq.rs:13:1
+  --> $DIR/derive_hash_xor_eq.rs:15:1
    |
 LL | / impl PartialEq for Bar {
 LL | |     fn eq(&self, _: &Bar) -> bool {
@@ -17,13 +17,13 @@ LL | | }
    = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: you are deriving `Hash` but have implemented `PartialEq` explicitly
-  --> $DIR/derive_hash_xor_eq.rs:19:10
+  --> $DIR/derive_hash_xor_eq.rs:21:10
    |
 LL | #[derive(Hash)]
    |          ^^^^
    |
 note: `PartialEq` implemented here
-  --> $DIR/derive_hash_xor_eq.rs:22:1
+  --> $DIR/derive_hash_xor_eq.rs:24:1
    |
 LL | / impl PartialEq<Baz> for Baz {
 LL | |     fn eq(&self, _: &Baz) -> bool {
@@ -34,7 +34,7 @@ LL | | }
    = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: you are implementing `Hash` explicitly but have derived `PartialEq`
-  --> $DIR/derive_hash_xor_eq.rs:31:1
+  --> $DIR/derive_hash_xor_eq.rs:33:1
    |
 LL | / impl std::hash::Hash for Bah {
 LL | |     fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
@@ -42,14 +42,14 @@ LL | | }
    | |_^
    |
 note: `PartialEq` implemented here
-  --> $DIR/derive_hash_xor_eq.rs:28:10
+  --> $DIR/derive_hash_xor_eq.rs:30:10
    |
 LL | #[derive(PartialEq)]
    |          ^^^^^^^^^
    = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: you are implementing `Hash` explicitly but have derived `PartialEq`
-  --> $DIR/derive_hash_xor_eq.rs:49:5
+  --> $DIR/derive_hash_xor_eq.rs:51:5
    |
 LL | /     impl Hash for Foo3 {
 LL | |         fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
@@ -57,7 +57,7 @@ LL | |     }
    | |_____^
    |
 note: `PartialEq` implemented here
-  --> $DIR/derive_hash_xor_eq.rs:46:14
+  --> $DIR/derive_hash_xor_eq.rs:48:14
    |
 LL |     #[derive(PartialEq)]
    |              ^^^^^^^^^
diff --git a/tests/ui/derive_partial_eq_without_eq.fixed b/tests/ui/derive_partial_eq_without_eq.fixed
new file mode 100644
index 00000000000..7d4d1b3b649
--- /dev/null
+++ b/tests/ui/derive_partial_eq_without_eq.fixed
@@ -0,0 +1,98 @@
+// run-rustfix
+
+#![allow(unused)]
+#![warn(clippy::derive_partial_eq_without_eq)]
+
+// Don't warn on structs that aren't PartialEq
+struct NotPartialEq {
+    foo: u32,
+    bar: String,
+}
+
+// Eq can be derived but is missing
+#[derive(Debug, PartialEq, Eq)]
+struct MissingEq {
+    foo: u32,
+    bar: String,
+}
+
+// Eq is derived
+#[derive(PartialEq, Eq)]
+struct NotMissingEq {
+    foo: u32,
+    bar: String,
+}
+
+// Eq is manually implemented
+#[derive(PartialEq)]
+struct ManualEqImpl {
+    foo: u32,
+    bar: String,
+}
+
+impl Eq for ManualEqImpl {}
+
+// Cannot be Eq because f32 isn't Eq
+#[derive(PartialEq)]
+struct CannotBeEq {
+    foo: u32,
+    bar: f32,
+}
+
+// Don't warn if PartialEq is manually implemented
+struct ManualPartialEqImpl {
+    foo: u32,
+    bar: String,
+}
+
+impl PartialEq for ManualPartialEqImpl {
+    fn eq(&self, other: &Self) -> bool {
+        self.foo == other.foo && self.bar == other.bar
+    }
+}
+
+// Generic fields should be properly checked for Eq-ness
+#[derive(PartialEq)]
+struct GenericNotEq<T: Eq, U: PartialEq> {
+    foo: T,
+    bar: U,
+}
+
+#[derive(PartialEq, Eq)]
+struct GenericEq<T: Eq, U: Eq> {
+    foo: T,
+    bar: U,
+}
+
+#[derive(PartialEq, Eq)]
+struct TupleStruct(u32);
+
+#[derive(PartialEq, Eq)]
+struct GenericTupleStruct<T: Eq>(T);
+
+#[derive(PartialEq)]
+struct TupleStructNotEq(f32);
+
+#[derive(PartialEq, Eq)]
+enum Enum {
+    Foo(u32),
+    Bar { a: String, b: () },
+}
+
+#[derive(PartialEq, Eq)]
+enum GenericEnum<T: Eq, U: Eq, V: Eq> {
+    Foo(T),
+    Bar { a: U, b: V },
+}
+
+#[derive(PartialEq)]
+enum EnumNotEq {
+    Foo(u32),
+    Bar { a: String, b: f32 },
+}
+
+// Ensure that rustfix works properly when `PartialEq` has other derives on either side
+#[derive(Debug, PartialEq, Eq, Clone)]
+struct RustFixWithOtherDerives;
+
+fn main() {}
diff --git a/tests/ui/derive_partial_eq_without_eq.rs b/tests/ui/derive_partial_eq_without_eq.rs
new file mode 100644
index 00000000000..ab4e1df1ca4
--- /dev/null
+++ b/tests/ui/derive_partial_eq_without_eq.rs
@@ -0,0 +1,98 @@
+// run-rustfix
+
+#![allow(unused)]
+#![warn(clippy::derive_partial_eq_without_eq)]
+
+// Don't warn on structs that aren't PartialEq
+struct NotPartialEq {
+    foo: u32,
+    bar: String,
+}
+
+// Eq can be derived but is missing
+#[derive(Debug, PartialEq)]
+struct MissingEq {
+    foo: u32,
+    bar: String,
+}
+
+// Eq is derived
+#[derive(PartialEq, Eq)]
+struct NotMissingEq {
+    foo: u32,
+    bar: String,
+}
+
+// Eq is manually implemented
+#[derive(PartialEq)]
+struct ManualEqImpl {
+    foo: u32,
+    bar: String,
+}
+
+impl Eq for ManualEqImpl {}
+
+// Cannot be Eq because f32 isn't Eq
+#[derive(PartialEq)]
+struct CannotBeEq {
+    foo: u32,
+    bar: f32,
+}
+
+// Don't warn if PartialEq is manually implemented
+struct ManualPartialEqImpl {
+    foo: u32,
+    bar: String,
+}
+
+impl PartialEq for ManualPartialEqImpl {
+    fn eq(&self, other: &Self) -> bool {
+        self.foo == other.foo && self.bar == other.bar
+    }
+}
+
+// Generic fields should be properly checked for Eq-ness
+#[derive(PartialEq)]
+struct GenericNotEq<T: Eq, U: PartialEq> {
+    foo: T,
+    bar: U,
+}
+
+#[derive(PartialEq)]
+struct GenericEq<T: Eq, U: Eq> {
+    foo: T,
+    bar: U,
+}
+
+#[derive(PartialEq)]
+struct TupleStruct(u32);
+
+#[derive(PartialEq)]
+struct GenericTupleStruct<T: Eq>(T);
+
+#[derive(PartialEq)]
+struct TupleStructNotEq(f32);
+
+#[derive(PartialEq)]
+enum Enum {
+    Foo(u32),
+    Bar { a: String, b: () },
+}
+
+#[derive(PartialEq)]
+enum GenericEnum<T: Eq, U: Eq, V: Eq> {
+    Foo(T),
+    Bar { a: U, b: V },
+}
+
+#[derive(PartialEq)]
+enum EnumNotEq {
+    Foo(u32),
+    Bar { a: String, b: f32 },
+}
+
+// Ensure that rustfix works properly when `PartialEq` has other derives on either side
+#[derive(Debug, PartialEq, Clone)]
+struct RustFixWithOtherDerives;
+
+fn main() {}
diff --git a/tests/ui/derive_partial_eq_without_eq.stderr b/tests/ui/derive_partial_eq_without_eq.stderr
new file mode 100644
index 00000000000..bf55165890a
--- /dev/null
+++ b/tests/ui/derive_partial_eq_without_eq.stderr
@@ -0,0 +1,46 @@
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:13:17
+   |
+LL | #[derive(Debug, PartialEq)]
+   |                 ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+   |
+   = note: `-D clippy::derive-partial-eq-without-eq` implied by `-D warnings`
+
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:61:10
+   |
+LL | #[derive(PartialEq)]
+   |          ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:67:10
+   |
+LL | #[derive(PartialEq)]
+   |          ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:70:10
+   |
+LL | #[derive(PartialEq)]
+   |          ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:76:10
+   |
+LL | #[derive(PartialEq)]
+   |          ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:82:10
+   |
+LL | #[derive(PartialEq)]
+   |          ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+
+error: you are deriving `PartialEq` and can implement `Eq`
+  --> $DIR/derive_partial_eq_without_eq.rs:95:17
+   |
+LL | #[derive(Debug, PartialEq, Clone)]
+   |                 ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq`
+
+error: aborting due to 7 previous errors
+
diff --git a/tests/ui/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed
index 88918d9671e..47bf25e409b 100644
--- a/tests/ui/equatable_if_let.fixed
+++ b/tests/ui/equatable_if_let.fixed
@@ -1,6 +1,6 @@
 // run-rustfix
 
-#![allow(unused_variables, dead_code)]
+#![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)]
 #![warn(clippy::equatable_if_let)]
 
 use std::cmp::Ordering;
diff --git a/tests/ui/equatable_if_let.rs b/tests/ui/equatable_if_let.rs
index 9a7ab75ef45..d498bca2455 100644
--- a/tests/ui/equatable_if_let.rs
+++ b/tests/ui/equatable_if_let.rs
@@ -1,6 +1,6 @@
 // run-rustfix
 
-#![allow(unused_variables, dead_code)]
+#![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)]
 #![warn(clippy::equatable_if_let)]
 
 use std::cmp::Ordering;
diff --git a/tests/ui/unit_cmp.rs b/tests/ui/unit_cmp.rs
index 8d3a4eed82e..3d271104361 100644
--- a/tests/ui/unit_cmp.rs
+++ b/tests/ui/unit_cmp.rs
@@ -1,5 +1,9 @@
 #![warn(clippy::unit_cmp)]
-#![allow(clippy::no_effect, clippy::unnecessary_operation)]
+#![allow(
+    clippy::no_effect,
+    clippy::unnecessary_operation,
+    clippy::derive_partial_eq_without_eq
+)]
 
 #[derive(PartialEq)]
 pub struct ContainsUnit(()); // should be fine
diff --git a/tests/ui/unit_cmp.stderr b/tests/ui/unit_cmp.stderr
index 824506a4257..41cf19ae685 100644
--- a/tests/ui/unit_cmp.stderr
+++ b/tests/ui/unit_cmp.stderr
@@ -1,5 +1,5 @@
 error: ==-comparison of unit values detected. This will always be true
-  --> $DIR/unit_cmp.rs:12:8
+  --> $DIR/unit_cmp.rs:16:8
    |
 LL |       if {
    |  ________^
@@ -12,7 +12,7 @@ LL | |     } {}
    = note: `-D clippy::unit-cmp` implied by `-D warnings`
 
 error: >-comparison of unit values detected. This will always be false
-  --> $DIR/unit_cmp.rs:18:8
+  --> $DIR/unit_cmp.rs:22:8
    |
 LL |       if {
    |  ________^
@@ -23,7 +23,7 @@ LL | |     } {}
    | |_____^
 
 error: `assert_eq` of unit values detected. This will always succeed
-  --> $DIR/unit_cmp.rs:24:5
+  --> $DIR/unit_cmp.rs:28:5
    |
 LL | /     assert_eq!(
 LL | |         {
@@ -35,7 +35,7 @@ LL | |     );
    | |_____^
 
 error: `debug_assert_eq` of unit values detected. This will always succeed
-  --> $DIR/unit_cmp.rs:32:5
+  --> $DIR/unit_cmp.rs:36:5
    |
 LL | /     debug_assert_eq!(
 LL | |         {
@@ -47,7 +47,7 @@ LL | |     );
    | |_____^
 
 error: `assert_ne` of unit values detected. This will always fail
-  --> $DIR/unit_cmp.rs:41:5
+  --> $DIR/unit_cmp.rs:45:5
    |
 LL | /     assert_ne!(
 LL | |         {
@@ -59,7 +59,7 @@ LL | |     );
    | |_____^
 
 error: `debug_assert_ne` of unit values detected. This will always fail
-  --> $DIR/unit_cmp.rs:49:5
+  --> $DIR/unit_cmp.rs:53:5
    |
 LL | /     debug_assert_ne!(
 LL | |         {