Fix equality comparison for inline class over inline class

^KT-54536: Fixed
diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java
index 63d4007..c59661b 100644
--- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java
+++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java
@@ -21642,6 +21642,12 @@
         }
 
         @Test
+        @TestMetadata("inlineOverInlineWithCustomEquals.kt")
+        public void testInlineOverInlineWithCustomEquals() throws Exception {
+            runTest("compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
+        }
+
+        @Test
         @TestMetadata("inlineToString.kt")
         public void testInlineToString() throws Exception {
             runTest("compiler/testData/codegen/box/inlineClasses/inlineToString.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
@@ -22260,6 +22266,12 @@
         }
 
         @Test
+        @TestMetadata("kt54536.kt")
+        public void testKt54536() throws Exception {
+            runTest("compiler/testData/codegen/box/inlineClasses/kt54536.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
+        }
+
+        @Test
         @TestMetadata("mangledDefaultParameterFunction.kt")
         public void testMangledDefaultParameterFunction() throws Exception {
             runTest("compiler/testData/codegen/box/inlineClasses/mangledDefaultParameterFunction.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt
index 8e70511..efaaba0 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt
@@ -489,10 +489,11 @@
         val untypedEquals = valueClass.functions.single { it.overridesEqualsFromAny }
 
         function.body = context.createIrBuilder(valueClass.symbol).run {
+            val context = this@JvmInlineClassLowering.context
             irExprBody(
                 if (typedEqualsStaticReplacement == null) {
                     if (untypedEquals.origin == IrDeclarationOrigin.DEFINED) {
-                        val boxFunction = this@JvmInlineClassLowering.context.inlineClassReplacements.getBoxFunction(valueClass)
+                        val boxFunction = context.inlineClassReplacements.getBoxFunction(valueClass)
 
                         fun irBox(expr: IrExpression) = irCall(boxFunction).apply { putValueArgument(0, expr) }
 
@@ -501,7 +502,19 @@
                             putValueArgument(0, irBox(irGet(right)))
                         }
                     } else {
-                        irEquals(coerceInlineClasses(irGet(left), left.type, type), coerceInlineClasses(irGet(right), right.type, type))
+                        val underlyingType = getInlineClassUnderlyingType(valueClass)
+                        val underlyingClass = underlyingType.getClass()
+                        // We can't directly compare unboxed values of underlying inline class since this class can have custom equals
+                        if (underlyingClass?.isSingleFieldValueClass == true && !underlyingType.isNullable()) {
+                            val underlyingClassEq =
+                                context.inlineClassReplacements.getSpecializedEqualsMethod(underlyingClass, context.irBuiltIns)
+                            irCall(underlyingClassEq).apply {
+                                putValueArgument(0, coerceInlineClasses(irGet(left), left.type, underlyingType))
+                                putValueArgument(1, coerceInlineClasses(irGet(right), right.type, underlyingType))
+                            }
+                        } else {
+                            irEquals(coerceInlineClasses(irGet(left), left.type, type), coerceInlineClasses(irGet(right), right.type, type))
+                        }
                     }
                 } else {
                     irCall(typedEqualsStaticReplacement).apply {
diff --git a/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt b/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt
new file mode 100644
index 0000000..1adf89b
--- /dev/null
+++ b/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt
@@ -0,0 +1,64 @@
+// WITH_STDLIB
+// WORKS_WHEN_VALUE_CLASS
+// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
+// TARGET_BACKEND: JVM_IR
+
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class A(val x: Int) {
+    fun equals(other: A) = true
+}
+
+class C
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class B1(val x: A)
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class B2(val x: A?)
+
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class D1(val x: C) {
+    fun equals(other: D1) = true
+}
+
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class D2(val x: C?) {
+    fun equals(other: D2) = true
+}
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class E1(val x: D1)
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class E2(val x: D2)
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class F<T>(val x: T)
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class G<T : D1>(val x: T)
+
+fun box(): String {
+    if (E1(D1(C())) != E1(D1(C()))) return "Fail 1"
+
+    if (E2(D2(C())) != E2(D2(C()))) return "Fail 2.1"
+    if (E2(D2(null)) != E2(D2(C()))) return "Fail 2.2"
+    if (E2(D2(C())) != E2(D2(null))) return "Fail 2.3"
+    if (E2(D2(null)) != E2(D2(null))) return "Fail 2.4"
+
+    if (B1(A(0)) != B1(A(5))) return "Fail 3"
+
+    if (B2(A(0)) != B2(A(5))) return "Fail 4.1"
+    if (B2(null) == B2(A(5))) return "Fail 4.2"
+    if (B2(A(0)) == B2(null)) return "Fail 4.3"
+    if (B2(null) != B2(null)) return "Fail 4.4"
+
+    if (F(D1(C())) != F(D1(C()))) return "Fail 5"
+
+    if (G(D1(C())) != G(D1(C()))) return "Fail 6"
+
+    return "OK"
+}
\ No newline at end of file
diff --git a/compiler/testData/codegen/box/inlineClasses/kt54536.kt b/compiler/testData/codegen/box/inlineClasses/kt54536.kt
new file mode 100644
index 0000000..ba5d880
--- /dev/null
+++ b/compiler/testData/codegen/box/inlineClasses/kt54536.kt
@@ -0,0 +1,18 @@
+// WITH_STDLIB
+// WORKS_WHEN_VALUE_CLASS
+// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
+// TARGET_BACKEND: JVM_IR
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class A(val x: Int) {
+    fun equals(other: A) = x % 5 == other.x % 5
+}
+
+OPTIONAL_JVM_INLINE_ANNOTATION
+value class B(val x: A)
+
+fun box() = if (B(A(0)) == B(A(5))) "OK" else "Fail"
+
+// CHECK_BYTECODE_TEXT
+// 0 INVOKESTATIC B.box-impl
+// 0 INVOKESTATIC A.box-impl
\ No newline at end of file
diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
index 019d4eb..5b00788 100644
--- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
+++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
@@ -21642,6 +21642,12 @@
         }
 
         @Test
+        @TestMetadata("inlineOverInlineWithCustomEquals.kt")
+        public void testInlineOverInlineWithCustomEquals() throws Exception {
+            runTest("compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
+        }
+
+        @Test
         @TestMetadata("inlineToString.kt")
         public void testInlineToString() throws Exception {
             runTest("compiler/testData/codegen/box/inlineClasses/inlineToString.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
@@ -22260,6 +22266,12 @@
         }
 
         @Test
+        @TestMetadata("kt54536.kt")
+        public void testKt54536() throws Exception {
+            runTest("compiler/testData/codegen/box/inlineClasses/kt54536.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
+        }
+
+        @Test
         @TestMetadata("mangledDefaultParameterFunction.kt")
         public void testMangledDefaultParameterFunction() throws Exception {
             runTest("compiler/testData/codegen/box/inlineClasses/mangledDefaultParameterFunction.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());