'operator' keyword for 'typed' equals in inline classes allowed
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java
index 5bdc9fa..a4765ba 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java
@@ -17849,6 +17849,12 @@
             }
 
             @Test
+            @TestMetadata("illegalEqualsOverridingInInlineClass.kt")
+            public void testIllegalEqualsOverridingInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
             public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassCanImplementInterfaceByDelegation.kt");
@@ -17957,6 +17963,12 @@
             }
 
             @Test
+            @TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
+            public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
             public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/unsignedLiteralsWithoutArtifactOnClasspath.kt");
diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java
index 99f3c86..40b1ea0 100644
--- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java
+++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java
@@ -17849,6 +17849,12 @@
             }
 
             @Test
+            @TestMetadata("illegalEqualsOverridingInInlineClass.kt")
+            public void testIllegalEqualsOverridingInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
             public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassCanImplementInterfaceByDelegation.kt");
@@ -17957,6 +17963,12 @@
             }
 
             @Test
+            @TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
+            public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
             public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/unsignedLiteralsWithoutArtifactOnClasspath.kt");
diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java
index 290b280..3321eac 100644
--- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java
+++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java
@@ -17849,6 +17849,12 @@
             }
 
             @Test
+            @TestMetadata("illegalEqualsOverridingInInlineClass.kt")
+            public void testIllegalEqualsOverridingInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
             public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassCanImplementInterfaceByDelegation.kt");
@@ -17957,6 +17963,12 @@
             }
 
             @Test
+            @TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
+            public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
             public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/unsignedLiteralsWithoutArtifactOnClasspath.kt");
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt
index 27e09e0..1fca77a 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt
@@ -25,6 +25,7 @@
 import org.jetbrains.kotlin.diagnostics.reportOn
 import org.jetbrains.kotlin.fir.containingClass
 import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
+import org.jetbrains.kotlin.fir.declarations.utils.isInline
 import org.jetbrains.kotlin.fir.declarations.utils.isOperator
 import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
 import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
@@ -184,10 +185,21 @@
         checkFor(
             EQUALS,
             member,
-            Checks.full("must override ''equals()'' in Any") { ctx, function ->
-                val containingClassSymbol = function.containingClass()?.toFirRegularClassSymbol(ctx.session) ?: return@full true
-                function.overriddenFunctions(containingClassSymbol, ctx).any {
-                    it.containingClass()?.classId == StandardClassIds.Any
+            object : Check {
+                override fun check(context: CheckerContext, function: FirSimpleFunction): String? {
+                    val containingClassSymbol = function.containingClass()?.toFirRegularClassSymbol(context.session) ?: return null
+                    if (function.overriddenFunctions(containingClassSymbol, context)
+                            .any { it.containingClass()?.classId == StandardClassIds.Any }
+                        || function.isTypedEqualsInInlineClass(context.session)
+                    ) {
+                        return null
+                    }
+                    return buildString {
+                        append("must override ''equals()'' in Any")
+                        if (containingClassSymbol.isInline) {
+                            append(" or define ''equals(other: ${containingClassSymbol.name}): Boolean''")
+                        }
+                    }
                 }
             }
         )
diff --git a/compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt b/compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt
new file mode 100644
index 0000000..48c2ff1
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt
@@ -0,0 +1,27 @@
+// FIR_IDENTICAL
+// WITH_STDLIB
+// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
+
+@JvmInline
+value class IC1(val x: Int) {
+    <!ILLEGAL_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?): Boolean<!> {
+        if (other !is IC1) {
+            return false
+        }
+        return x == other.x
+    }
+}
+
+@JvmInline
+value class IC2(val x: Int) {
+    override fun hashCode() = 0
+}
+
+@JvmInline
+value class IC3(val x: Int) {
+    override fun equals(other: Any?) = true
+
+    fun equals(other: IC3) = true
+
+    override fun hashCode() = 0
+}
\ No newline at end of file
diff --git a/compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.txt b/compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.txt
new file mode 100644
index 0000000..06e3b9d
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.txt
@@ -0,0 +1,26 @@
+package
+
+@kotlin.jvm.JvmInline public final value class IC1 {
+    public constructor IC1(/*0*/ x: kotlin.Int)
+    public final val x: kotlin.Int
+    public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+    public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+    public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+@kotlin.jvm.JvmInline public final value class IC2 {
+    public constructor IC2(/*0*/ x: kotlin.Int)
+    public final val x: kotlin.Int
+    public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+    public open override /*1*/ fun hashCode(): kotlin.Int
+    public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+@kotlin.jvm.JvmInline public final value class IC3 {
+    public constructor IC3(/*0*/ x: kotlin.Int)
+    public final val x: kotlin.Int
+    public final fun equals(/*0*/ other: IC3): kotlin.Boolean
+    public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+    public open override /*1*/ fun hashCode(): kotlin.Int
+    public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
diff --git a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt
new file mode 100644
index 0000000..26dc6c9
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt
@@ -0,0 +1,20 @@
+// FIR_IDENTICAL
+// WITH_STDLIB
+// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
+
+@JvmInline
+value class IC1(val x: Int) {
+    override fun equals(other: Any?) = true
+
+    operator fun equals(other: IC1) = true
+
+    override fun hashCode() = 0
+}
+
+@JvmInline
+value class IC2(val x: Int) {
+    <!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC1) = true
+
+    <!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC2) {
+    }
+}
\ No newline at end of file
diff --git a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt
new file mode 100644
index 0000000..e304e65
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt
@@ -0,0 +1,20 @@
+package
+
+@kotlin.jvm.JvmInline public final value class IC1 {
+    public constructor IC1(/*0*/ x: kotlin.Int)
+    public final val x: kotlin.Int
+    public final operator fun equals(/*0*/ other: IC1): kotlin.Boolean
+    public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+    public open override /*1*/ fun hashCode(): kotlin.Int
+    public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
+
+@kotlin.jvm.JvmInline public final value class IC2 {
+    public constructor IC2(/*0*/ x: kotlin.Int)
+    public final val x: kotlin.Int
+    public final operator fun equals(/*0*/ other: IC1): kotlin.Boolean
+    public final operator fun equals(/*0*/ other: IC2): kotlin.Unit
+    public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
+    public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
+    public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
+}
diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java
index 9dc8d50..a0ad526 100644
--- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java
+++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java
@@ -17855,6 +17855,12 @@
             }
 
             @Test
+            @TestMetadata("illegalEqualsOverridingInInlineClass.kt")
+            public void testIllegalEqualsOverridingInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
             public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassCanImplementInterfaceByDelegation.kt");
@@ -17963,6 +17969,12 @@
             }
 
             @Test
+            @TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
+            public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
+                runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
+            }
+
+            @Test
             @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
             public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
                 runTest("compiler/testData/diagnostics/tests/inlineClasses/unsignedLiteralsWithoutArtifactOnClasspath.kt");
diff --git a/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt b/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt
index 5fb6beb..0f61c7e 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt
@@ -24,6 +24,7 @@
 import org.jetbrains.kotlin.resolve.descriptorUtil.classId
 import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
 import org.jetbrains.kotlin.resolve.descriptorUtil.module
+import org.jetbrains.kotlin.resolve.isInlineClass
 import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
 import org.jetbrains.kotlin.types.KotlinType
 import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
@@ -198,8 +199,14 @@
         Checks(RANGE_UNTIL, MemberOrExtension, SingleValueParameter, NoDefaultAndVarargsCheck),
         Checks(EQUALS, Member) {
             fun DeclarationDescriptor.isAny() = this is ClassDescriptor && KotlinBuiltIns.isAny(this)
-            ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() }) {
-                "must override ''equals()'' in Any"
+            ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() }
+                           || (containingDeclaration.isInlineClass() && isTypedEqualsInInlineClass())) {
+                buildString {
+                    append("must override ''equals()'' in Any")
+                    if (containingDeclaration.isInlineClass()) {
+                        append(" or define ''equals(other: ${containingDeclaration.name}): Boolean''")
+                    }
+                }
             }
         },
         Checks(COMPARE_TO, MemberOrExtension, ReturnsInt, SingleValueParameter, NoDefaultAndVarargsCheck),