Repository: JelteF/derive_more
Pull request #473 (2025-05-26 18:18:02)

Bootstrap `derive(PartialEq)` (#163)
Part of #163

## Synopsis

This PR is the initial part of #163 and adds only structural expansion of `derive(PartialEq)`.

## Solution

Mirrors the `PartialEq` expansion of `std`, but applies trait bounds for generics correctly.

## Checklist

- [x] Documentation is updated
- [x] Tests are added/updated
- [x] [CHANGELOG entry](/CHANGELOG.md) is added


tyranron: ping @JelteF 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37cd054..6cf9a95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
   ([#459](https://github.com/JelteF/derive_more/pull/459))
 - Support structs with no fields in `FromStr` derive.
   ([#469](https://github.com/JelteF/derive_more/pull/469))
+- Add `PartialEq` derive similar to `std`'s one, but considering generics correctly.
+  ([#473](https://github.com/JelteF/derive_more/pull/473))
 
 ### Changed
 
diff --git a/Cargo.toml b/Cargo.toml
index 9d56ff9..cb74b91 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,6 +61,7 @@ debug = ["derive_more-impl/debug"]
 deref = ["derive_more-impl/deref"]
 deref_mut = ["derive_more-impl/deref_mut"]
 display = ["derive_more-impl/display"]
+eq = ["derive_more-impl/eq"]
 error = ["derive_more-impl/error"]
 from = ["derive_more-impl/from"]
 from_str = ["derive_more-impl/from_str"]
@@ -88,6 +89,7 @@ full = [
     "deref",
     "deref_mut",
     "display",
+    "eq",
     "error",
     "from",
     "from_str",
@@ -213,6 +215,11 @@ name = "not"
 path = "tests/not.rs"
 required-features = ["not"]
 
+[[test]]
+name = "partial_eq"
+path = "tests/partial_eq.rs"
+required-features = ["eq"]
+
 [[test]]
 name = "sum"
 path = "tests/sum.rs"
diff --git a/README.md b/README.md
index 06e484b..3177d31 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,7 @@ These are traits that can be used for operator overloading.
    `BitOrAssign` and `BitXorAssign`
 10. [`MulAssign`-like], contains `MulAssign`, `DivAssign`, `RemAssign`,
     `ShrAssign` and `ShlAssign`
+11. [`PartialEq`]
 
 
 ### Static methods
@@ -260,6 +261,7 @@ Changing [MSRV] (minimum supported Rust version) of this crate is treated as a *
 [`DerefMut`]: https://docs.rs/derive_more/latest/derive_more/derive.DerefMut.html
 [`AddAssign`-like]: https://docs.rs/derive_more/latest/derive_more/derive.AddAssign.html
 [`MulAssign`-like]: https://docs.rs/derive_more/latest/derive_more/derive.MulAssign.html
+[`PartialEq`]: https://docs.rs/derive_more/latest/derive_more/derive.PartialEq.html
 
 [`Constructor`]: https://docs.rs/derive_more/latest/derive_more/derive.Constructor.html
 [`IsVariant`]: https://docs.rs/derive_more/latest/derive_more/derive.IsVariant.html
diff --git a/impl/Cargo.toml b/impl/Cargo.toml
index 53a417c..1d1ca52 100644
--- a/impl/Cargo.toml
+++ b/impl/Cargo.toml
@@ -57,6 +57,7 @@ debug = ["syn/extra-traits", "dep:unicode-xid"]
 deref = []
 deref_mut = []
 display = ["syn/extra-traits", "dep:unicode-xid", "dep:convert_case"]
+eq = ["syn/extra-traits", "syn/visit"]
 error = ["syn/extra-traits"]
 from = ["syn/extra-traits"]
 from_str = ["dep:convert_case"]
@@ -83,6 +84,7 @@ full = [
     "deref",
     "deref_mut",
     "display",
+    "eq",
     "error",
     "from",
     "from_str",
diff --git a/impl/doc/eq.md b/impl/doc/eq.md
new file mode 100644
index 0000000..c1954e7
--- /dev/null
+++ b/impl/doc/eq.md
@@ -0,0 +1,147 @@
+# Using `#[derive(PartialEq)]`
+
+Deriving `PartialEq` works by checking whether two values are equal according to
+their type structure.
+
+
+
+
+## Structural equality
+
+Deriving `PartialEq` for enums/structs works in a similar way to the one in `std`,
+by comparing all the available fields, but, in the contrast, does not overconstrain
+generic parameters.
+
+
+### Structs
+
+For structs all the available fields are checked for equality.
+
+```rust
+# use std::marker::PhantomData;
+# use derive_more::PartialEq;
+#
+trait Trait {
+    type Assoc;
+}
+impl<T: ?Sized> Trait for T {
+    type Assoc = u8;
+}
+
+#[derive(Debug, PartialEq)]
+struct Foo<A, B, C: Trait + ?Sized> {
+    a: A,
+    b: PhantomData<B>,
+    c: C::Assoc,
+}
+
+#[derive(Debug)]
+struct NoEq;
+
+assert_eq!(Foo::<_, NoEq, NoEq> { a: 3, b: PhantomData, c: 0 }, Foo { a: 3, b: PhantomData, c: 0 });
+assert_ne!(Foo::<_, NoEq, NoEq> { a: 3, b: PhantomData, c: 0 }, Foo { a: 0, b: PhantomData, c: 3 });
+```
+This generates code equivalent to:
+```rust
+# use std::marker::PhantomData;
+#
+# trait Trait {
+#     type Assoc;
+# }
+# impl<T: ?Sized> Trait for T {
+#     type Assoc = u8;
+# }
+#
+# struct Foo<A, B, C: Trait + ?Sized> {
+#     a: A,
+#     b: PhantomData<B>,
+#     c: C::Assoc,
+# }
+#
+impl<A, B, C: Trait + ?Sized> PartialEq for Foo<A, B, C>
+where
+    A: PartialEq,
+    PhantomData<B>: PartialEq, // `B: PartialEq` is generated by `std` instead
+    C::Assoc: PartialEq,       // `C: PartialEq` is generated by `std` instead
+{
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self { a: self_0, b: self_1, c: self_2 }, Self { a: other_0, b: other_1, c: other_2 }) => {
+                self_0 == other_0 && self_1 == other_1 && self_2 == other_2
+            }
+        }
+    }
+}
+```
+
+
+### Enums
+
+For enums the first check is whether these two values represent the same variant,
+and after that we check fields equality.
+
+```rust
+# use std::marker::PhantomData;
+# use derive_more::PartialEq;
+#
+# trait Trait {
+#     type Assoc;
+# }
+# impl<T: ?Sized> Trait for T {
+#     type Assoc = u8;
+# }
+#
+#[derive(Debug, PartialEq)]
+enum Foo<A, B, C: Trait + ?Sized> {
+    A(A),
+    B { b: PhantomData<B> },
+    C(C::Assoc),
+}
+#
+# #[derive(Debug)]
+# struct NoEq;
+
+assert_eq!(Foo::<_, NoEq, NoEq>::A(3), Foo::A(3));
+assert_ne!(Foo::<_, NoEq, NoEq>::A(3), Foo::A(0));
+
+assert_eq!(Foo::<u16, NoEq, NoEq>::B { b: PhantomData }, Foo::B { b: PhantomData });
+
+assert_eq!(Foo::<i32, NoEq, NoEq>::C(3), Foo::C(3));
+assert_ne!(Foo::<i32, NoEq, NoEq>::C(3), Foo::C(0));
+```
+This generates code equivalent to:
+```rust
+# use std::marker::PhantomData;
+#
+# trait Trait {
+#     type Assoc;
+# }
+# impl<T: ?Sized> Trait for T {
+#     type Assoc = u8;
+# }
+#
+# enum Foo<A, B, C: Trait + ?Sized> {
+#     A(A),
+#     B { b: PhantomData<B> },
+#     C(C::Assoc),
+# }
+#
+impl<A, B, C: Trait + ?Sized> PartialEq for Foo<A, B, C>
+where
+    A: PartialEq,
+    PhantomData<B>: PartialEq, // `B: PartialEq` is generated by `std` instead
+    C::Assoc: PartialEq,       // `C: PartialEq` is generated by `std` instead
+{
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        std::mem::discriminant(self) == std::mem::discriminant(other) &&
+            match (self, other) {
+                (Self::A(self_0), Self::A(other_0)) => { self_0 == other_0 }
+                (Self::B { b: self_0 }, Self::B { b: other_0 }) => { self_0 == other_0 }
+                (Self::C(self_0), Self::C(other_0)) => { self_0 == other_0 }
+                _ => unsafe { std::hint::unreachable_unchecked() },
+            }
+    }
+}
+```
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 8a40cad..f01d781 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -57,6 +57,8 @@ mod mul_like;
 mod not_like;
 #[cfg(any(feature = "debug", feature = "display"))]
 pub(crate) mod parsing;
+#[cfg(feature = "eq")]
+mod partial_eq;
 #[cfg(feature = "sum")]
 mod sum_like;
 #[cfg(feature = "try_from")]
@@ -184,6 +186,8 @@ create_derive!(
 );
 create_derive!("display", fmt::display, Pointer, pointer_derive, pointer);
 
+create_derive!("eq", partial_eq, PartialEq, partial_eq_derive);
+
 create_derive!("error", error, Error, error_derive, error);
 
 create_derive!("from", from, From, from_derive, from);
diff --git a/impl/src/partial_eq.rs b/impl/src/partial_eq.rs
new file mode 100644
index 0000000..53a32cc
--- /dev/null
+++ b/impl/src/partial_eq.rs
@@ -0,0 +1,192 @@
+//! Implementation of a [`PartialEq`] derive macro.
+
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{parse_quote, spanned::Spanned as _, token};
+
+use crate::utils::GenericsSearch;
+
+/// Expands a [`PartialEq`] derive macro.
+pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
+    Ok(StructuralExpansion::try_from(input)?.into_token_stream())
+}
+
+/// Expansion of a macro for generating a structural [`PartialEq`] implementation of an enum or a
+/// struct.
+struct StructuralExpansion<'i> {
+    /// [`syn::Ident`] and [`syn::Generics`] of the enum/struct.
+    ///
+    /// [`syn::Ident`]: struct@syn::Ident
+    self_ty: (&'i syn::Ident, &'i syn::Generics),
+
+    /// [`syn::Fields`] of the enum/struct to be compared in this [`StructuralExpansion`].
+    variants: Vec<(Option<&'i syn::Ident>, &'i syn::Fields)>,
+}
+
+impl<'i> TryFrom<&'i syn::DeriveInput> for StructuralExpansion<'i> {
+    type Error = syn::Error;
+
+    fn try_from(input: &'i syn::DeriveInput) -> syn::Result<Self> {
+        let variants = match &input.data {
+            syn::Data::Struct(data) => {
+                vec![(None, &data.fields)]
+            }
+            syn::Data::Enum(data) => data
+                .variants
+                .iter()
+                .map(|variant| (Some(&variant.ident), &variant.fields))
+                .collect(),
+            syn::Data::Union(data) => {
+                return Err(syn::Error::new(
+                    data.union_token.span(),
+                    "`PartialEq` cannot be derived structurally for unions",
+                ))
+            }
+        };
+
+        Ok(Self {
+            self_ty: (&input.ident, &input.generics),
+            variants,
+        })
+    }
+}
+
+impl StructuralExpansion<'_> {
+    /// Generates body of the [`PartialEq::eq()`] method implementation for this
+    /// [`StructuralExpansion`].
+    fn eq_body(&self) -> TokenStream {
+        // Special case: empty enum.
+        if self.variants.is_empty() {
+            return quote! { match *self {} };
+        }
+        // Special case: no fields to compare.
+        if self.variants.len() == 1 && self.variants[0].1.is_empty() {
+            return quote! { true };
+        }
+
+        let discriminants_eq = (self.variants.len() > 1).then(|| {
+            quote! {
+                derive_more::core::mem::discriminant(self) ==
+                    derive_more::core::mem::discriminant(__other)
+            }
+        });
+
+        let matched_variants = self
+            .variants
+            .iter()
+            .filter_map(|(variant, fields)| {
+                if fields.is_empty() {
+                    return None;
+                }
+                let variant = variant.map(|variant| quote! { :: #variant });
+                let self_pattern = fields.arm_pattern("__self_");
+                let other_pattern = fields.arm_pattern("__other_");
+                let val_eqs = (0..fields.len()).map(|num| {
+                    let self_val = format_ident!("__self_{num}");
+                    let other_val = format_ident!("__other_{num}");
+                    quote! { #self_val == #other_val }
+                });
+                Some(quote! {
+                    (Self #variant #self_pattern, Self #variant #other_pattern) => {
+                        #( #val_eqs )&&*
+                    }
+                })
+            })
+            .collect::<Vec<_>>();
+        let match_expr = (!matched_variants.is_empty()).then(|| {
+            let always_true_arm =
+                (matched_variants.len() != self.variants.len()).then(|| {
+                    quote! { _ => true }
+                });
+            let unreachable_arm = (self.variants.len() > 1
+                && always_true_arm.is_none())
+            .then(|| {
+                quote! {
+                    // SAFETY: This arm is never reachable, but is required by the expanded
+                    //         `match (self, other)` expression when there is more than one variant.
+                    _ => unsafe { derive_more::core::hint::unreachable_unchecked() },
+                }
+            });
+
+            quote! {
+                match (self, __other) {
+                    #( #matched_variants , )*
+                    #always_true_arm
+                    #unreachable_arm
+                }
+            }
+        });
+
+        let and = (discriminants_eq.is_some() && match_expr.is_some())
+            .then_some(token::AndAnd::default());
+
+        quote! {
+            #discriminants_eq #and #match_expr
+        }
+    }
+}
+
+impl ToTokens for StructuralExpansion<'_> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let ty = self.self_ty.0;
+
+        let generics_search = GenericsSearch::from(self.self_ty.1);
+        let mut generics = self.self_ty.1.clone();
+        for variant in &self.variants {
+            for field_ty in variant.1.iter().map(|field| &field.ty) {
+                if generics_search.any_in(field_ty) {
+                    generics.make_where_clause().predicates.push(parse_quote! {
+                        #field_ty: derive_more::core::cmp::PartialEq
+                    });
+                }
+            }
+        }
+        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+        let eq_body = self.eq_body();
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_generics derive_more::core::cmp::PartialEq for #ty #ty_generics
+                 #where_clause
+            {
+                #[inline]
+                fn eq(&self, __other: &Self) -> bool {
+                    #eq_body
+                }
+            }
+        }
+        .to_tokens(tokens);
+    }
+}
+
+/// Extension of [`syn::Fields`] used by this expansion.
+trait FieldsExt {
+    /// Generates a pattern for matching these [`syn::Fields`] in an arm of a `match` expression.
+    ///
+    /// All the [`syn::Fields`] will be assigned as `{prefix}{num}` bindings for use.
+    fn arm_pattern(&self, prefix: &str) -> TokenStream;
+}
+
+impl FieldsExt for syn::Fields {
+    fn arm_pattern(&self, prefix: &str) -> TokenStream {
+        match self {
+            Self::Named(fields) => {
+                let fields = fields.named.iter().enumerate().map(|(num, field)| {
+                    let name = &field.ident;
+                    let binding = format_ident!("{prefix}{num}");
+                    quote! { #name: #binding }
+                });
+                quote! {{ #( #fields , )* }}
+            }
+            Self::Unnamed(fields) => {
+                let fields = (0..fields.unnamed.len()).map(|num| {
+                    let binding = format_ident!("{prefix}{num}");
+                    quote! { #binding }
+                });
+                quote! {( #( #fields , )* )}
+            }
+            Self::Unit => quote! {},
+        }
+    }
+}
diff --git a/impl/src/utils.rs b/impl/src/utils.rs
index 825d95d..86b15dd 100644
--- a/impl/src/utils.rs
+++ b/impl/src/utils.rs
@@ -24,7 +24,7 @@ use syn::{
 pub(crate) use self::either::Either;
 #[cfg(any(feature = "from", feature = "into"))]
 pub(crate) use self::fields_ext::FieldsExt;
-#[cfg(feature = "as_ref")]
+#[cfg(any(feature = "as_ref", feature = "eq"))]
 pub(crate) use self::generics_search::GenericsSearch;
 #[cfg(any(
     feature = "as_ref",
@@ -2381,7 +2381,7 @@ mod fields_ext {
     impl<T: Len + ?Sized> FieldsExt for T {}
 }
 
-#[cfg(feature = "as_ref")]
+#[cfg(any(feature = "as_ref", feature = "eq"))]
 mod generics_search {
     use syn::visit::Visit;
 
diff --git a/src/lib.rs b/src/lib.rs
index 6d9f7f6..050b8e9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,6 +23,7 @@
 //! [`DerefMut`]: macro@crate::DerefMut
 //! [`AddAssign`-like]: macro@crate::AddAssign
 //! [`MulAssign`-like]: macro@crate::MulAssign
+//! [`PartialEq`]: macro@crate::PartialEq
 //!
 //! [`Constructor`]: macro@crate::Constructor
 //! [`IsVariant`]: macro@crate::IsVariant
@@ -192,6 +193,8 @@ pub mod with_trait {
             UpperHex,
         );
 
+        re_export_traits!("eq", eq_traits, core::cmp, PartialEq);
+
         re_export_traits!("error", error_traits, core::error, Error);
 
         re_export_traits!("from", from_traits, core::convert, From);
@@ -355,6 +358,10 @@ pub mod with_trait {
         Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex,
     };
 
+    #[cfg(feature = "eq")]
+    #[doc(hidden)]
+    pub use all_traits_and_derives::PartialEq;
+
     #[cfg(feature = "error")]
     #[doc(hidden)]
     pub use all_traits_and_derives::Error;
@@ -438,6 +445,7 @@ pub mod with_trait {
     feature = "deref",
     feature = "deref_mut",
     feature = "display",
+    feature = "eq",
     feature = "error",
     feature = "from",
     feature = "from_str",


Test patch:
diff --git a/tests/compile_fail/partial_eq/union.rs b/tests/compile_fail/partial_eq/union.rs
new file mode 100644
index 0000000..5583c5c
--- /dev/null
+++ b/tests/compile_fail/partial_eq/union.rs
@@ -0,0 +1,6 @@
+#[derive(derive_more::PartialEq)]
+union IntOrFloat {
+    i: u32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/partial_eq/union.stderr b/tests/compile_fail/partial_eq/union.stderr
new file mode 100644
index 0000000..c908f8e
--- /dev/null
+++ b/tests/compile_fail/partial_eq/union.stderr
@@ -0,0 +1,5 @@
+error: `PartialEq` cannot be derived structurally for unions
+ --> tests/compile_fail/partial_eq/union.rs:2:1
+  |
+2 | union IntOrFloat {
+  | ^^^^^
diff --git a/tests/partial_eq.rs b/tests/partial_eq.rs
new file mode 100644
index 0000000..1b7dab6
--- /dev/null
+++ b/tests/partial_eq.rs
@@ -0,0 +1,511 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+#![allow(dead_code)] // some code is tested for type checking only
+
+mod structs {
+    mod structural {
+        use derive_more::PartialEq;
+
+        #[test]
+        fn unit() {
+            #[derive(Debug, PartialEq)]
+            struct Baz;
+
+            assert_eq!(Baz, Baz);
+        }
+
+        #[test]
+        fn empty_tuple() {
+            #[derive(Debug, PartialEq)]
+            struct Foo();
+
+            assert_eq!(Foo(), Foo());
+        }
+
+        #[test]
+        fn empty_struct() {
+            #[derive(Debug, PartialEq)]
+            struct Bar {}
+
+            assert_eq!(Bar {}, Bar {});
+        }
+
+        #[test]
+        fn multi_field_tuple() {
+            #[derive(Debug, PartialEq)]
+            struct Foo(bool, i32);
+
+            assert_eq!(Foo(true, 0), Foo(true, 0));
+            assert_ne!(Foo(true, 0), Foo(false, 0));
+            assert_ne!(Foo(true, 0), Foo(true, 1));
+            assert_ne!(Foo(true, 0), Foo(false, 1));
+        }
+
+        #[test]
+        fn multi_field_struct() {
+            #[derive(Debug, PartialEq)]
+            struct Bar {
+                b: bool,
+                i: i32,
+            }
+
+            assert_eq!(Bar { b: true, i: 0 }, Bar { b: true, i: 0 });
+            assert_ne!(Bar { b: true, i: 0 }, Bar { b: false, i: 0 });
+            assert_ne!(Bar { b: true, i: 0 }, Bar { b: true, i: 1 });
+            assert_ne!(Bar { b: true, i: 0 }, Bar { b: false, i: 1 });
+        }
+
+        mod generic {
+            use derive_more::PartialEq;
+
+            trait Some {
+                type Assoc;
+            }
+
+            impl<T> Some for T {
+                type Assoc = bool;
+            }
+
+            #[test]
+            fn multi_field_tuple() {
+                #[derive(Debug, PartialEq)]
+                struct Foo<A: Some, B>(A::Assoc, B);
+
+                assert_eq!(Foo::<(), _>(true, 0), Foo(true, 0));
+                assert_ne!(Foo::<(), _>(true, 0), Foo(false, 0));
+                assert_ne!(Foo::<(), _>(true, 0), Foo(true, 1));
+                assert_ne!(Foo::<(), _>(true, 0), Foo(false, 1));
+            }
+
+            #[test]
+            fn multi_field_struct() {
+                #[derive(Debug, PartialEq)]
+                struct Bar<A, B: Some> {
+                    b: B::Assoc,
+                    i: A,
+                }
+
+                assert_eq!(Bar::<_, ()> { b: true, i: 0 }, Bar { b: true, i: 0 });
+                assert_ne!(Bar::<_, ()> { b: true, i: 0 }, Bar { b: false, i: 0 });
+                assert_ne!(Bar::<_, ()> { b: true, i: 0 }, Bar { b: true, i: 1 });
+                assert_ne!(Bar::<_, ()> { b: true, i: 0 }, Bar { b: false, i: 1 });
+            }
+
+            #[test]
+            fn lifetime() {
+                #[derive(Debug, PartialEq)]
+                struct Foo<'a>(&'a str, i32);
+
+                #[derive(Debug, PartialEq)]
+                struct Bar<'a> {
+                    b: Foo<'a>,
+                    i: i32,
+                }
+
+                assert_eq!(Foo("hi", 0), Foo("hi", 0));
+                assert_ne!(Foo("hi", 0), Foo("bye", 0));
+                assert_ne!(Foo("hi", 0), Foo("hi", 1));
+                assert_ne!(Foo("hi", 0), Foo("bye", 1));
+
+                assert_eq!(
+                    Bar {
+                        b: Foo("hi", 0),
+                        i: 0,
+                    },
+                    Bar {
+                        b: Foo("hi", 0),
+                        i: 0,
+                    },
+                );
+                assert_ne!(
+                    Bar {
+                        b: Foo("hi", 0),
+                        i: 0,
+                    },
+                    Bar {
+                        b: Foo("bye", 0),
+                        i: 0,
+                    },
+                );
+                assert_ne!(
+                    Bar {
+                        b: Foo("hi", 0),
+                        i: 0,
+                    },
+                    Bar {
+                        b: Foo("hi", 0),
+                        i: 1,
+                    },
+                );
+                assert_ne!(
+                    Bar {
+                        b: Foo("hi", 0),
+                        i: 0,
+                    },
+                    Bar {
+                        b: Foo("bye", 0),
+                        i: 1,
+                    },
+                );
+            }
+
+            #[test]
+            fn const_param() {
+                #[derive(Debug, PartialEq)]
+                struct Baz<const N: usize>;
+
+                #[derive(Debug, PartialEq)]
+                struct Foo<const N: usize>([i32; N], i8);
+
+                #[derive(Debug, PartialEq)]
+                struct Bar<const N: usize> {
+                    b: Foo<N>,
+                    i: Baz<N>,
+                }
+
+                assert_eq!(Baz::<1>, Baz);
+
+                assert_eq!(Foo([3], 0), Foo([3], 0));
+                assert_ne!(Foo([3], 0), Foo([4], 0));
+                assert_ne!(Foo([3], 0), Foo([3], 1));
+                assert_ne!(Foo([3], 0), Foo([4], 1));
+
+                assert_eq!(
+                    Bar {
+                        b: Foo([3], 0),
+                        i: Baz,
+                    },
+                    Bar {
+                        b: Foo([3], 0),
+                        i: Baz,
+                    },
+                );
+                assert_ne!(
+                    Bar {
+                        b: Foo([3], 0),
+                        i: Baz,
+                    },
+                    Bar {
+                        b: Foo([3], 1),
+                        i: Baz,
+                    },
+                );
+            }
+        }
+    }
+}
+
+mod enums {
+    mod structural {
+        use derive_more::PartialEq;
+
+        #[test]
+        fn empty() {
+            #[derive(Debug, PartialEq)]
+            enum E {}
+        }
+
+        #[test]
+        fn single_variant_unit() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Baz,
+            }
+
+            assert_eq!(E::Baz, E::Baz);
+        }
+
+        #[test]
+        fn single_variant_empty_tuple() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Foo(),
+            }
+
+            assert_eq!(E::Foo(), E::Foo());
+        }
+
+        #[test]
+        fn single_variant_empty_struct() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Bar {},
+            }
+
+            assert_eq!(E::Bar {}, E::Bar {});
+        }
+
+        #[test]
+        fn single_variant_multi_field_tuple() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Foo(bool, i32),
+            }
+
+            assert_eq!(E::Foo(true, 0), E::Foo(true, 0));
+            assert_ne!(E::Foo(true, 0), E::Foo(false, 0));
+            assert_ne!(E::Foo(true, 0), E::Foo(true, 1));
+            assert_ne!(E::Foo(true, 0), E::Foo(false, 1));
+        }
+
+        #[test]
+        fn single_variant_multi_field_struct() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Bar { b: bool, i: i32 },
+            }
+
+            assert_eq!(E::Bar { b: true, i: 0 }, E::Bar { b: true, i: 0 });
+            assert_ne!(E::Bar { b: true, i: 0 }, E::Bar { b: false, i: 0 });
+            assert_ne!(E::Bar { b: true, i: 0 }, E::Bar { b: true, i: 1 });
+            assert_ne!(E::Bar { b: true, i: 0 }, E::Bar { b: false, i: 1 });
+        }
+
+        #[test]
+        fn multi_variant_empty_field() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Foo(),
+                Bar {},
+                Baz,
+            }
+
+            assert_eq!(E::Foo(), E::Foo());
+
+            assert_eq!(E::Bar {}, E::Bar {});
+
+            assert_eq!(E::Baz, E::Baz);
+
+            assert_ne!(E::Foo(), E::Bar {});
+            assert_ne!(E::Bar {}, E::Foo());
+            assert_ne!(E::Foo(), E::Baz);
+            assert_ne!(E::Baz, E::Foo());
+            assert_ne!(E::Bar {}, E::Baz);
+            assert_ne!(E::Baz, E::Bar {});
+        }
+
+        #[test]
+        fn multi_variant_multi_field() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Foo(bool, i32),
+                Bar { b: bool, i: i32 },
+            }
+
+            assert_eq!(E::Foo(true, 0), E::Foo(true, 0));
+            assert_ne!(E::Foo(true, 0), E::Foo(false, 0));
+            assert_ne!(E::Foo(true, 0), E::Foo(true, 1));
+            assert_ne!(E::Foo(true, 0), E::Foo(false, 1));
+
+            assert_eq!(E::Bar { b: true, i: 0 }, E::Bar { b: true, i: 0 });
+            assert_ne!(E::Bar { b: true, i: 0 }, E::Bar { b: false, i: 0 });
+            assert_ne!(E::Bar { b: true, i: 0 }, E::Bar { b: true, i: 1 });
+            assert_ne!(E::Bar { b: true, i: 0 }, E::Bar { b: false, i: 1 });
+
+            assert_ne!(E::Foo(true, 0), E::Bar { b: true, i: 0 });
+            assert_ne!(E::Bar { b: false, i: 1 }, E::Foo(false, 1));
+        }
+
+        #[test]
+        fn multi_variant_empty_and_multi_field() {
+            #[derive(Debug, PartialEq)]
+            enum E {
+                Foo(bool, i32),
+                Baz,
+            }
+
+            assert_eq!(E::Foo(true, 0), E::Foo(true, 0));
+            assert_ne!(E::Foo(true, 0), E::Foo(false, 0));
+            assert_ne!(E::Foo(true, 0), E::Foo(true, 1));
+            assert_ne!(E::Foo(true, 0), E::Foo(false, 1));
+
+            assert_eq!(E::Baz, E::Baz);
+
+            assert_ne!(E::Foo(true, 0), E::Baz);
+            assert_ne!(E::Baz, E::Foo(false, 1));
+        }
+
+        mod generic {
+            use derive_more::PartialEq;
+
+            trait Some {
+                type Assoc;
+            }
+
+            impl<T> Some for T {
+                type Assoc = bool;
+            }
+
+            #[test]
+            fn single_variant_multi_field_tuple() {
+                #[derive(Debug, PartialEq)]
+                enum E<A: Some, B> {
+                    Foo(A::Assoc, B),
+                }
+
+                assert_eq!(E::<u8, _>::Foo(true, 0), E::Foo(true, 0));
+                assert_ne!(E::<u8, _>::Foo(true, 0), E::Foo(false, 0));
+                assert_ne!(E::<u8, _>::Foo(true, 0), E::Foo(true, 1));
+                assert_ne!(E::<u8, _>::Foo(true, 0), E::Foo(false, 1));
+            }
+
+            #[test]
+            fn single_variant_multi_field_struct() {
+                #[derive(Debug, PartialEq)]
+                enum E<A, B: Some> {
+                    Bar { b: B::Assoc, i: A },
+                }
+
+                assert_eq!(E::<_, ()>::Bar { b: true, i: 0 }, E::Bar { b: true, i: 0 });
+                assert_ne!(
+                    E::<_, ()>::Bar { b: true, i: 0 },
+                    E::Bar { b: false, i: 0 }
+                );
+                assert_ne!(E::<_, ()>::Bar { b: true, i: 0 }, E::Bar { b: true, i: 1 });
+                assert_ne!(
+                    E::<_, ()>::Bar { b: true, i: 0 },
+                    E::Bar { b: false, i: 1 }
+                );
+            }
+
+            #[test]
+            fn multi_variant_empty_and_multi_field() {
+                #[derive(Debug, PartialEq)]
+                enum E<A, B: Some> {
+                    Foo(B::Assoc, A),
+                    Bar { b: B::Assoc, i: A },
+                    Baz,
+                }
+
+                assert_eq!(E::<_, ()>::Foo(true, 0), E::Foo(true, 0));
+                assert_ne!(E::<_, ()>::Foo(true, 0), E::Foo(false, 0));
+                assert_ne!(E::<_, ()>::Foo(true, 0), E::Foo(true, 1));
+                assert_ne!(E::<_, ()>::Foo(true, 0), E::Foo(false, 1));
+
+                assert_eq!(E::<_, ()>::Bar { b: true, i: 0 }, E::Bar { b: true, i: 0 });
+                assert_ne!(
+                    E::<_, ()>::Bar { b: true, i: 0 },
+                    E::Bar { b: false, i: 0 }
+                );
+                assert_ne!(E::<_, ()>::Bar { b: true, i: 0 }, E::Bar { b: true, i: 1 });
+                assert_ne!(
+                    E::<_, ()>::Bar { b: true, i: 0 },
+                    E::Bar { b: false, i: 1 }
+                );
+
+                assert_eq!(E::<i32, ()>::Baz, E::Baz);
+
+                assert_ne!(E::<_, ()>::Foo(true, 0), E::Bar { b: true, i: 0 });
+                assert_ne!(E::<_, ()>::Bar { b: false, i: 1 }, E::Foo(false, 1));
+                assert_ne!(E::<_, ()>::Foo(true, 0), E::Baz);
+                assert_ne!(E::<_, ()>::Baz, E::Foo(false, 1));
+                assert_ne!(E::<_, ()>::Bar { b: false, i: 1 }, E::Baz);
+                assert_ne!(E::<_, ()>::Baz, E::Bar { b: true, i: 0 });
+            }
+
+            #[test]
+            fn lifetime() {
+                #[derive(Debug, PartialEq)]
+                enum E1<'a> {
+                    Foo(&'a str, i32),
+                }
+
+                #[derive(Debug, PartialEq)]
+                enum E2<'a> {
+                    Bar { b: E1<'a>, i: i32 },
+                }
+
+                assert_eq!(E1::Foo("hi", 0), E1::Foo("hi", 0));
+                assert_ne!(E1::Foo("hi", 0), E1::Foo("bye", 0));
+                assert_ne!(E1::Foo("hi", 0), E1::Foo("hi", 1));
+                assert_ne!(E1::Foo("hi", 0), E1::Foo("bye", 1));
+
+                assert_eq!(
+                    E2::Bar {
+                        b: E1::Foo("hi", 0),
+                        i: 0,
+                    },
+                    E2::Bar {
+                        b: E1::Foo("hi", 0),
+                        i: 0,
+                    },
+                );
+                assert_ne!(
+                    E2::Bar {
+                        b: E1::Foo("hi", 0),
+                        i: 0,
+                    },
+                    E2::Bar {
+                        b: E1::Foo("bye", 0),
+                        i: 0,
+                    },
+                );
+                assert_ne!(
+                    E2::Bar {
+                        b: E1::Foo("hi", 0),
+                        i: 0,
+                    },
+                    E2::Bar {
+                        b: E1::Foo("hi", 0),
+                        i: 1,
+                    },
+                );
+                assert_ne!(
+                    E2::Bar {
+                        b: E1::Foo("hi", 0),
+                        i: 0,
+                    },
+                    E2::Bar {
+                        b: E1::Foo("bye", 0),
+                        i: 1,
+                    },
+                );
+            }
+
+            #[test]
+            fn const_param() {
+                #[derive(Debug, PartialEq)]
+                enum E3<const N: usize> {
+                    Baz,
+                }
+
+                #[derive(Debug, PartialEq)]
+                enum E1<const N: usize> {
+                    Foo([i32; N], i8),
+                }
+
+                #[derive(Debug, PartialEq)]
+                enum E2<const N: usize> {
+                    Bar { b: E1<N>, i: E3<N> },
+                }
+
+                assert_eq!(E3::<1>::Baz, E3::Baz);
+
+                assert_eq!(E1::Foo([3], 0), E1::Foo([3], 0));
+                assert_ne!(E1::Foo([3], 0), E1::Foo([4], 0));
+                assert_ne!(E1::Foo([3], 0), E1::Foo([3], 1));
+                assert_ne!(E1::Foo([3], 0), E1::Foo([4], 1));
+
+                assert_eq!(
+                    E2::Bar {
+                        b: E1::Foo([3], 0),
+                        i: E3::Baz,
+                    },
+                    E2::Bar {
+                        b: E1::Foo([3], 0),
+                        i: E3::Baz,
+                    },
+                );
+                assert_ne!(
+                    E2::Bar {
+                        b: E1::Foo([3], 0),
+                        i: E3::Baz,
+                    },
+                    E2::Bar {
+                        b: E1::Foo([3], 1),
+                        i: E3::Baz,
+                    },
+                );
+            }
+        }
+    }
+}