Repository: intellij-rust/intellij-rust
Issue: assert!(a == b) wrongly correct to assert_eq!(a, b) where T: !Debug

Code such as
```rust
#[derive(PartialEq, Eq)] // Note: NOT Debug
struct X(u32);

fn a() {
    assert!(X(0) == X(0));
}
```

will trigger a suggestion to convert the assertion to `assert_eq!(X(0), X(0))`. This is incorrect because X isn't Debug, and `assert_eq!` requires this.

Changed files:
src/test/kotlin/org/rust/ide/inspections/RsAssertEqualInspectionTest.kt, src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt, src/main/kotlin/org/rust/ide/inspections/RsAssertEqualInspection.kt

diff --git a/src/main/kotlin/org/rust/ide/inspections/RsAssertEqualInspection.kt b/src/main/kotlin/org/rust/ide/inspections/RsAssertEqualInspection.kt
index e3e16a178..92d9585a4 100644
--- a/src/main/kotlin/org/rust/ide/inspections/RsAssertEqualInspection.kt
+++ b/src/main/kotlin/org/rust/ide/inspections/RsAssertEqualInspection.kt
@@ -14,6 +14,8 @@ import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.EqualityOp
 import org.rust.lang.core.psi.ext.macroName
 import org.rust.lang.core.psi.ext.operatorType
+import org.rust.lang.core.resolve.ImplLookup
+import org.rust.lang.core.types.type
 
 class RsAssertEqualInspection : RsLocalInspectionTool() {
     override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : RsVisitor() {
@@ -24,6 +26,15 @@ class RsAssertEqualInspection : RsLocalInspectionTool() {
             if (macroName != "assert") return
 
             val expr = assertMacroArg.expr as? RsBinaryExpr ?: return
+            val leftType = expr.left.type
+            val rightType = (expr.right ?: return).type
+
+            val lookup = ImplLookup.relativeTo(expr)
+            // The `assert_eq!` macro, as opposed to `assert!`, requires both arguments to implement `core::fmt::Debug`.
+            if (!lookup.isDebug(leftType) || !lookup.isDebug(rightType)) {
+                return
+            }
+
             when (expr.operatorType) {
                 EqualityOp.EQ -> {
                     holder.registerProblem(
diff --git a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
index 6bb5f064d..c9eb85ea8 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
@@ -155,6 +155,9 @@ class ImplLookup(
     private val sizedTrait: RsTraitItem? by lazy(NONE) {
         RsLangItemIndex.findLangItem(project, "sized")
     }
+    private val debugTrait: RsTraitItem? by lazy(NONE) {
+        RsLangItemIndex.findLangItem(project, "debug_trait")
+    }
     private val derefTraitAndTarget: Pair<RsTraitItem, RsTypeAlias>? = run {
         val trait = RsLangItemIndex.findLangItem(project, "deref") ?: return@run null
         trait.findAssociatedType("Target")?.let { trait to it }
@@ -236,6 +239,9 @@ class ImplLookup(
 
     private fun getHardcodedImplsForPrimitives(ty: Ty): Collection<BoundElement<RsTraitItem>> {
         val impls = mutableListOf<BoundElement<RsTraitItem>>()
+        debugTrait?.let {
+            impls.add(BoundElement(it))
+        }
         if (ty is TyNumeric || ty is TyInfer.IntVar || ty is TyInfer.FloatVar) {
             // libcore/ops/arith.rs libcore/ops/bit.rs
             impls += arithOps.map { it.withSubst(ty).substAssocType("Output", ty) }
@@ -618,6 +624,7 @@ class ImplLookup(
 
     fun isCopy(ty: Ty): Boolean = ty.isTraitImplemented(copyTrait)
     fun isSized(ty: Ty): Boolean = ty.isTraitImplemented(sizedTrait)
+    fun isDebug(ty: Ty): Boolean = ty.isTraitImplemented(debugTrait)
 
     private fun Ty.isTraitImplemented(trait: RsTraitItem?): Boolean {
         if (trait == null) return false
diff --git a/src/test/kotlin/org/rust/ide/inspections/RsAssertEqualInspectionTest.kt b/src/test/kotlin/org/rust/ide/inspections/RsAssertEqualInspectionTest.kt
index c440f13dd..56223b3b1 100644
--- a/src/test/kotlin/org/rust/ide/inspections/RsAssertEqualInspectionTest.kt
+++ b/src/test/kotlin/org/rust/ide/inspections/RsAssertEqualInspectionTest.kt
@@ -5,7 +5,7 @@
 
 package org.rust.ide.inspections
 
-class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspection()) {
+class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspection(), useStdLib = true) {
     fun `test simple assert_eq fix`() = checkFixByText("Convert to assert_eq!", """
         fn main() {
             let x = 10;
@@ -18,7 +18,7 @@ class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspectio
             let y = 10;
             assert_eq!(x, y);
         }
-    """)
+    """, checkWeakWarn = true)
 
     fun `test expr assert_eq fix`() = checkFixByText("Convert to assert_eq!", """
         fn answer() -> i32 {
@@ -36,7 +36,7 @@ class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspectio
         fn main() {
             assert_eq!(answer(), 42);
         }
-    """)
+    """, checkWeakWarn = true)
 
     fun `test simple assert_eq fix with format_args`() = checkFixByText("Convert to assert_eq!", """
         fn main() {
@@ -50,7 +50,7 @@ class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspectio
             let y = 10;
             assert_eq!(x, y, "format {}", 0);
         }
-    """)
+    """, checkWeakWarn = true)
 
     fun `test simple assert_ne fix`() = checkFixByText("Convert to assert_ne!", """
         fn main() {
@@ -64,7 +64,7 @@ class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspectio
             let y = 42;
             assert_ne!(x, y);
         }
-    """)
+    """, checkWeakWarn = true)
 
     fun `test expr assert_ne fix`() = checkFixByText("Convert to assert_ne!", """
         fn answer() -> i32 {
@@ -82,13 +82,13 @@ class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspectio
         fn main() {
             assert_ne!(answer(), 50);
         }
-    """)
+    """, checkWeakWarn = true)
 
     fun `test simple assert_ne fix with format_args`() = checkFixByText("Convert to assert_ne!", """
         fn main() {
             let x = 10;
             let y = 10;
-            <weak_warning descr="assert!(a == b) can be assert_eq!(a, b)">assert!(x != y, "format {}", 0<caret>)</weak_warning>;
+            <weak_warning descr="assert!(a != b) can be assert_ne!(a, b)">assert!(x != y, "format {}", 0<caret>)</weak_warning>;
         }
     """, """
         fn main() {
@@ -96,5 +96,36 @@ class RsAssertEqualInspectionTest : RsInspectionsTestBase(RsAssertEqualInspectio
             let y = 10;
             assert_ne!(x, y, "format {}", 0);
         }
+    """, checkWeakWarn = true)
+
+    fun `test fix unavailable when arguments do not implement Debug`() = checkFixIsUnavailable("Convert to assert_eq!", """
+        #[derive(PartialEq)]
+        struct Number(u32);
+
+        fn main() {
+            let x = Number(10);
+            let y = Number(10);
+            assert!(x == y/*caret*/);
+        }
+    """)
+
+    fun `test fix available when arguments derive Debug`() = checkFixByText("Convert to assert_ne!", """
+        #[derive(Debug, PartialEq)]
+        struct Number(u32);
+
+        fn main() {
+            let x = Number(10);
+            let y = Number(10);
+            assert!(x != y/*caret*/);
+        }
+    """, """
+        #[derive(Debug, PartialEq)]
+        struct Number(u32);
+
+        fn main() {
+            let x = Number(10);
+            let y = Number(10);
+            assert_ne!(x, y);
+        }
     """)
 }